From 7cf20f9a627cf70e766073b8dee050f884edb265 Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Mon, 24 Jul 2023 14:53:55 -0400 Subject: [PATCH] storageRefAccount --- src/lib/compiler.ts | 55 ++++++- tests/abi.test.ts | 8 +- tests/contracts/abi.algo.ts | 17 +++ .../ABITestStorageRefAccount.abi.json | 15 ++ .../ABITestStorageRefAccount.approval.teal | 134 ++++++++++++++++++ .../ABITestStorageRefAccount.clear.teal | 3 + .../artifacts/ABITestStorageRefAccount.json | 56 ++++++++ 7 files changed, 280 insertions(+), 8 deletions(-) create mode 100644 tests/contracts/artifacts/ABITestStorageRefAccount.abi.json create mode 100644 tests/contracts/artifacts/ABITestStorageRefAccount.approval.teal create mode 100644 tests/contracts/artifacts/ABITestStorageRefAccount.clear.teal create mode 100644 tests/contracts/artifacts/ABITestStorageRefAccount.json diff --git a/src/lib/compiler.ts b/src/lib/compiler.ts index b52c559d9..537a78c5f 100644 --- a/src/lib/compiler.ts +++ b/src/lib/compiler.ts @@ -311,6 +311,7 @@ export default class Compiler { accessors?: (ts.Expression | string)[] storageExpression?: ts.CallExpression storageKeyFrame?: string + storageAccountFrame?: string } } = {}; @@ -511,7 +512,7 @@ export default class Compiler { }, }, local: { - get: (node: ts.CallExpression, storageKeyFrame?: string) => { + get: (node: ts.CallExpression, storageKeyFrame?: string, storageAccountFrame?: string) => { if (!ts.isPropertyAccessExpression(node.expression)) throw new Error(); if (!ts.isPropertyAccessExpression(node.expression.expression)) throw new Error(); const name = node.expression.expression.name.getText(); @@ -520,7 +521,11 @@ export default class Compiler { valueType, keyType, key, prefix, } = this.storageProps[name]; - this.processNode(node.arguments[0]); + if (storageAccountFrame) { + this.pushVoid(node.expression, `frame_dig ${this.frame[storageAccountFrame].index} // ${storageAccountFrame}`); + } else { + this.processNode(node.arguments[0]); + } if (key) { this.pushVoid(node.expression, `byte "${key}"`); @@ -541,7 +546,7 @@ export default class Compiler { this.push(node.expression, 'app_local_get', valueType); if (valueType !== StackType.bytes) this.checkDecoding(node, valueType); }, - set: (node: ts.CallExpression, storageKeyFrame?: string) => { + set: (node: ts.CallExpression, storageKeyFrame?: string, storageAccountFrame?: string) => { if (!ts.isPropertyAccessExpression(node.expression)) throw new Error(); if (!ts.isPropertyAccessExpression(node.expression.expression)) throw new Error(); const name = node.expression.expression.name.getText(); @@ -550,7 +555,11 @@ export default class Compiler { valueType, keyType, key, prefix, } = this.storageProps[name]; - this.processNode(node.arguments[0]); + if (storageAccountFrame) { + this.pushVoid(node.expression, `frame_dig ${this.frame[storageAccountFrame].index} // ${storageAccountFrame}`); + } else { + this.processNode(node.arguments[0]); + } if (key) { this.pushVoid(node.expression, `byte "${key}"`); @@ -573,7 +582,12 @@ export default class Compiler { if (valueType !== StackType.bytes) { this.checkEncoding(node.arguments[key ? 1 : 2], this.lastType); } - } else this.pushVoid(node.expression, 'uncover 2'); // Used when updating storage array + } else { + this.pushVoid(node.expression, 'uncover 2'); // Used when updating storage array + if (valueType !== StackType.bytes) { + this.checkEncoding(node, valueType); + } + } this.push(node.expression, 'app_local_put', valueType); }, @@ -1859,7 +1873,14 @@ export default class Compiler { node: ts.Node, inputName: string, load: boolean, - ): {accessors: (ts.Expression | string)[], name: string, type: 'frame' | 'storage', storageExpression?: ts.CallExpression, storageKeyFrame?: string} { + ): { + accessors: (ts.Expression | string)[], + name: string, + type: 'frame' | 'storage', + storageExpression?: ts.CallExpression, + storageKeyFrame?: string + storageAccountFrame?: string + } { let name = inputName; let currentFrame = this.frame[inputName]; let type: 'frame' | 'storage' = 'frame'; @@ -1889,6 +1910,7 @@ export default class Compiler { accessors: accessors.reverse().flat(), storageExpression, storageKeyFrame: currentFrame.storageKeyFrame, + storageAccountFrame: currentFrame.storageAccountFrame, }; } @@ -1896,6 +1918,7 @@ export default class Compiler { this.storageFunctions[this.storageProps[name].type].get( currentFrame.storageExpression, currentFrame.storageKeyFrame, + currentFrame.storageAccountFrame, ); } else { this.push( @@ -1928,6 +1951,7 @@ export default class Compiler { this.storageFunctions[type].set( processedFrame.storageExpression, processedFrame.storageKeyFrame, + processedFrame.storageAccountFrame, ); } } @@ -2738,7 +2762,24 @@ export default class Compiler { this.frame[name].storageKeyFrame = keyFrameName; } - // TODO: Save local account + if (storageProp.type === 'local') { + const accountNode = storageExpression.arguments[0]; + const accountFrameName = `storage account//${name}`; + + this.addSourceComment(node, true); + this.processNode(accountNode); + + this.pushVoid(accountNode, `frame_bury ${this.frameIndex} // ${accountFrameName}`); + + this.frame[accountFrameName] = { + index: this.frameIndex, + type: StackType.uint64, + }; + + this.frameIndex -= 1; + + this.frame[name].storageAccountFrame = accountFrameName; + } } private processVariableDeclarator(node: ts.VariableDeclaration) { diff --git a/tests/abi.test.ts b/tests/abi.test.ts index aed4267cb..03185040d 100644 --- a/tests/abi.test.ts +++ b/tests/abi.test.ts @@ -144,7 +144,7 @@ async function runMethod( }; try { - if (name.includes('Storage')) { + if (name.includes('Storage') || name.includes('RefAccount')) { await appClient.fundAppAccount({ amount: algokit.microAlgos(127400), sendParams: { suppressLog: true }, @@ -685,4 +685,10 @@ describe('ABI', function () { expect(await runMethod(appClient, 'storageRefKey')).toEqual(4n); }); + + test.concurrent('storageRefAccount', async () => { + const { appClient } = await compileAndCreate('storageRefAccount'); + + expect(await runMethod(appClient, 'storageRefAccount')).toEqual(4n); + }); }); diff --git a/tests/contracts/abi.algo.ts b/tests/contracts/abi.algo.ts index 12724ab1c..816463469 100644 --- a/tests/contracts/abi.algo.ts +++ b/tests/contracts/abi.algo.ts @@ -957,3 +957,20 @@ class ABITestStorageRefKey extends Contract { return this.gMap.get(0)[1]; } } + +class ABITestStorageRefAccount extends Contract { + lMap = new LocalStateMap(); + + @handle.optIn + storageRefAccount(): uint64 { + let addr = this.txn.sender; + this.lMap.set(addr, 0, [1, 2, 3]); + const r = this.lMap.get(addr, 0); + + addr = globals.zeroAddress; + + r[1] = 4; + + return this.lMap.get(this.txn.sender, 0)[1]; + } +} diff --git a/tests/contracts/artifacts/ABITestStorageRefAccount.abi.json b/tests/contracts/artifacts/ABITestStorageRefAccount.abi.json new file mode 100644 index 000000000..7916ed9e2 --- /dev/null +++ b/tests/contracts/artifacts/ABITestStorageRefAccount.abi.json @@ -0,0 +1,15 @@ +{ + "name": "ABITestStorageRefAccount", + "desc": "", + "methods": [ + { + "name": "storageRefAccount", + "args": [], + "desc": "", + "returns": { + "type": "uint64", + "desc": "" + } + } + ] +} \ No newline at end of file diff --git a/tests/contracts/artifacts/ABITestStorageRefAccount.approval.teal b/tests/contracts/artifacts/ABITestStorageRefAccount.approval.teal new file mode 100644 index 000000000..8f341e584 --- /dev/null +++ b/tests/contracts/artifacts/ABITestStorageRefAccount.approval.teal @@ -0,0 +1,134 @@ +#pragma version 8 + b main + +abi_route_storageRefAccount: + txn OnCompletion + int OptIn + == + txn ApplicationID + int 0 + != + && + assert + byte 0x; dup + callsub storageRefAccount + int 1 + return + +storageRefAccount: + proto 2 0 + + // tests/contracts/abi.algo.ts:966 + // addr = this.txn.sender + txn Sender + frame_bury -1 // addr: address + + // tests/contracts/abi.algo.ts:967 + // this.lMap.set(addr, 0, [1, 2, 3]) + frame_dig -1 // addr: address + int 0 + itob + int 1 + itob + int 2 + itob + concat + int 3 + itob + concat + dup + len + int 8 + / + itob + extract 6 2 + swap + concat + app_local_put + + // tests/contracts/abi.algo.ts:968 + // r = this.lMap.get(addr, 0) + frame_dig -1 // addr: address + frame_bury -2 // storage account//r + + // tests/contracts/abi.algo.ts:970 + // addr = globals.zeroAddress + global ZeroAddress + frame_bury -1 // addr: address + + // tests/contracts/abi.algo.ts:972 + // r[1] = 4 + frame_dig -2 // storage account//r + int 0 + itob + app_local_get + extract 2 0 + store 0 // full array + int 0 // initial offset + int 1 + int 8 + * // acc * typeLength + + + load 0 // full array + swap + int 4 + itob + replace3 + frame_dig -2 // storage account//r + int 0 + itob + uncover 2 + dup + len + int 8 + / + itob + extract 6 2 + swap + concat + app_local_put + + // tests/contracts/abi.algo.ts:974 + // return this.lMap.get(this.txn.sender, 0)[1]; + txn Sender + int 0 + itob + app_local_get + extract 2 0 + store 0 // full array + int 0 // initial offset + int 1 + int 8 + * // acc * typeLength + + + load 0 // full array + swap + int 8 + extract3 + btoi + itob + byte 0x151f7c75 + swap + concat + log + retsub + +main: + txn NumAppArgs + bnz route_abi + + // default createApplication + txn ApplicationID + int 0 + == + txn OnCompletion + int NoOp + == + && + return + +route_abi: + method "storageRefAccount()uint64" + txna ApplicationArgs 0 + match abi_route_storageRefAccount + err \ No newline at end of file diff --git a/tests/contracts/artifacts/ABITestStorageRefAccount.clear.teal b/tests/contracts/artifacts/ABITestStorageRefAccount.clear.teal new file mode 100644 index 000000000..31588a8ec --- /dev/null +++ b/tests/contracts/artifacts/ABITestStorageRefAccount.clear.teal @@ -0,0 +1,3 @@ +#pragma version 8 +int 1 +return \ No newline at end of file diff --git a/tests/contracts/artifacts/ABITestStorageRefAccount.json b/tests/contracts/artifacts/ABITestStorageRefAccount.json new file mode 100644 index 000000000..78a05f0a4 --- /dev/null +++ b/tests/contracts/artifacts/ABITestStorageRefAccount.json @@ -0,0 +1,56 @@ +{ + "hints": { + "storageRefAccount()uint64": { + "call_config": { + "optIn": "CALL" + } + } + }, + "bare_call_config": { + "no_op": "CREATE" + }, + "schema": { + "local": { + "declared": { + "lMap": { + "type": "bytes", + "key": "lMap" + } + }, + "reserved": {} + }, + "global": { + "declared": {}, + "reserved": {} + } + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 1, + "num_uints": 0 + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKCWIgbWFpbgoKYWJpX3JvdXRlX3N0b3JhZ2VSZWZBY2NvdW50OgoJdHhuIE9uQ29tcGxldGlvbgoJaW50IE9wdEluCgk9PQoJdHhuIEFwcGxpY2F0aW9uSUQKCWludCAwCgkhPQoJJiYKCWFzc2VydAoJYnl0ZSAweDsgZHVwCgljYWxsc3ViIHN0b3JhZ2VSZWZBY2NvdW50CglpbnQgMQoJcmV0dXJuCgpzdG9yYWdlUmVmQWNjb3VudDoKCXByb3RvIDIgMAoKCS8vIHRlc3RzL2NvbnRyYWN0cy9hYmkuYWxnby50czo5NjYKCS8vIGFkZHIgPSB0aGlzLnR4bi5zZW5kZXIKCXR4biBTZW5kZXIKCWZyYW1lX2J1cnkgLTEgLy8gYWRkcjogYWRkcmVzcwoKCS8vIHRlc3RzL2NvbnRyYWN0cy9hYmkuYWxnby50czo5NjcKCS8vIHRoaXMubE1hcC5zZXQoYWRkciwgMCwgWzEsIDIsIDNdKQoJZnJhbWVfZGlnIC0xIC8vIGFkZHI6IGFkZHJlc3MKCWludCAwCglpdG9iCglpbnQgMQoJaXRvYgoJaW50IDIKCWl0b2IKCWNvbmNhdAoJaW50IDMKCWl0b2IKCWNvbmNhdAoJZHVwCglsZW4KCWludCA4CgkvCglpdG9iCglleHRyYWN0IDYgMgoJc3dhcAoJY29uY2F0CglhcHBfbG9jYWxfcHV0CgoJLy8gdGVzdHMvY29udHJhY3RzL2FiaS5hbGdvLnRzOjk2OAoJLy8gciA9IHRoaXMubE1hcC5nZXQoYWRkciwgMCkKCWZyYW1lX2RpZyAtMSAvLyBhZGRyOiBhZGRyZXNzCglmcmFtZV9idXJ5IC0yIC8vIHN0b3JhZ2UgYWNjb3VudC8vcgoKCS8vIHRlc3RzL2NvbnRyYWN0cy9hYmkuYWxnby50czo5NzAKCS8vIGFkZHIgPSBnbG9iYWxzLnplcm9BZGRyZXNzCglnbG9iYWwgWmVyb0FkZHJlc3MKCWZyYW1lX2J1cnkgLTEgLy8gYWRkcjogYWRkcmVzcwoKCS8vIHRlc3RzL2NvbnRyYWN0cy9hYmkuYWxnby50czo5NzIKCS8vIHJbMV0gPSA0CglmcmFtZV9kaWcgLTIgLy8gc3RvcmFnZSBhY2NvdW50Ly9yCglpbnQgMAoJaXRvYgoJYXBwX2xvY2FsX2dldAoJZXh0cmFjdCAyIDAKCXN0b3JlIDAgLy8gZnVsbCBhcnJheQoJaW50IDAgLy8gaW5pdGlhbCBvZmZzZXQKCWludCAxCglpbnQgOAoJKiAvLyBhY2MgKiB0eXBlTGVuZ3RoCgkrCglsb2FkIDAgLy8gZnVsbCBhcnJheQoJc3dhcAoJaW50IDQKCWl0b2IKCXJlcGxhY2UzCglmcmFtZV9kaWcgLTIgLy8gc3RvcmFnZSBhY2NvdW50Ly9yCglpbnQgMAoJaXRvYgoJdW5jb3ZlciAyCglkdXAKCWxlbgoJaW50IDgKCS8KCWl0b2IKCWV4dHJhY3QgNiAyCglzd2FwCgljb25jYXQKCWFwcF9sb2NhbF9wdXQKCgkvLyB0ZXN0cy9jb250cmFjdHMvYWJpLmFsZ28udHM6OTc0CgkvLyByZXR1cm4gdGhpcy5sTWFwLmdldCh0aGlzLnR4bi5zZW5kZXIsIDApWzFdOwoJdHhuIFNlbmRlcgoJaW50IDAKCWl0b2IKCWFwcF9sb2NhbF9nZXQKCWV4dHJhY3QgMiAwCglzdG9yZSAwIC8vIGZ1bGwgYXJyYXkKCWludCAwIC8vIGluaXRpYWwgb2Zmc2V0CglpbnQgMQoJaW50IDgKCSogLy8gYWNjICogdHlwZUxlbmd0aAoJKwoJbG9hZCAwIC8vIGZ1bGwgYXJyYXkKCXN3YXAKCWludCA4CglleHRyYWN0MwoJYnRvaQoJaXRvYgoJYnl0ZSAweDE1MWY3Yzc1Cglzd2FwCgljb25jYXQKCWxvZwoJcmV0c3ViCgptYWluOgoJdHhuIE51bUFwcEFyZ3MKCWJueiByb3V0ZV9hYmkKCgkvLyBkZWZhdWx0IGNyZWF0ZUFwcGxpY2F0aW9uCgl0eG4gQXBwbGljYXRpb25JRAoJaW50IDAKCT09Cgl0eG4gT25Db21wbGV0aW9uCglpbnQgTm9PcAoJPT0KCSYmCglyZXR1cm4KCnJvdXRlX2FiaToKCW1ldGhvZCAic3RvcmFnZVJlZkFjY291bnQoKXVpbnQ2NCIKCXR4bmEgQXBwbGljYXRpb25BcmdzIDAKCW1hdGNoIGFiaV9yb3V0ZV9zdG9yYWdlUmVmQWNjb3VudAoJZXJy", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50IDEKcmV0dXJu" + }, + "contract": { + "name": "ABITestStorageRefAccount", + "desc": "", + "methods": [ + { + "name": "storageRefAccount", + "args": [], + "desc": "", + "returns": { + "type": "uint64", + "desc": "" + } + } + ] + } +} \ No newline at end of file