Skip to content

Commit 92ff824

Browse files
committed
feat: add tests for auction example contract
1 parent 965d07c commit 92ff824

File tree

5 files changed

+191
-20
lines changed

5 files changed

+191
-20
lines changed

examples/auction/contract.spec.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { TransactionType } from '@algorandfoundation/algorand-typescript'
2+
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
3+
import { afterEach, describe, expect, it } from 'vitest'
4+
import { Auction } from './contract.algo'
5+
6+
describe('Auction', () => {
7+
const ctx = new TestExecutionContext()
8+
afterEach(() => {
9+
ctx.reset()
10+
})
11+
12+
it('should be able to opt into an asset', () => {
13+
// Arrange
14+
const asset = ctx.any.asset()
15+
const contract = ctx.contract.create(Auction)
16+
contract.createApplication()
17+
18+
// Act
19+
contract.optIntoAsset(asset)
20+
21+
// Assert
22+
expect(contract.asa.value.id).toEqual(asset.id)
23+
const innerTxn = ctx.txn.lastGroup.lastItxnGroup().getAssetTransferInnerTxn()
24+
25+
expect(innerTxn.assetReceiver, 'Asset receiver does not match').toEqual(ctx.ledger.getApplicationForContract(contract).address)
26+
27+
expect(innerTxn.xferAsset, 'Transferred asset does not match').toEqual(asset)
28+
})
29+
30+
it('should be able to start an auction', () => {
31+
// Arrange
32+
const contract = ctx.contract.create(Auction)
33+
contract.createApplication()
34+
35+
const app = ctx.ledger.getApplicationForContract(contract)
36+
37+
const latestTimestamp = ctx.any.uint64(1, 1000)
38+
const startingPrice = ctx.any.uint64()
39+
const auctionDuration = ctx.any.uint64(100, 1000)
40+
const axferTxn = ctx.any.txn.assetTransfer({
41+
assetReceiver: app.address,
42+
assetAmount: startingPrice,
43+
})
44+
contract.asaAmt.value = startingPrice
45+
ctx.ledger.patchGlobalData({
46+
latestTimestamp: latestTimestamp,
47+
})
48+
49+
// Act
50+
contract.startAuction(startingPrice, auctionDuration, axferTxn)
51+
52+
// Assert
53+
expect(contract.auctionEnd.value).toEqual(latestTimestamp + auctionDuration)
54+
expect(contract.previousBid.value).toEqual(startingPrice)
55+
expect(contract.asaAmt.value).toEqual(startingPrice)
56+
})
57+
58+
it('should be able to bid', () => {
59+
// Arrange
60+
const account = ctx.defaultSender
61+
const auctionEnd = ctx.any.uint64(Date.now() + 10_000)
62+
const previousBid = ctx.any.uint64(1, 100)
63+
const payAmount = ctx.any.uint64()
64+
65+
const contract = ctx.contract.create(Auction)
66+
contract.createApplication()
67+
contract.auctionEnd.value = auctionEnd
68+
contract.previousBid.value = previousBid
69+
const pay = ctx.any.txn.payment({ sender: account, amount: payAmount })
70+
71+
// Act
72+
contract.bid(pay)
73+
74+
// Assert
75+
expect(contract.previousBid.value).toEqual(payAmount)
76+
expect(contract.previousBidder.value).toEqual(account)
77+
expect(contract.claimableAmount(account).value).toEqual(payAmount)
78+
})
79+
80+
it('should be able to claim bids', () => {
81+
// Arrange
82+
const account = ctx.any.account()
83+
const contract = ctx.contract.create(Auction)
84+
contract.createApplication()
85+
86+
const claimableAmount = ctx.any.uint64()
87+
contract.claimableAmount(account).value = claimableAmount
88+
89+
contract.previousBidder.value = account
90+
const previousBid = ctx.any.uint64(undefined, claimableAmount)
91+
contract.previousBid.value = previousBid
92+
93+
// Act
94+
ctx.txn.createScope([ctx.any.txn.applicationCall({ sender: account })]).execute(() => {
95+
contract.claimBids()
96+
})
97+
98+
// Assert
99+
const expectedPayment = claimableAmount - previousBid
100+
const lastInnerTxn = ctx.txn.lastGroup.lastItxnGroup().getPaymentInnerTxn()
101+
102+
expect(lastInnerTxn.amount).toEqual(expectedPayment)
103+
expect(lastInnerTxn.receiver).toEqual(account)
104+
expect(contract.claimableAmount(account).value).toEqual(claimableAmount - expectedPayment)
105+
})
106+
107+
it('should be able to claim asset', () => {
108+
// Arrange
109+
ctx.ledger.patchGlobalData({ latestTimestamp: ctx.any.uint64() })
110+
const contract = ctx.contract.create(Auction)
111+
contract.createApplication()
112+
113+
contract.auctionEnd.value = ctx.any.uint64(1, 100)
114+
contract.previousBidder.value = ctx.defaultSender
115+
const asaAmount = ctx.any.uint64(1000, 2000)
116+
contract.asaAmt.value = asaAmount
117+
const asset = ctx.any.asset()
118+
119+
// Act
120+
contract.claimAsset(asset)
121+
122+
// Assert
123+
const lastInnerTxn = ctx.txn.lastGroup.lastItxnGroup().getAssetTransferInnerTxn()
124+
expect(lastInnerTxn.xferAsset).toEqual(asset)
125+
expect(lastInnerTxn.assetCloseTo).toEqual(ctx.defaultSender)
126+
expect(lastInnerTxn.assetReceiver).toEqual(ctx.defaultSender)
127+
expect(lastInnerTxn.assetAmount).toEqual(asaAmount)
128+
})
129+
130+
it('should be able to delete application', () => {
131+
// Arrange
132+
const account = ctx.any.account()
133+
134+
// Act
135+
// setting sender will determine creator
136+
let contract
137+
ctx.txn.createScope([ctx.any.txn.applicationCall({ sender: account })]).execute(() => {
138+
contract = ctx.contract.create(Auction)
139+
contract.createApplication()
140+
})
141+
142+
ctx.txn.createScope([ctx.any.txn.applicationCall({ onCompletion: 'DeleteApplication' })]).execute(() => {
143+
contract!.deleteApplication()
144+
})
145+
146+
// Assert
147+
const innerTransactions = ctx.txn.lastGroup.lastItxnGroup().getPaymentInnerTxn()
148+
expect(innerTransactions).toBeTruthy()
149+
expect(innerTransactions.type).toEqual(TransactionType.Payment)
150+
expect(innerTransactions.receiver).toEqual(account)
151+
expect(innerTransactions.closeRemainderTo).toEqual(account)
152+
})
153+
154+
it('should be able to call clear state program', () => {
155+
const contract = ctx.contract.create(Auction)
156+
contract.createApplication()
157+
158+
expect(contract.clearStateProgram()).toBeTruthy()
159+
})
160+
})

