Skip to content

Commit 3b2a05d

Browse files
committed
feat: add tests for voting example contract
1 parent 92ff824 commit 3b2a05d

File tree

21 files changed

+190
-81
lines changed

21 files changed

+190
-81
lines changed

examples/hello-world-abi/contract.spec.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { assert, Bytes, TransactionType } from '@algorandfoundation/algorand-typescript'
22
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
33
import { afterEach, describe, expect, it } from 'vitest'
4-
import { ABI_RETURN_VALUE_LOG_PREFIX } from '../../src/constants'
54
import HelloWorldContract from './contract.algo'
65

76
describe('HelloWorldContract', () => {
@@ -16,9 +15,8 @@ describe('HelloWorldContract', () => {
1615

1716
expect(result).toBe('Bananas')
1817
const bananasBytes = Bytes('Bananas')
19-
const abiLog = ABI_RETURN_VALUE_LOG_PREFIX.concat(bananasBytes)
2018
const logs = ctx.exportLogs(ctx.txn.lastActive.appId.id, 's', 'b')
21-
expect(logs).toStrictEqual([result, abiLog])
19+
expect(logs).toStrictEqual([result, bananasBytes])
2220
})
2321
it('logs the returned value when sayHello is called', async () => {
2422
const contract = ctx.contract.create(HelloWorldContract)
@@ -27,8 +25,7 @@ describe('HelloWorldContract', () => {
2725

2826
expect(result).toBe('Hello John Doe')
2927
const helloBytes = Bytes('Hello John Doe')
30-
const abiLog = ABI_RETURN_VALUE_LOG_PREFIX.concat(helloBytes)
3128
const logs = ctx.exportLogs(ctx.txn.lastActive.appId.id, 's', 'b')
32-
expect(logs).toStrictEqual([result, abiLog])
29+
expect(logs).toStrictEqual([result, helloBytes])
3330
})
3431
})

examples/htlc-logicsig/signature.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { Account, Bytes } from '@algorandfoundation/algorand-typescript'
22
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
33
import algosdk from 'algosdk'
44
import { afterEach, describe, expect, it } from 'vitest'
5-
import { ZERO_ADDRESS } from '../../src/constants'
65
import HashedTimeLockedLogicSig from './signature.algo'
76

7+
const ZERO_ADDRESS_B32 = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ'
8+
const ZERO_ADDRESS = Bytes.fromBase32(ZERO_ADDRESS_B32)
9+
810
describe('HTLC LogicSig', () => {
911
const ctx = new TestExecutionContext()
1012

examples/precompiled/contract.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { arc4 } from '@algorandfoundation/algorand-typescript'
1+
import { arc4, Bytes } from '@algorandfoundation/algorand-typescript'
22
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
33
import { afterEach, describe, it } from 'vitest'
4-
import { ABI_RETURN_VALUE_LOG_PREFIX, MAX_BYTES_SIZE } from '../../src/constants'
5-
import { asUint64Cls } from '../../src/util'
64
import { HelloFactory } from './contract.algo'
75
import { Hello, HelloTemplate, HelloTemplateCustomPrefix, LargeProgram, TerribleCustodialAccount } from './precompiled-apps.algo'
86

7+
const MAX_BYTES_SIZE = 4096
8+
const ABI_RETURN_VALUE_LOG_PREFIX = Bytes.fromHex('151F7C75')
9+
910
describe('pre compiled app calls', () => {
1011
const ctx = new TestExecutionContext()
1112
afterEach(() => {
@@ -58,7 +59,7 @@ describe('pre compiled app calls', () => {
5859
// Arrange
5960
const largeProgramApp = ctx.any.application({
6061
approvalProgram: ctx.any.bytes(20),
61-
appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(asUint64Cls(MAX_BYTES_SIZE).toBytes().asAlgoTs())],
62+
appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(MAX_BYTES_SIZE))],
6263
})
6364
ctx.setCompiledApp(LargeProgram, largeProgramApp.id)
6465

examples/voting/contract.algo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export class VotingRoundApp extends arc4.Contract {
154154
note: note,
155155
fee: Global.minTxnFee,
156156
})
157-
.submit().configAsset
157+
.submit().createdAsset
158158
}
159159

160160
@abimethod({ readonly: true })

examples/voting/contract.spec.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Bytes, Uint64 } from '@algorandfoundation/algorand-typescript'
2+
import { bytesToUint8Array, TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
3+
import { DynamicArray, UintN8 } from '@algorandfoundation/algorand-typescript/arc4'
4+
import nacl from 'tweetnacl'
5+
import { afterEach, describe, expect, it } from 'vitest'
6+
import { VotingRoundApp } from './contract.algo'
7+
8+
describe('VotingRoundApp', () => {
9+
const ctx = new TestExecutionContext()
10+
11+
const boostrapMinBalanceReq = Uint64(287100)
12+
const voteMinBalanceReq = Uint64(21300)
13+
const tallyBoxSize = 208
14+
const keyPair = nacl.sign.keyPair()
15+
const voteId = ctx.any.string()
16+
17+
const createContract = () => {
18+
const contract = ctx.contract.create(VotingRoundApp)
19+
const snapshotPublicKey = Bytes(keyPair.publicKey)
20+
const metadataIpfsCid = ctx.any.string(16)
21+
const startTime = ctx.any.uint64(Date.now() - 10_000, Date.now())
22+
const endTime = ctx.any.uint64(Date.now() + 10_000, Date.now() + 100_000)
23+
const optionCounts = new DynamicArray<UintN8>(
24+
...Array(13)
25+
.fill(0)
26+
.map(() => new UintN8(2)),
27+
)
28+
const quorum = ctx.any.uint64()
29+
const nftImageUrl = ctx.any.string(64)
30+
contract.create(voteId, snapshotPublicKey, metadataIpfsCid, startTime, endTime, optionCounts, quorum, nftImageUrl)
31+
return contract
32+
}
33+
34+
afterEach(() => {
35+
ctx.reset()
36+
})
37+
38+
it('shoulld be able to bootstrap', () => {
39+
const contract = createContract()
40+
const app = ctx.ledger.getApplicationForContract(contract)
41+
contract.bootstrap(ctx.any.txn.payment({ receiver: app.address, amount: boostrapMinBalanceReq }))
42+
43+
expect(contract.isBootstrapped.value).toEqual(true)
44+
expect(contract.tallyBox.value).toEqual(Bytes.fromHex('00'.repeat(tallyBoxSize)))
45+
})
46+
47+
it('should be able to get pre conditions', () => {
48+
const contract = createContract()
49+
const app = ctx.ledger.getApplicationForContract(contract)
50+
contract.bootstrap(ctx.any.txn.payment({ receiver: app.address, amount: boostrapMinBalanceReq }))
51+
52+
const account = ctx.any.account()
53+
const signature = nacl.sign.detached(bytesToUint8Array(account.bytes), keyPair.secretKey)
54+
ctx.txn.createScope([ctx.any.txn.applicationCall({ sender: account })]).execute(() => {
55+
const preconditions = contract.getPreconditions(Bytes(signature))
56+
57+
expect(preconditions.is_allowed_to_vote).toEqual(1)
58+
expect(preconditions.is_voting_open).toEqual(1)
59+
expect(preconditions.has_already_voted).toEqual(0)
60+
expect(preconditions.current_time).toEqual(ctx.txn.activeGroup.latestTimestamp)
61+
})
62+
})
63+
64+
it('should be able to vote', () => {
65+
const contract = createContract()
66+
const app = ctx.ledger.getApplicationForContract(contract)
67+
contract.bootstrap(ctx.any.txn.payment({ receiver: app.address, amount: boostrapMinBalanceReq }))
68+
69+
const account = ctx.any.account()
70+
const signature = nacl.sign.detached(bytesToUint8Array(account.bytes), keyPair.secretKey)
71+
const answerIds = new DynamicArray<UintN8>(
72+
...Array(13)
73+
.fill(0)
74+
.map(() => new UintN8(Math.ceil(Math.random() * 10) % 2)),
75+
)
76+
77+
ctx.txn.createScope([ctx.any.txn.applicationCall({ appId: app, sender: account })]).execute(() => {
78+
contract.vote(ctx.any.txn.payment({ receiver: app.address, amount: voteMinBalanceReq }), Bytes(signature), answerIds)
79+
80+
expect(contract.votesByAccount.get(account).bytes).toEqual(answerIds.bytes)
81+
expect(contract.voterCount.value).toEqual(13)
82+
})
83+
})
84+
85+
it('should be able to close', () => {
86+
const contract = createContract()
87+
const app = ctx.ledger.getApplicationForContract(contract)
88+
contract.bootstrap(ctx.any.txn.payment({ receiver: app.address, amount: boostrapMinBalanceReq }))
89+
90+
contract.close()
91+
92+
expect(contract.closeTime.value).toEqual(ctx.txn.lastGroup.latestTimestamp)
93+
expect(contract.nftAsset.value.name).toEqual(`[VOTE RESULT] ${voteId}`)
94+
expect(contract.nftAsset.value.unitName).toEqual('VOTERSLT')
95+
})
96+
})

src/decode-logs.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
import { bytes, op } from '@algorandfoundation/algorand-typescript'
2+
import { ABI_RETURN_VALUE_LOG_PREFIX } from './constants'
3+
import { asNumber } from './util'
24

35
export type LogDecoding = 'i' | 's' | 'b'
46

57
export type DecodedLog<T extends LogDecoding> = T extends 'i' ? bigint : T extends 's' ? string : Uint8Array
68
export type DecodedLogs<T extends [...LogDecoding[]]> = {
79
[Index in keyof T]: DecodedLog<T[Index]>
810
} & { length: T['length'] }
11+
12+
const ABI_RETURN_VALUE_LOG_PREFIX_LENGTH = asNumber(ABI_RETURN_VALUE_LOG_PREFIX.length)
913
export function decodeLogs<const T extends [...LogDecoding[]]>(logs: bytes[], decoding: T): DecodedLogs<T> {
1014
return logs.map((log, i) => {
15+
const value = log.slice(0, ABI_RETURN_VALUE_LOG_PREFIX_LENGTH).equals(ABI_RETURN_VALUE_LOG_PREFIX)
16+
? log.slice(ABI_RETURN_VALUE_LOG_PREFIX_LENGTH)
17+
: log
1118
switch (decoding[i]) {
1219
case 'i':
13-
return op.btoi(log)
20+
return op.btoi(value)
1421
case 's':
15-
return log.toString()
22+
return value.toString()
1623
default:
17-
return log
24+
return value
1825
}
1926
}) as DecodedLogs<T>
2027
}

src/encoders.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { biguint, BigUint, bytes, internal, TransactionType, uint64, Uint64 } from '@algorandfoundation/algorand-typescript'
2-
import { OnCompleteAction } from '@algorandfoundation/algorand-typescript/arc4'
2+
import { ARC4Encoded, OnCompleteAction } from '@algorandfoundation/algorand-typescript/arc4'
33
import { AccountCls } from './impl/account'
44
import { ApplicationCls } from './impl/application'
55
import { AssetCls } from './impl/asset'
6-
import { arc4Encoders, getArc4Encoder } from './impl/encoded-types'
6+
import { BytesBackedCls, Uint64BackedCls } from './impl/base'
7+
import { arc4Encoders, encodeArc4Impl, getArc4Encoder } from './impl/encoded-types'
78
import { DeliberateAny } from './typescript-helpers'
8-
import { asBytes, asUint8Array } from './util'
9+
import { asBytes, asMaybeBigUintCls, asMaybeBytesCls, asMaybeUint64Cls, asUint64Cls, asUint8Array, nameOfType } from './util'
910

1011
export type TypeInfo = {
1112
name: string
@@ -59,3 +60,31 @@ export const encoders: Record<string, fromBytes<DeliberateAny>> = {
5960
export const getEncoder = <T>(typeInfo: TypeInfo): fromBytes<T> => {
6061
return getArc4Encoder<T>(typeInfo, encoders)
6162
}
63+
64+
export const toBytes = (val: unknown): bytes => {
65+
const uint64Val = asMaybeUint64Cls(val)
66+
if (uint64Val !== undefined) {
67+
return uint64Val.toBytes().asAlgoTs()
68+
}
69+
const bytesVal = asMaybeBytesCls(val)
70+
if (bytesVal !== undefined) {
71+
return bytesVal.asAlgoTs()
72+
}
73+
const bigUintVal = asMaybeBigUintCls(val)
74+
if (bigUintVal !== undefined) {
75+
return bigUintVal.toBytes().asAlgoTs()
76+
}
77+
if (val instanceof BytesBackedCls) {
78+
return val.bytes
79+
}
80+
if (val instanceof Uint64BackedCls) {
81+
return asUint64Cls(val.uint64).toBytes().asAlgoTs()
82+
}
83+
if (val instanceof ARC4Encoded) {
84+
return val.bytes
85+
}
86+
if (Array.isArray(val) || typeof val === 'object') {
87+
return encodeArc4Impl('', val)
88+
}
89+
internal.errors.internalError(`Invalid type for bytes: ${nameOfType(val)}`)
90+
}

src/impl/app-global.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Application, Bytes, bytes, internal, Uint64, uint64 } from '@algorandfoundation/algorand-typescript'
22
import { lazyContext } from '../context-helpers/internal-context'
3-
import { asBytes, toBytes } from '../util'
3+
import { toBytes } from '../encoders'
4+
import { asBytes } from '../util'
45
import { getApp } from './app-params'
56

67
export const AppGlobal: internal.opTypes.AppGlobalType = {

src/impl/app-local.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Account, Application, Bytes, bytes, internal, Uint64, uint64 } from '@algorandfoundation/algorand-typescript'
22
import { lazyContext } from '../context-helpers/internal-context'
3-
import { asBytes, toBytes } from '../util'
3+
import { toBytes } from '../encoders'
4+
import { asBytes } from '../util'
45
import { getAccount } from './acct-params'
56
import { getApp } from './app-params'
67

src/impl/box.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { bytes, internal, uint64 } from '@algorandfoundation/algorand-typescript'
22
import { MAX_BOX_SIZE } from '../constants'
33
import { lazyContext } from '../context-helpers/internal-context'
4-
import { asBytes, asBytesCls, asNumber, asUint8Array, conactUint8Arrays, toBytes } from '../util'
4+
import { toBytes } from '../encoders'
5+
import { asBytes, asBytesCls, asNumber, asUint8Array, conactUint8Arrays } from '../util'
56

67
export const Box: internal.opTypes.BoxType = {
78
create(a: internal.primitives.StubBytesCompat, b: internal.primitives.StubUint64Compat): boolean {

0 commit comments

Comments
 (0)