Skip to content

Commit 4f46ca5

Browse files
feat: deterministic resource population (#443)
* feat: deterministic resource population * test: add test to ensure resource population is deterministic * docs: generate latest docs * test: fix comparison string * test: fix asset comparison string Co-authored-by: Yaroslav Nekryach <[email protected]> --------- Co-authored-by: Yaroslav Nekryach <[email protected]>
1 parent 4731264 commit 4f46ca5

File tree

8 files changed

+200
-16
lines changed

8 files changed

+200
-16
lines changed

docs/code/modules/index.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ the estimated rate.
575575

576576
#### Defined in
577577

578-
[src/transaction/transaction.ts:1078](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1078)
578+
[src/transaction/transaction.ts:1104](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1104)
579579

580580
___
581581

@@ -639,7 +639,7 @@ Allows for control of fees on a `Transaction` or `SuggestedParams` object
639639

640640
#### Defined in
641641

642-
[src/transaction/transaction.ts:1105](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1105)
642+
[src/transaction/transaction.ts:1131](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1131)
643643

644644
___
645645

@@ -967,7 +967,7 @@ Converts `bigint`'s for Uint's < 64 to `number` for easier use.
967967

968968
#### Defined in
969969

970-
[src/transaction/transaction.ts:944](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L944)
970+
[src/transaction/transaction.ts:970](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L970)
971971

972972
___
973973

@@ -1943,7 +1943,7 @@ Returns the array of transactions currently present in the given `AtomicTransact
19431943

19441944
#### Defined in
19451945

1946-
[src/transaction/transaction.ts:1154](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1154)
1946+
[src/transaction/transaction.ts:1180](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1180)
19471947

19481948
___
19491949

@@ -2330,7 +2330,7 @@ Returns suggested transaction parameters from algod unless some are already prov
23302330

23312331
#### Defined in
23322332

2333-
[src/transaction/transaction.ts:1132](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1132)
2333+
[src/transaction/transaction.ts:1158](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1158)
23342334

23352335
___
23362336

@@ -2762,7 +2762,7 @@ A new ATC with the resources populated into the transactions
27622762

27632763
#### Defined in
27642764

2765-
[src/transaction/transaction.ts:388](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L388)
2765+
[src/transaction/transaction.ts:414](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L414)
27662766

27672767
___
27682768

@@ -2791,7 +2791,7 @@ A new ATC with the changes applied
27912791

27922792
#### Defined in
27932793

2794-
[src/transaction/transaction.ts:407](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L407)
2794+
[src/transaction/transaction.ts:433](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L433)
27952795

27962796
___
27972797

@@ -2941,7 +2941,7 @@ An object with transaction IDs, transactions, group transaction ID (`groupTransa
29412941

29422942
#### Defined in
29432943

2944-
[src/transaction/transaction.ts:804](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L804)
2944+
[src/transaction/transaction.ts:830](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L830)
29452945

29462946
___
29472947

@@ -2970,7 +2970,7 @@ Signs and sends a group of [up to 16](https://dev.algorand.co/concepts/transacti
29702970

29712971
#### Defined in
29722972

2973-
[src/transaction/transaction.ts:977](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L977)
2973+
[src/transaction/transaction.ts:1003](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1003)
29742974

29752975
___
29762976

@@ -3243,4 +3243,4 @@ Throws an error if the transaction is not confirmed or rejected in the next `tim
32433243

32443244
#### Defined in
32453245

3246-
[src/transaction/transaction.ts:1021](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1021)
3246+
[src/transaction/transaction.ts:1047](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction/transaction.ts#L1047)

src/transaction/transaction.spec.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { AlgoAmount } from '../types/amount'
1313
import { AppClient } from '../types/app-client'
1414
import { PaymentParams, TransactionComposer } from '../types/composer'
1515
import { Arc2TransactionNote } from '../types/transaction'
16-
import { getABIReturnValue, waitForConfirmation } from './transaction'
16+
import { getABIReturnValue, populateAppCallResources, waitForConfirmation } from './transaction'
1717

1818
describe('transaction', () => {
1919
const localnet = algorandFixture()
@@ -1138,6 +1138,88 @@ describe('Resource population: meta', () => {
11381138
expect(boxRef).toBeDefined()
11391139
expect(boxRef?.appIndex).toBe(0n)
11401140
})
1141+
1142+
test('order is deterministic', async () => {
1143+
const { algorand, context } = fixture
1144+
1145+
const testAccount = context.testAccount
1146+
1147+
const v9AppFactory = algorand.client.getAppFactory({
1148+
appSpec: JSON.stringify(v9ARC32),
1149+
defaultSender: testAccount,
1150+
})
1151+
1152+
const v9AppClient = (await v9AppFactory.send.create({ method: 'createApplication' })).appClient
1153+
1154+
await v9AppClient.fundAppAccount({ amount: (118_000).microAlgo() })
1155+
1156+
const accounts = []
1157+
for (let i = 0; i < 4; i++) {
1158+
accounts.push(algorand.account.random().addr)
1159+
}
1160+
1161+
const apps = []
1162+
for (let i = 0; i < 4; i++) {
1163+
const app = await v9AppFactory.send.create({ method: 'createApplication' })
1164+
apps.push(app.appClient.appId)
1165+
}
1166+
1167+
const assets = []
1168+
for (let i = 0; i < 4; i++) {
1169+
const res = await algorand.send.assetCreate({ sender: testAccount, total: 1n })
1170+
assets.push(res.assetId)
1171+
}
1172+
1173+
const appCall = await v9AppClient.params.call({
1174+
method: 'manyResources',
1175+
args: [accounts, assets, apps, [1, 2, 3, 4]],
1176+
})
1177+
1178+
const composer = algorand.newGroup()
1179+
1180+
composer.addAppCallMethodCall(appCall)
1181+
1182+
for (let i = 0; i < 10; i++) {
1183+
composer.addAppCallMethodCall(await v9AppClient.params.call({ method: 'dummy', note: `${i}` }))
1184+
}
1185+
1186+
const atc = (await composer.build()).atc
1187+
const getResources = async () => {
1188+
const populatedAtc = await populateAppCallResources(atc, algorand.client.algod)
1189+
1190+
const resources = []
1191+
for (const txnWithSigner of populatedAtc.buildGroup()) {
1192+
const txn = txnWithSigner.txn
1193+
1194+
for (const acct of txn.applicationCall?.accounts ?? []) {
1195+
resources.push(acct.toString())
1196+
}
1197+
1198+
for (const asset of txn.applicationCall?.foreignAssets ?? []) {
1199+
resources.push(asset.toString())
1200+
}
1201+
1202+
for (const app of txn.applicationCall?.foreignApps ?? []) {
1203+
resources.push(app.toString())
1204+
}
1205+
1206+
for (const box of txn.applicationCall?.boxes ?? []) {
1207+
resources.push(`${box.appIndex}-${box.name.toString()}`)
1208+
}
1209+
}
1210+
1211+
return resources
1212+
}
1213+
1214+
const allResources = []
1215+
for (let i = 0; i < 100; i++) {
1216+
allResources.push(await getResources())
1217+
}
1218+
1219+
for (let i = 1; i < allResources.length; i++) {
1220+
expect(allResources[i]).toEqual(allResources[0])
1221+
}
1222+
})
11411223
})
11421224

11431225
describe('abi return', () => {

src/transaction/transaction.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,34 @@ async function getGroupExecutionInfo(
329329
throw Error(`Error resolving execution info via simulate in transaction ${groupResponse.failedAt}: ${groupResponse.failureMessage}`)
330330
}
331331

332+
const sortedResources = groupResponse.unnamedResourcesAccessed
333+
334+
// NOTE: We explicitly want to avoid localeCompare as that can lead to different results in different environments
335+
const compare = (a: string | bigint, b: string | bigint) => (a < b ? -1 : a > b ? 1 : 0)
336+
337+
if (sortedResources) {
338+
sortedResources.accounts?.sort((a, b) => compare(a.toString(), b.toString()))
339+
sortedResources.assets?.sort(compare)
340+
sortedResources.apps?.sort(compare)
341+
sortedResources.boxes?.sort((a, b) => {
342+
const aStr = `${a.app}-${a.name}`
343+
const bStr = `${b.app}-${b.name}`
344+
return compare(aStr, bStr)
345+
})
346+
sortedResources.appLocals?.sort((a, b) => {
347+
const aStr = `${a.app}-${a.account}`
348+
const bStr = `${b.app}-${b.account}`
349+
return compare(aStr, bStr)
350+
})
351+
sortedResources.assetHoldings?.sort((a, b) => {
352+
const aStr = `${a.asset}-${a.account}`
353+
const bStr = `${b.asset}-${b.account}`
354+
return compare(aStr, bStr)
355+
})
356+
}
357+
332358
return {
333-
groupUnnamedResourcesAccessed: sendParams.populateAppCallResources ? groupResponse.unnamedResourcesAccessed : undefined,
359+
groupUnnamedResourcesAccessed: sendParams.populateAppCallResources ? sortedResources : undefined,
334360
txns: groupResponse.txnResults.map((txn, i) => {
335361
const originalTxn = atc['transactions'][i].txn as algosdk.Transaction
336362

tests/example-contracts/resource-packer/artifacts/ExternalApp.arc32.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/example-contracts/resource-packer/artifacts/InnerBoxApp.arc32.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
}
4040
},
4141
"source": {
42-
"approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCmludGNibG9jayAxCgovLyBUaGlzIFRFQUwgd2FzIGdlbmVyYXRlZCBieSBURUFMU2NyaXB0IHYwLjEwNi4zCi8vIGh0dHBzOi8vZ2l0aHViLmNvbS9hbGdvcmFuZGZvdW5kYXRpb24vVEVBTFNjcmlwdAoKLy8gVGhpcyBjb250cmFjdCBpcyBjb21wbGlhbnQgd2l0aCBhbmQvb3IgaW1wbGVtZW50cyB0aGUgZm9sbG93aW5nIEFSQ3M6IFsgQVJDNCBdCgovLyBUaGUgZm9sbG93aW5nIHRlbiBsaW5lcyBvZiBURUFMIGhhbmRsZSBpbml0aWFsIHByb2dyYW0gZmxvdwovLyBUaGlzIHBhdHRlcm4gaXMgdXNlZCB0byBtYWtlIGl0IGVhc3kgZm9yIGFueW9uZSB0byBwYXJzZSB0aGUgc3RhcnQgb2YgdGhlIHByb2dyYW0gYW5kIGRldGVybWluZSBpZiBhIHNwZWNpZmljIGFjdGlvbiBpcyBhbGxvd2VkCi8vIEhlcmUsIGFjdGlvbiByZWZlcnMgdG8gdGhlIE9uQ29tcGxldGUgaW4gY29tYmluYXRpb24gd2l0aCB3aGV0aGVyIHRoZSBhcHAgaXMgYmVpbmcgY3JlYXRlZCBvciBjYWxsZWQKLy8gRXZlcnkgcG9zc2libGUgYWN0aW9uIGZvciB0aGlzIGNvbnRyYWN0IGlzIHJlcHJlc2VudGVkIGluIHRoZSBzd2l0Y2ggc3RhdGVtZW50Ci8vIElmIHRoZSBhY3Rpb24gaXMgbm90IGltcGxlbWVudGVkIGluIHRoZSBjb250cmFjdCwgaXRzIHJlc3BlY3RpdmUgYnJhbmNoIHdpbGwgYmUgIipOT1RfSU1QTEVNRU5URUQiIHdoaWNoIGp1c3QgY29udGFpbnMgImVyciIKdHhuIEFwcGxpY2F0aW9uSUQKIQpwdXNoaW50IDYKKgp0eG4gT25Db21wbGV0aW9uCisKc3dpdGNoICpjYWxsX05vT3AgKk5PVF9JTVBMRU1FTlRFRCAqTk9UX0lNUExFTUVOVEVEICpOT1RfSU1QTEVNRU5URUQgKk5PVF9JTVBMRU1FTlRFRCAqTk9UX0lNUExFTUVOVEVEICpjcmVhdGVfTm9PcCAqTk9UX0lNUExFTUVOVEVEICpOT1RfSU1QTEVNRU5URUQgKk5PVF9JTVBMRU1FTlRFRCAqTk9UX0lNUExFTUVOVEVEICpOT1RfSU1QTEVNRU5URUQKCipOT1RfSU1QTEVNRU5URUQ6CgkvLyBUaGUgcmVxdWVzdGVkIGFjdGlvbiBpcyBub3QgaW1wbGVtZW50ZWQgaW4gdGhpcyBjb250cmFjdC4gQXJlIHlvdSB1c2luZyB0aGUgY29ycmVjdCBPbkNvbXBsZXRlPyBEaWQgeW91IHNldCB5b3VyIGFwcCBJRD8KCWVycgoKLy8gY3JlYXRlRW1wdHlCb3goKXZvaWQKKmFiaV9yb3V0ZV9jcmVhdGVFbXB0eUJveDoKCS8vIGV4ZWN1dGUgY3JlYXRlRW1wdHlCb3goKXZvaWQKCWNhbGxzdWIgY3JlYXRlRW1wdHlCb3gKCWludGMgMCAvLyAxCglyZXR1cm4KCi8vIGNyZWF0ZUVtcHR5Qm94KCk6IHZvaWQKY3JlYXRlRW1wdHlCb3g6Cglwcm90byAwIDAKCgkvLyB0ZXN0cy9leGFtcGxlLWNvbnRyYWN0cy9yZXNvdXJjZS1wYWNrZXIvcmVzb3VyY2UtcGFja2VyLmFsZ28udHM6NjEKCS8vIHRoaXMuZW1wdHlCb3guY3JlYXRlKCkKCXB1c2hieXRlcyAweDY1NmQ3MDc0Nzk0MjZmNzggLy8gImVtcHR5Qm94IgoJcHVzaGludCAwCglib3hfY3JlYXRlCglwb3AKCXJldHN1YgoKKmFiaV9yb3V0ZV9jcmVhdGVBcHBsaWNhdGlvbjoKCWludGMgMCAvLyAxCglyZXR1cm4KCipjcmVhdGVfTm9PcDoKCXB1c2hieXRlcyAweGI4NDQ3YjM2IC8vIG1ldGhvZCAiY3JlYXRlQXBwbGljYXRpb24oKXZvaWQiCgl0eG5hIEFwcGxpY2F0aW9uQXJncyAwCgltYXRjaCAqYWJpX3JvdXRlX2NyZWF0ZUFwcGxpY2F0aW9uCgoJLy8gdGhpcyBjb250cmFjdCBkb2VzIG5vdCBpbXBsZW1lbnQgdGhlIGdpdmVuIEFCSSBtZXRob2QgZm9yIGNyZWF0ZSBOb09wCgllcnIKCipjYWxsX05vT3A6CglwdXNoYnl0ZXMgMHhhNjhiZDI5NyAvLyBtZXRob2QgImNyZWF0ZUVtcHR5Qm94KCl2b2lkIgoJdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMAoJbWF0Y2ggKmFiaV9yb3V0ZV9jcmVhdGVFbXB0eUJveAoKCS8vIHRoaXMgY29udHJhY3QgZG9lcyBub3QgaW1wbGVtZW50IHRoZSBnaXZlbiBBQkkgbWV0aG9kIGZvciBjYWxsIE5vT3AKCWVycg==",
42+
"approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCmludGNibG9jayAxCgovLyBUaGlzIFRFQUwgd2FzIGdlbmVyYXRlZCBieSBURUFMU2NyaXB0IHYwLjEwNi4zCi8vIGh0dHBzOi8vZ2l0aHViLmNvbS9hbGdvcmFuZGZvdW5kYXRpb24vVEVBTFNjcmlwdAoKLy8gVGhpcyBjb250cmFjdCBpcyBjb21wbGlhbnQgd2l0aCBhbmQvb3IgaW1wbGVtZW50cyB0aGUgZm9sbG93aW5nIEFSQ3M6IFsgQVJDNCBdCgovLyBUaGUgZm9sbG93aW5nIHRlbiBsaW5lcyBvZiBURUFMIGhhbmRsZSBpbml0aWFsIHByb2dyYW0gZmxvdwovLyBUaGlzIHBhdHRlcm4gaXMgdXNlZCB0byBtYWtlIGl0IGVhc3kgZm9yIGFueW9uZSB0byBwYXJzZSB0aGUgc3RhcnQgb2YgdGhlIHByb2dyYW0gYW5kIGRldGVybWluZSBpZiBhIHNwZWNpZmljIGFjdGlvbiBpcyBhbGxvd2VkCi8vIEhlcmUsIGFjdGlvbiByZWZlcnMgdG8gdGhlIE9uQ29tcGxldGUgaW4gY29tYmluYXRpb24gd2l0aCB3aGV0aGVyIHRoZSBhcHAgaXMgYmVpbmcgY3JlYXRlZCBvciBjYWxsZWQKLy8gRXZlcnkgcG9zc2libGUgYWN0aW9uIGZvciB0aGlzIGNvbnRyYWN0IGlzIHJlcHJlc2VudGVkIGluIHRoZSBzd2l0Y2ggc3RhdGVtZW50Ci8vIElmIHRoZSBhY3Rpb24gaXMgbm90IGltcGxlbWVudGVkIGluIHRoZSBjb250cmFjdCwgaXRzIHJlc3BlY3RpdmUgYnJhbmNoIHdpbGwgYmUgIipOT1RfSU1QTEVNRU5URUQiIHdoaWNoIGp1c3QgY29udGFpbnMgImVyciIKdHhuIEFwcGxpY2F0aW9uSUQKIQpwdXNoaW50IDYKKgp0eG4gT25Db21wbGV0aW9uCisKc3dpdGNoICpjYWxsX05vT3AgKk5PVF9JTVBMRU1FTlRFRCAqTk9UX0lNUExFTUVOVEVEICpOT1RfSU1QTEVNRU5URUQgKk5PVF9JTVBMRU1FTlRFRCAqTk9UX0lNUExFTUVOVEVEICpjcmVhdGVfTm9PcCAqTk9UX0lNUExFTUVOVEVEICpOT1RfSU1QTEVNRU5URUQgKk5PVF9JTVBMRU1FTlRFRCAqTk9UX0lNUExFTUVOVEVEICpOT1RfSU1QTEVNRU5URUQKCipOT1RfSU1QTEVNRU5URUQ6CgkvLyBUaGUgcmVxdWVzdGVkIGFjdGlvbiBpcyBub3QgaW1wbGVtZW50ZWQgaW4gdGhpcyBjb250cmFjdC4gQXJlIHlvdSB1c2luZyB0aGUgY29ycmVjdCBPbkNvbXBsZXRlPyBEaWQgeW91IHNldCB5b3VyIGFwcCBJRD8KCWVycgoKLy8gY3JlYXRlRW1wdHlCb3goKXZvaWQKKmFiaV9yb3V0ZV9jcmVhdGVFbXB0eUJveDoKCS8vIGV4ZWN1dGUgY3JlYXRlRW1wdHlCb3goKXZvaWQKCWNhbGxzdWIgY3JlYXRlRW1wdHlCb3gKCWludGMgMCAvLyAxCglyZXR1cm4KCi8vIGNyZWF0ZUVtcHR5Qm94KCk6IHZvaWQKY3JlYXRlRW1wdHlCb3g6Cglwcm90byAwIDAKCgkvLyByZXNvdXJjZS1wYWNrZXIuYWxnby50czo2MQoJLy8gdGhpcy5lbXB0eUJveC5jcmVhdGUoKQoJcHVzaGJ5dGVzIDB4NjU2ZDcwNzQ3OTQyNmY3OCAvLyAiZW1wdHlCb3giCglwdXNoaW50IDAKCWJveF9jcmVhdGUKCXBvcAoJcmV0c3ViCgoqYWJpX3JvdXRlX2NyZWF0ZUFwcGxpY2F0aW9uOgoJaW50YyAwIC8vIDEKCXJldHVybgoKKmNyZWF0ZV9Ob09wOgoJcHVzaGJ5dGVzIDB4Yjg0NDdiMzYgLy8gbWV0aG9kICJjcmVhdGVBcHBsaWNhdGlvbigpdm9pZCIKCXR4bmEgQXBwbGljYXRpb25BcmdzIDAKCW1hdGNoICphYmlfcm91dGVfY3JlYXRlQXBwbGljYXRpb24KCgkvLyB0aGlzIGNvbnRyYWN0IGRvZXMgbm90IGltcGxlbWVudCB0aGUgZ2l2ZW4gQUJJIG1ldGhvZCBmb3IgY3JlYXRlIE5vT3AKCWVycgoKKmNhbGxfTm9PcDoKCXB1c2hieXRlcyAweGE2OGJkMjk3IC8vIG1ldGhvZCAiY3JlYXRlRW1wdHlCb3goKXZvaWQiCgl0eG5hIEFwcGxpY2F0aW9uQXJncyAwCgltYXRjaCAqYWJpX3JvdXRlX2NyZWF0ZUVtcHR5Qm94CgoJLy8gdGhpcyBjb250cmFjdCBkb2VzIG5vdCBpbXBsZW1lbnQgdGhlIGdpdmVuIEFCSSBtZXRob2QgZm9yIGNhbGwgTm9PcAoJZXJy",
4343
"clear": "I3ByYWdtYSB2ZXJzaW9uIDEw"
4444
},
4545
"contract": {

0 commit comments

Comments
 (0)