src/impl/inner-transactions.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -191,27 +191,21 @@ export function submitGroup<TFields extends itxn.InnerTxnList>(...transactionFie
191191
return transactionFields.map((f: (typeof transactionFields)[number]) => f.submit()) as itxn.TxnFor<TFields>
192192
}
193193
export function payment(fields: itxn.PaymentFields): itxn.PaymentItxnParams {
194-
ensureItxnGroupBegin()
195194
return new ItxnParams<itxn.PaymentFields, itxn.PaymentInnerTxn>(fields, TransactionType.Payment)
196195
}
197196
export function keyRegistration(fields: itxn.KeyRegistrationFields): itxn.KeyRegistrationItxnParams {
198-
ensureItxnGroupBegin()
199197
return new ItxnParams<itxn.KeyRegistrationFields, itxn.KeyRegistrationInnerTxn>(fields, TransactionType.KeyRegistration)
200198
}
201199
export function assetConfig(fields: itxn.AssetConfigFields): itxn.AssetConfigItxnParams {
202-
ensureItxnGroupBegin()
203200
return new ItxnParams<itxn.AssetConfigFields, itxn.AssetConfigInnerTxn>(fields, TransactionType.AssetConfig)
204201
}
205202
export function assetTransfer(fields: itxn.AssetTransferFields): itxn.AssetTransferItxnParams {
206-
ensureItxnGroupBegin()
207203
return new ItxnParams<itxn.AssetTransferFields, itxn.AssetTransferInnerTxn>(fields, TransactionType.AssetTransfer)
208204
}
209205
export function assetFreeze(fields: itxn.AssetFreezeFields): itxn.AssetFreezeItxnParams {
210-
ensureItxnGroupBegin()
211206
return new ItxnParams<itxn.AssetFreezeFields, itxn.AssetFreezeInnerTxn>(fields, TransactionType.AssetFreeze)
212207
}
213208
export function applicationCall(fields: itxn.ApplicationCallFields): itxn.ApplicationCallItxnParams {
214-
ensureItxnGroupBegin()
215209
return new ItxnParams<itxn.ApplicationCallFields, itxn.ApplicationInnerTxn>(fields, TransactionType.ApplicationCall)
216210
}
217211

