diff --git a/CHANGELOG.md b/CHANGELOG.md index 4caa4d395..40a0ea428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,8 @@ Added: - Add new opcode `bsqrt` and `divw`([##605](https://github.com/scale-it/algo-builder/pull/605)). - Add new opcode `gloadss`([#606](https://github.com/scale-it/algo-builder/pull/606)). - Add new opcode `acct_params_get`([#618](https://github.com/scale-it/algo-builder/pull/618)). - - Add new opcode `itxn_next`([#626](https://github.com/scale-it/algo-builder/pull/626)) + - Add new opcode `itxn_next`([#626](https://github.com/scale-it/algo-builder/pull/626)). + - Add new opcode `gitxn`, `gitxna` and `gitxnas`.([#628](https://github.com/scale-it/algo-builder/pull/628)). - Contract to contract calls. However we limit c2c call with only AppCall(NoOpt) transactions.([#611](https://github.com/scale-it/algo-builder/pull/611)) - Added support for saving smart contract template params in ASCCache. diff --git a/packages/runtime/src/interpreter/opcode-list.ts b/packages/runtime/src/interpreter/opcode-list.ts index 3e6380a7d..993af5617 100644 --- a/packages/runtime/src/interpreter/opcode-list.ts +++ b/packages/runtime/src/interpreter/opcode-list.ts @@ -57,11 +57,12 @@ import { encTxToExecParams, isEncTxApplicationCall, txAppArg, - txnSpecbyField, + txnSpecByField, } from "../lib/txn"; import { DecodingMode, EncodingType, + EncTx, StackElem, TEALStack, TxnType, @@ -1325,7 +1326,7 @@ export class Txn extends Op { this.line ); } else { - result = txnSpecbyField( + result = txnSpecByField( this.field, this.interpreter.runtime.ctx.tx, this.interpreter.runtime.ctx.gtxs, @@ -1345,7 +1346,9 @@ export class Gtxn extends Op { readonly interpreter: Interpreter; readonly line: number; protected txIdx: number; - + // use to store group txn we want to query + // can change it to inner group txn + groupTxn: EncTx[]; /** * Sets `field`, `txIdx` values according to the passed arguments. * @param args Expected arguments: [transaction group index, transaction field] @@ -1370,24 +1373,20 @@ export class Gtxn extends Op { this.txIdx = Number(args[0]); // transaction group index this.field = args[1]; // field + this.groupTxn = interpreter.runtime.ctx.gtxs; this.interpreter = interpreter; } execute(stack: TEALStack): void { this.assertUint8(BigInt(this.txIdx), this.line); - this.checkIndexBound(this.txIdx, this.interpreter.runtime.ctx.gtxs, this.line); + this.checkIndexBound(this.txIdx, this.groupTxn, this.line); let result; + const tx = this.groupTxn[this.txIdx]; // current tx if (this.txFieldIdx !== undefined) { - const tx = this.interpreter.runtime.ctx.gtxs[this.txIdx]; // current tx result = txAppArg(this.field, tx, this.txFieldIdx, this, this.interpreter, this.line); } else { - result = txnSpecbyField( - this.field, - this.interpreter.runtime.ctx.gtxs[this.txIdx], - this.interpreter.runtime.ctx.gtxs, - this.interpreter.tealVersion - ); + result = txnSpecByField(this.field, tx, this.groupTxn, this.interpreter.tealVersion); } stack.push(result); } @@ -1456,6 +1455,7 @@ export class Gtxna extends Op { readonly interpreter: Interpreter; readonly line: number; fieldIdx: number; // array index + groupTxn: EncTx[]; protected txIdx: number; // transaction group index /** @@ -1478,14 +1478,15 @@ export class Gtxna extends Op { this.txIdx = Number(args[0]); // transaction group index this.field = args[1]; // field this.fieldIdx = Number(args[2]); // transaction field array index + this.groupTxn = interpreter.runtime.ctx.gtxs; this.interpreter = interpreter; this.line = line; } execute(stack: TEALStack): void { this.assertUint8(BigInt(this.txIdx), this.line); - this.checkIndexBound(this.txIdx, this.interpreter.runtime.ctx.gtxs, this.line); - const tx = this.interpreter.runtime.ctx.gtxs[this.txIdx]; + this.checkIndexBound(this.txIdx, this.groupTxn, this.line); + const tx = this.groupTxn[this.txIdx]; const result = txAppArg(this.field, tx, this.fieldIdx, this, this.interpreter, this.line); stack.push(result); } @@ -4178,7 +4179,7 @@ export class ITxn extends Op { // if field is an array use txAppArg (with "Accounts"/"ApplicationArgs"/'Assets'..) result = txAppArg(this.field, tx, this.idx, this, this.interpreter, this.line); } else { - result = txnSpecbyField(this.field, tx, [tx], this.interpreter.tealVersion); + result = txnSpecByField(this.field, tx, [tx], this.interpreter.tealVersion); } break; @@ -4658,3 +4659,77 @@ export class ITxnNext extends Op { ); } } + +// Stack: ... → ..., any +// field F of the Tth transaction in the last inner group submitted +export class Gitxn extends Gtxn { + /** + * Sets `txIdx `field`, ` values according to the passed arguments. + * @param args Expected arguments: [transaction group index, transaction field] + * // Note: Transaction field is expected as string instead of number. + * For ex: `Fee` is expected and `0` is not expected. + * @param line line number in TEAL file + * @param interpreter interpreter object + */ + constructor(args: string[], line: number, interpreter: Interpreter) { + super(args, line, interpreter); + } + + execute(stack: TEALStack): void { + // change context to last inner txn submitted + const lastInnerTxnGroupIndex = this.interpreter.innerTxnGroups.length - 1; + const lastInnerTxnGroup = this.interpreter.innerTxnGroups[lastInnerTxnGroupIndex]; + this.groupTxn = lastInnerTxnGroup; + super.execute(stack); + } +} + +//Stack: ... → ..., any +//Ith value of the array field F from the Tth transaction in the last inner group submitted +export class Gitxna extends Gtxna { + /** + * Sets `field`(Transaction Field), `fieldIdx`(Array Index) and + * `txIdx`(Transaction Group Index) values according to the passed arguments. + * @param args Expected arguments: + * [transaction group index, transaction field, transaction field array index] + * Note: Transaction field is expected as string instead of a number. + * For ex: `"Fee"` rather than `0`. + * @param line line number in TEAL file + * @param interpreter interpreter object + */ + constructor(args: string[], line: number, interpreter: Interpreter) { + super(args, line, interpreter); + } + + execute(stack: TEALStack): void { + // change context to last inner txn submitted + const lastInnerTxnGroupIndex = this.interpreter.innerTxnGroups.length - 1; + const lastInnerTxnGroup = this.interpreter.innerTxnGroups[lastInnerTxnGroupIndex]; + this.groupTxn = lastInnerTxnGroup; + super.execute(stack); + } +} + +export class Gitxnas extends Gtxnas { + /** + * Sets `field`(Transaction Field), `fieldIdx`(Array Index) and + * `txIdx`(Transaction Group Index) values according to the passed arguments. + * @param args Expected arguments: + * [transaction group index, transaction field, transaction field array index] + * Note: Transaction field is expected as string instead of a number. + * For ex: `"Fee"` rather than `0`. + * @param line line number in TEAL file + * @param interpreter interpreter object + */ + constructor(args: string[], line: number, interpreter: Interpreter) { + super(args, line, interpreter); + } + + execute(stack: TEALStack): void { + // change context to last inner txn submitted + const lastInnerTxnGroupIndex = this.interpreter.innerTxnGroups.length - 1; + const lastInnerTxnGroup = this.interpreter.innerTxnGroups[lastInnerTxnGroupIndex]; + this.groupTxn = lastInnerTxnGroup; + super.execute(stack); + } +} diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index 291e7c38a..f3964ca80 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -79,7 +79,7 @@ export function checkIfAssetDeletionTx(txn: Transaction): boolean { * @param txns Transaction group * @param tealVersion version of TEAL */ -export function txnSpecbyField( +export function txnSpecByField( txField: string, tx: EncTx, gtxns: EncTx[], diff --git a/packages/runtime/src/parser/parser.ts b/packages/runtime/src/parser/parser.ts index a563de480..eb0054344 100644 --- a/packages/runtime/src/parser/parser.ts +++ b/packages/runtime/src/parser/parser.ts @@ -81,6 +81,9 @@ import { GetAssetHolding, GetBit, GetByte, + Gitxn, + Gitxna, + Gitxnas, Gload, Gloads, Gloadss, @@ -379,6 +382,9 @@ opCodeMap[6] = { gloadss: Gloadss, acct_params_get: AcctParamsGet, itxn_next: ITxnNext, + gitxn: Gitxn, + gitxna: Gitxna, + gitxnas: Gitxnas, itxnas: ITxnas, }; @@ -438,6 +444,9 @@ const interpreterReqList = new Set([ "gloadss", "acct_params_get", "itxn_next", + "gitxn", + "gitxna", + "gitxnas", "itxnas", ]); @@ -470,9 +479,14 @@ const applicationModeOps = new Set([ "itxna", "gloadss", "acct_params_get", + "itxn_next", + "gitxn", + "gitxna", + "gitxnas", "itxnas", ]); +// TODO: Check where we can use `commonModeOps` // opcodes allowed in both application and signature mode const commonModeOps = new Set([ "err", @@ -586,7 +600,6 @@ const commonModeOps = new Set([ "divw", "bsqrt", "gloadss", - "acct_params_get", ]); /** diff --git a/packages/runtime/test/fixtures/teal-files/assets/teal-v6.teal b/packages/runtime/test/fixtures/teal-files/assets/teal-v6.teal index 3d59b29f9..709788b1e 100644 --- a/packages/runtime/test/fixtures/teal-files/assets/teal-v6.teal +++ b/packages/runtime/test/fixtures/teal-files/assets/teal-v6.teal @@ -3,4 +3,8 @@ bsqrt divw gloadss acct_params_get AcctBalance +itxn_next +gitxn 0 Fee +gitxna 1 Accounts 1 +gitxnas 0 Accounts itxnas Accounts \ No newline at end of file diff --git a/packages/runtime/test/src/interpreter/opcode-list.ts b/packages/runtime/test/src/interpreter/opcode-list.ts index 91a909083..4a2809269 100644 --- a/packages/runtime/test/src/interpreter/opcode-list.ts +++ b/packages/runtime/test/src/interpreter/opcode-list.ts @@ -90,6 +90,9 @@ import { GetAssetHolding, GetBit, GetByte, + Gitxn, + Gitxna, + Gitxnas, Gload, Gloads, Gloadss, @@ -2464,6 +2467,87 @@ describe("Teal Opcodes", function () { }); }); + describe("Gitxn", function () { + before(function () { + const tx = interpreter.runtime.ctx.tx; + // a) 'apas' represents 'foreignAssets', b) + // 'apfa' represents 'foreignApps' (id's of foreign apps) + // https://developer.algorand.org/docs/reference/transactions/ + const tx2 = { ...tx, fee: 2222, apas: [3033, 4044], apfa: [5005, 6006, 7077] }; + interpreter.innerTxnGroups = [[tx, tx2]]; + }); + + it("Should push fee from 2nd transaction in group", function () { + const op = new Gitxn(["1", "Fee"], 1, interpreter); + op.execute(stack); + + assert.equal(1, stack.length()); + assert.equal(2222n, stack.pop()); + }); + + it("Should push value from accounts or args array by index from tx group", function () { + let op = new Gitxn(["1", "Accounts", "0"], 1, interpreter); + op.execute(stack); + + const senderPk = Uint8Array.from(interpreter.runtime.ctx.tx.snd); + assert.equal(1, stack.length()); + assert.deepEqual(senderPk, stack.pop()); + + // should push Accounts[0] to stack + op = new Gitxn(["1", "Accounts", "1"], 1, interpreter); + op.execute(stack); + + assert.equal(1, stack.length()); + assert.deepEqual(TXN_OBJ.apat[0], stack.pop()); + + // should push Accounts[1] to stack + op = new Gitxn(["1", "Accounts", "2"], 1, interpreter); + op.execute(stack); + + assert.equal(1, stack.length()); + assert.deepEqual(TXN_OBJ.apat[1], stack.pop()); + + op = new Gitxn(["1", "ApplicationArgs", "0"], 0, interpreter); + op.execute(stack); + + assert.equal(1, stack.length()); + assert.deepEqual(TXN_OBJ.apaa[0], stack.pop()); + }); + + it("Should push value from assets or applications array by index from tx group", function () { + let op = new Gitxn(["1", "Assets", "0"], 1, interpreter); + op.execute(stack); + assert.equal(1, stack.length()); + assert.deepEqual(3033n, stack.pop()); // first asset from 2nd tx in group + + op = new Gitxn(["0", "Assets", "0"], 1, interpreter); + op.execute(stack); + assert.equal(1, stack.length()); + assert.deepEqual(BigInt(TXN_OBJ.apas[0]), stack.pop()); // first asset from 1st tx + + op = new Gitxn(["1", "NumAssets"], 1, interpreter); + op.execute(stack); + assert.equal(1, stack.length()); + assert.deepEqual(2n, stack.pop()); + + op = new Gitxn(["1", "NumApplications"], 1, interpreter); + op.execute(stack); + assert.equal(1, stack.length()); + assert.deepEqual(3n, stack.pop()); + + // index 0 represent tx.apid (current application id) + op = new Gitxn(["1", "Applications", "0"], 1, interpreter); + op.execute(stack); + assert.equal(1, stack.length()); + assert.deepEqual(BigInt(interpreter.runtime.ctx.tx.apid as number), stack.pop()); + + op = new Gitxn(["0", "Applications", "2"], 1, interpreter); + op.execute(stack); + assert.equal(1, stack.length()); + assert.deepEqual(BigInt(TXN_OBJ.apfa[1]), stack.pop()); + }); + }); + describe("Txna", function () { before(function () { interpreter.runtime.ctx.tx.type = "pay"; @@ -2588,6 +2672,90 @@ describe("Teal Opcodes", function () { ); }); }); + + describe("Gitxna", function () { + this.beforeEach(() => { + interpreter.tealVersion = 6; + const tx = interpreter.runtime.ctx.tx; + // a) 'apas' represents 'foreignAssets', b) + // 'apfa' represents 'foreignApps' (id's of foreign apps) + // https://developer.algorand.org/docs/reference/transactions/ + const tx2 = { ...tx, fee: 2222, apas: [3033, 4044], apfa: [5005, 6006, 7077] }; + interpreter.innerTxnGroups = [[tx, tx2]]; + }); + + it("Should push addr from 1st account of 2nd Txn in txGrp to stack", function () { + // index 0 should push sender's address to stack from 1st tx + let op = new Gitxna(["0", "Accounts", "1"], 1, interpreter); + op.execute(stack); + + const senderPk = Uint8Array.from(interpreter.runtime.ctx.gtxs[0].snd); + assert.equal(1, stack.length()); + assert.deepEqual(senderPk, stack.pop()); + + // should push Accounts[0] to stack + op = new Gitxna(["0", "Accounts", "1"], 1, interpreter); + op.execute(stack); + + assert.equal(1, stack.length()); + assert.deepEqual(TXN_OBJ.apat[0], stack.pop()); + + // should push Accounts[1] to stack + op = new Gitxna(["0", "Accounts", "2"], 1, interpreter); + op.execute(stack); + + assert.equal(1, stack.length()); + assert.deepEqual(TXN_OBJ.apat[1], stack.pop()); + }); + + it("Should throw an error if field is not an array", function () { + execExpectError( + stack, + [], + new Gitxna(["1", "Accounts", "0"], 1, interpreter), + RUNTIME_ERRORS.TEAL.INVALID_OP_ARG + ); + }); + }); + + describe("Gtxnas", function () { + it("Should push addr from 1st account of 2nd Txn in txGrp to stack", function () { + // index 0 should push sender's address to stack from 1st tx + stack.push(0n); + let op = new Gitxnas(["0", "Accounts"], 1, interpreter); + op.execute(stack); + + const senderPk = Uint8Array.from(interpreter.runtime.ctx.gtxs[0].snd); + assert.equal(1, stack.length()); + assert.deepEqual(senderPk, stack.pop()); + + // should push Accounts[0] to stack + stack.push(1n); + op = new Gitxnas(["0", "Accounts"], 1, interpreter); + op.execute(stack); + + assert.equal(1, stack.length()); + assert.deepEqual(TXN_OBJ.apat[0], stack.pop()); + + // should push Accounts[1] to stack + stack.push(2n); + op = new Gitxnas(["0", "Accounts"], 1, interpreter); + op.execute(stack); + + assert.equal(1, stack.length()); + assert.deepEqual(TXN_OBJ.apat[1], stack.pop()); + }); + + it("should throw error if field is not an array", function () { + stack.push(0n); + execExpectError( + stack, + [], + new Gitxnas(["1", "Accounts"], 1, interpreter), + RUNTIME_ERRORS.TEAL.INVALID_OP_ARG + ); + }); + }); }); describe("Global Opcode", function () { diff --git a/packages/runtime/test/src/parser/parser.ts b/packages/runtime/test/src/parser/parser.ts index 889d202be..8e927641c 100644 --- a/packages/runtime/test/src/parser/parser.ts +++ b/packages/runtime/test/src/parser/parser.ts @@ -62,6 +62,9 @@ import { GetAssetHolding, GetBit, GetByte, + Gitxn, + Gitxna, + Gitxnas, Gload, Gloads, Gloadss, @@ -377,6 +380,7 @@ describe("Parser", function () { beforeEach(function () { interpreter = new Interpreter(); interpreter.tealVersion = MaxTEALVersion; + interpreter.runtime = new Runtime([]); }); it("should return correct opcode object for '+'", () => { @@ -1978,69 +1982,241 @@ describe("Parser", function () { }); }); - describe("shoud return correct opcode for tealv6 ops", () => { - it("gloadss", () => { - const res = opcodeFromSentence(["gloadss"], 1, interpreter, ExecutionMode.APPLICATION); - const expected = new Gloadss([], 1, interpreter); - - assert.deepEqual(res, expected); - - expectRuntimeError( - () => opcodeFromSentence(["gloadss", "1"], 1, interpreter, ExecutionMode.APPLICATION), - RUNTIME_ERRORS.TEAL.ASSERT_LENGTH - ); - - expectRuntimeError( - () => opcodeFromSentence(["gloadss"], 1, interpreter, ExecutionMode.SIGNATURE), - RUNTIME_ERRORS.TEAL.EXECUTION_MODE_NOT_VALID - ); + describe("opcodes for tealv6 ops", function () { + this.beforeEach(() => { + interpreter.tealVersion = 6; }); - it("acct_params_get", () => { - Object.keys(AcctParamQueryFields).forEach((appParam: string) => { + describe("gloadss opcode", function () { + it("should succeed create gloadss", () => { const res = opcodeFromSentence( - ["acct_params_get", appParam], + ["gloadss"], 1, interpreter, ExecutionMode.APPLICATION ); - const expected = new AcctParamsGet([appParam], 1, interpreter); + const expected = new Gloadss([], 1, interpreter); + assert.deepEqual(res, expected); }); + it("Should fail: create opcode with invalid parameters", () => { + expectRuntimeError( + () => + opcodeFromSentence(["gloadss", "1"], 1, interpreter, ExecutionMode.APPLICATION), + RUNTIME_ERRORS.TEAL.ASSERT_LENGTH + ); - expectRuntimeError( - () => - opcodeFromSentence( - ["acct_params_get", "unknow", "hello"], + expectRuntimeError( + () => opcodeFromSentence(["gloadss"], 1, interpreter, ExecutionMode.SIGNATURE), + RUNTIME_ERRORS.TEAL.EXECUTION_MODE_NOT_VALID + ); + }); + }); + + describe("acct_params_get Opcode", function () { + it("Should succeed: create new acct_params_get opcode", () => { + Object.keys(AcctParamQueryFields).forEach((appParam: string) => { + const res = opcodeFromSentence( + ["acct_params_get", appParam], 1, interpreter, ExecutionMode.APPLICATION - ), - RUNTIME_ERRORS.TEAL.ASSERT_LENGTH - ); + ); + const expected = new AcctParamsGet([appParam], 1, interpreter); + assert.deepEqual(res, expected); + }); + }); + it("Should fail: create acct_params_get opcode with invalid parameter", () => { + expectRuntimeError( + () => + opcodeFromSentence( + ["acct_params_get", "unknow", "hello"], + 1, + interpreter, + ExecutionMode.APPLICATION + ), + RUNTIME_ERRORS.TEAL.ASSERT_LENGTH + ); + }); }); - it("itxn_next", () => { - // can parse opcode - const res = opcodeFromSentence( - ["itxn_next"], - 1, - interpreter, - ExecutionMode.APPLICATION - ); - const expected = new ITxnNext([], 1, interpreter); - assert.deepEqual(res, expected); + describe("itxn_next opcode", () => { + it("Should succeed: create new itxn_next opcode", () => { + // can parse opcode + const res = opcodeFromSentence( + ["itxn_next"], + 1, + interpreter, + ExecutionMode.APPLICATION + ); + const expected = new ITxnNext([], 1, interpreter); + assert.deepEqual(res, expected); + }); + it("Should fail: Create itxn_next with invalid parameters", () => { + expectRuntimeError( + () => + opcodeFromSentence( + ["itxn_next", "unknowfield"], + 1, + interpreter, + ExecutionMode.APPLICATION + ), + RUNTIME_ERRORS.TEAL.ASSERT_LENGTH + ); + }); + }); - expectRuntimeError( - () => - opcodeFromSentence( - ["itxn_next", "unknowfield"], - 1, - interpreter, - ExecutionMode.APPLICATION - ), - RUNTIME_ERRORS.TEAL.ASSERT_LENGTH - ); + describe("gitxn Opcode", () => { + it("Should succeed: create new gitxn opcode", () => { + let res = opcodeFromSentence( + ["gitxn", "0", "Fee"], + 1, + interpreter, + ExecutionMode.APPLICATION + ); + let expected = new Gitxn(["0", "Fee"], 1, interpreter); + assert.deepEqual(res, expected); + + res = opcodeFromSentence( + ["gitxn", "0", "ApplicationArgs", "0"], + 1, + interpreter, + ExecutionMode.APPLICATION + ); + expected = new Gitxn(["0", "ApplicationArgs", "0"], 1, interpreter); + assert.deepEqual(res, expected); + }); + it("Should fail: create gitxn opcode with invalid parameters", () => { + expectRuntimeError( + () => opcodeFromSentence(["gitxn", "1"], 1, interpreter, ExecutionMode.APPLICATION), + RUNTIME_ERRORS.TEAL.ASSERT_LENGTH + ); + + expectRuntimeError( + () => + opcodeFromSentence( + ["gitxn", "1AA", "Fee"], + 1, + interpreter, + ExecutionMode.APPLICATION + ), + RUNTIME_ERRORS.TEAL.INVALID_TYPE + ); + }); + }); + + describe("gitxna Opcode", () => { + it("Should succeed: create new gitxna opcode", () => { + let res = opcodeFromSentence( + ["gitxna", "1", "Accounts", "1"], + 1, + interpreter, + ExecutionMode.APPLICATION + ); + let expected = new Gitxna(["1", "Accounts", "1"], 1, interpreter); + assert.deepEqual(res, expected); + + res = opcodeFromSentence( + ["gitxna", "1", "ApplicationArgs", "4"], + 1, + interpreter, + ExecutionMode.APPLICATION + ); + expected = new Gitxna(["1", "ApplicationArgs", "4"], 1, interpreter); + assert.deepEqual(res, expected); + }); + + it("Should fail: create gitxna with invalid parameters", () => { + expectRuntimeError( + () => + opcodeFromSentence( + ["gitxna", "1", "Fee", "4"], + 1, + interpreter, + ExecutionMode.APPLICATION + ), + RUNTIME_ERRORS.TEAL.INVALID_OP_ARG + ); + + expectRuntimeError( + () => + opcodeFromSentence( + ["gitxna", "1", "2", "3", "4"], + 1, + interpreter, + ExecutionMode.APPLICATION + ), + RUNTIME_ERRORS.TEAL.ASSERT_LENGTH + ); + + expectRuntimeError( + () => + opcodeFromSentence( + ["gitxna", "1AB", "Fee", "4"], + 1, + interpreter, + ExecutionMode.APPLICATION + ), + RUNTIME_ERRORS.TEAL.INVALID_TYPE + ); + }); + }); + + describe("gitxnas Opcode", () => { + it("Should succeed: create new gitxnas opcode", () => { + let res = opcodeFromSentence( + ["gitxnas", "1", "Accounts"], + 1, + interpreter, + ExecutionMode.APPLICATION + ); + let expected = new Gitxnas(["1", "Accounts"], 1, interpreter); + assert.deepEqual(res, expected); + + res = opcodeFromSentence( + ["gitxnas", "1", "ApplicationArgs"], + 1, + interpreter, + ExecutionMode.APPLICATION + ); + expected = new Gitxnas(["1", "ApplicationArgs"], 1, interpreter); + assert.deepEqual(res, expected); + }); + + it("Should fail: create gitxnas with invalid parameters", () => { + expectRuntimeError( + () => + opcodeFromSentence( + ["gitxnas", "1", "Fee"], + 1, + interpreter, + ExecutionMode.APPLICATION + ), + RUNTIME_ERRORS.TEAL.INVALID_OP_ARG + ); + + expectRuntimeError( + () => + opcodeFromSentence( + ["gitxnas", "1", "2", "3"], + 1, + interpreter, + ExecutionMode.APPLICATION + ), + RUNTIME_ERRORS.TEAL.ASSERT_LENGTH + ); + + expectRuntimeError( + () => + opcodeFromSentence( + ["gitxnas", "1AB", "Fee"], + 1, + interpreter, + ExecutionMode.APPLICATION + ), + RUNTIME_ERRORS.TEAL.INVALID_TYPE + ); + }); }); describe("itxnas opcode", () => { @@ -2079,6 +2255,7 @@ describe("Parser", function () { let interpreter: Interpreter; beforeEach(function () { interpreter = new Interpreter(); + interpreter.runtime = new Runtime([]); interpreter.tealVersion = 2; }); @@ -2387,7 +2564,7 @@ describe("Parser", function () { assert.deepEqual(res, expected); }); - it("should rreturn correct opcode list for `teal v6`", async () => { + it("should return correct opcode list for `teal v6`", async () => { const file = "teal-v6.teal"; const res = parser(getProgram(file), ExecutionMode.APPLICATION, interpreter); @@ -2397,7 +2574,11 @@ describe("Parser", function () { new Bsqrt([], 3), new Gloadss([], 4, interpreter), new AcctParamsGet(["AcctBalance"], 5, interpreter), - new ITxnas(["Accounts"], 6, interpreter), + new ITxnNext([], 6, interpreter), + new Gitxn(["0", "Fee"], 7, interpreter), + new Gitxna(["1", "Accounts", "1"], 8, interpreter), + new Gitxnas(["0", "Accounts"], 9, interpreter), + new ITxnas(["Accounts"], 10, interpreter), ]; assert.deepEqual(res, expected);