Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:add gitxn, gitxna and gitxnas #628

Merged
merged 16 commits into from
Mar 28, 2022
73 changes: 62 additions & 11 deletions packages/runtime/src/interpreter/opcode-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { encTxToExecParams, txAppArg, txnSpecbyField } from "../lib/txn";
import {
DecodingMode,
EncodingType,
EncTx,
StackElem,
TEALStack,
TxnType,
Expand Down Expand Up @@ -1338,7 +1339,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]
Expand All @@ -1363,16 +1366,17 @@ 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,
Expand All @@ -1382,12 +1386,7 @@ export class Gtxn extends Op {
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);
}
Expand Down Expand Up @@ -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

/**
Expand All @@ -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,
Expand Down Expand Up @@ -4616,3 +4617,53 @@ 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);
}
}
11 changes: 10 additions & 1 deletion packages/runtime/src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ import {
GetAssetHolding,
GetBit,
GetByte,
Gitxn,
Gitxna,
Gload,
Gloads,
Gloadss,
Expand Down Expand Up @@ -378,6 +380,8 @@ opCodeMap[6] = {
gloadss: Gloadss,
acct_params_get: AcctParamsGet,
itxn_next: ITxnNext,
gitxn: Gitxn,
gitxna: Gitxna,
};

// list of opcodes with exactly one parameter.
Expand Down Expand Up @@ -436,6 +440,8 @@ const interpreterReqList = new Set([
"gloadss",
"acct_params_get",
"itxn_next",
"gitxn",
"gitxna",
]);

const signatureModeOps = new Set(["arg", "args", "arg_0", "arg_1", "arg_2", "arg_3"]);
Expand Down Expand Up @@ -467,8 +473,12 @@ const applicationModeOps = new Set([
"itxna",
"gloadss",
"acct_params_get",
"itxn_next",
"gitxn",
"gitxna",
]);

// TODO: Check where we can use `commonModeOps`
// opcodes allowed in both application and signature mode
const commonModeOps = new Set([
"err",
Expand Down Expand Up @@ -582,7 +592,6 @@ const commonModeOps = new Set([
"divw",
"bsqrt",
"gloadss",
"acct_params_get",
]);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
bsqrt
divw
gloadss
acct_params_get AcctBalance
acct_params_get AcctBalance
itxn_next
gitxn 0 Fee
gitxna 1 Accounts 1
127 changes: 127 additions & 0 deletions packages/runtime/test/src/interpreter/opcode-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ import {
GetAssetHolding,
GetBit,
GetByte,
Gitxn,
Gitxna,
Gload,
Gloads,
Gloadss,
Expand Down Expand Up @@ -2463,6 +2465,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";
Expand Down Expand Up @@ -2587,6 +2670,50 @@ 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 error if field is not an array", function () {
execExpectError(
stack,
[],
new Gitxna(["1", "Accounts", "0"], 1, interpreter),
RUNTIME_ERRORS.TEAL.INVALID_OP_ARG
);
});
});
});

describe("Global Opcode", function () {
Expand Down
Loading