@@ -221,7 +215,9 @@ export class ItxnParams<TFields extends InnerTxnFields, TTransaction extends Inn
221215
this.#fields = { ...fields, type }
222216
}
223217
submit(): TTransaction {
224-
return createInnerTxn<InnerTxnFields>(this.#fields) as unknown as TTransaction
218+
const innerTxn = createInnerTxn<InnerTxnFields>(this.#fields) as unknown as TTransaction
219+
lazyContext.txn.activeGroup.addInnerTransactionGroup(innerTxn)
220+
return innerTxn
225221
}
226222

227223
set(p: Partial<TFields>) {
@@ -232,9 +228,3 @@ export class ItxnParams<TFields extends InnerTxnFields, TTransaction extends Inn
232228
return new ItxnParams<TFields, TTransaction>(this.#fields, this.#fields.type)
233229
}
234230
}
235-
236-
const ensureItxnGroupBegin = () => {
237-
if (!lazyContext.activeGroup.constructingItxnGroup.length) {
238-
lazyContext.activeGroup.beginInnerTransactionGroup()
239-
}
240-
}

src/runtime-helpers.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ARC4Encoded } from '@algorandfoundation/algorand-typescript/arc4'
33
import { MAX_UINT64 } from './constants'
44
import type { TypeInfo } from './encoders'
55
import { AccountCls } from './impl/account'
6+
import { Uint64BackedCls } from './impl/base'
67
import { DeliberateAny } from './typescript-helpers'
78
import { nameOfType } from './util'
89

@@ -48,6 +49,9 @@ export function binaryOp(left: unknown, right: unknown, op: BinaryOps) {
4849
if (left instanceof AccountCls && right instanceof AccountCls) {
4950
return accountBinaryOp(left, right, op)
5051
}
52+
if (left instanceof Uint64BackedCls && right instanceof Uint64BackedCls) {
53+
return uint64BackedClsBinaryOp(left, right, op)
54+
}
5155
if (left instanceof internal.primitives.BigUintCls || right instanceof internal.primitives.BigUintCls) {
5256
return bigUintBinaryOp(left, right, op)
5357
}
@@ -105,6 +109,15 @@ function accountBinaryOp(left: AccountCls, right: AccountCls, op: BinaryOps): De
105109
internal.errors.internalError(`Unsupported operator ${op}`)
106110
}
107111
}
112+
function uint64BackedClsBinaryOp(left: Uint64BackedCls, right: Uint64BackedCls, op: BinaryOps): DeliberateAny {
113+
switch (op) {
114+
case '===':
115+
case '!==':
116+
return uint64BinaryOp(left.uint64, right.uint64, op)
117+
default:
118+
internal.errors.internalError(`Unsupported operator ${op}`)
119+
}
120+
}
108121
function uint64BinaryOp(left: DeliberateAny, right: DeliberateAny, op: BinaryOps): DeliberateAny {
109122
const lbi = internal.primitives.Uint64Cls.fromCompat(left).valueOf()
110123
const rbi = internal.primitives.Uint64Cls.fromCompat(right).valueOf()

src/subcontexts/transaction-context.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,14 @@ export class TransactionContext {
8888
createScope(group: Array<Transaction | DeferredAppCall<unknown[], unknown>>, activeTransactionIndex?: number): ExecutionScope {
8989
const transactions = group.map((t) => (t instanceof DeferredAppCall ? t.txns : [t])).flat()
9090
const transactionGroup = new TransactionGroup(transactions, activeTransactionIndex)
91-
const previousGroup = this.#activeGroup
91+
9292
this.#activeGroup = transactionGroup
9393

9494
const scope = ScopeGenerator(() => {
9595
if (this.#activeGroup?.transactions?.length) {
9696
this.groups.push(this.#activeGroup)
9797
}
98-
this.#activeGroup = previousGroup
98+
this.#activeGroup = undefined
9999
})
100100
return {
101101
execute: <TReturn>(body: () => TReturn) => {
@@ -221,6 +221,9 @@ export class TransactionGroup {
221221
Object.assign(activeTransaction, filteredFields)
222222
}
223223

224+
addInnerTransactionGroup(...itxns: InnerTxn[]) {
225+
this.itxnGroups.push(new ItxnGroup(itxns))
226+
}
224227
beginInnerTransactionGroup() {
225228
if (this.constructingItxnGroup.length) {
226229
internal.errors.internalError('itxn begin without itxn submit')
@@ -252,6 +255,9 @@ export class TransactionGroup {
252255
this.constructingItxnGroup = []
253256
}
254257

258+
lastItxnGroup() {
259+
return this.getItxnGroup()
260+
}
255261
getItxnGroup(index?: internal.primitives.StubUint64Compat): ItxnGroup {
256262
const i = index !== undefined ? asNumber(index) : undefined
257263

src/value-generators/avm.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,19 @@ type AssetContextData = Partial<AssetData> & { assetId?: internal.primitives.Stu
1818
type ApplicationContextData = Partial<ApplicationData['application']> & { applicationId?: internal.primitives.StubUint64Compat }
1919

2020
export class AvmValueGenerator {
21-
uint64(minValue: number | bigint = 0n, maxValue: number | bigint = MAX_UINT64): uint64 {
22-
if (maxValue > MAX_UINT64) {
21+
uint64(minValue: internal.primitives.StubUint64Compat = 0n, maxValue: internal.primitives.StubUint64Compat = MAX_UINT64): uint64 {
22+
const min = asBigInt(minValue)
23+
const max = asBigInt(maxValue)
24+
if (max > MAX_UINT64) {
2325
internal.errors.internalError('maxValue must be less than or equal to MAX_UINT64')
2426
}
25-
if (minValue > maxValue) {
27+
if (min > max) {
2628
internal.errors.internalError('minValue must be less than or equal to maxValue')
2729
}
28-
if (minValue < 0n || maxValue < 0n) {
30+
if (min < 0n || max < 0n) {
2931
internal.errors.internalError('minValue and maxValue must be greater than or equal to 0')
3032
}
31-
return Uint64(getRandomBigInt(minValue, maxValue))
33+
return Uint64(getRandomBigInt(min, max))
3234
}
3335

3436
bytes(length = MAX_BYTES_SIZE): bytes {

0 commit comments

Comments
 (0)