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 acct_params_get opcode #618

Merged
merged 8 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ Added:
- Teal V6 support:
- 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)).

### Template improvements

- Using App instead of Lsig (Smart Signature) in `examples/dao` to simplify deposit management.
Expand Down
7 changes: 7 additions & 0 deletions packages/runtime/src/errors/errors-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,13 @@ maximun uint128`,
title: "Execution mode not valid",
description: `Execution mode not valid`,
},
UNKNOWN_ACCT_FIELD: {
number: 1055,
message:
"Account Field Error - Unknown Field: %field% at line %line% for teal version #%tealV%",
title: "Account Field Error at line %line%",
description: `Account field unknown`,
},
};

const runtimeGeneralErrors = {
Expand Down
43 changes: 36 additions & 7 deletions packages/runtime/src/interpreter/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {

import { RUNTIME_ERRORS } from "../errors/errors-list";
import { RuntimeError } from "../errors/runtime-errors";
import { Runtime } from "../index";
import { AccountStore, Runtime } from "../index";
import { checkIndexBound, compareArray } from "../lib/compare";
import {
ALGORAND_MAX_APP_ARGS_LEN,
Expand All @@ -22,6 +22,7 @@ import { keyToBytes } from "../lib/parsing";
import { Stack } from "../lib/stack";
import { assertMaxCost, parser } from "../parser/parser";
import {
AccountAddress,
AccountStoreI,
AppInfo,
BaseTxReceipt,
Expand Down Expand Up @@ -107,14 +108,35 @@ export class Interpreter {
return this.runtime.ctx.getApp(appID, line);
}

/**
* Create new account with `address` if this is undefined in ctx.
* return state of this account in ctx.
* @param addr address we want to query.
*/
private createAccountIfAbsent(addr: AccountAddress): AccountStoreI {
let account = this.runtime.ctx.state.accounts.get(addr);
if (!account) {
account = new AccountStore(0, { addr, sk: new Uint8Array(0) });
this.runtime.ctx.state.accounts.set(addr, account);
}
return account;
}

/**
* Beginning from TEALv4, user can directly pass address instead of index to Txn.Accounts.
* However, the address must still be present in tx.Accounts OR should be equal to Txn.Sender
* When `create` flag is true we will throws exception if account is not found.
* When `create` flag is false we will create new account and add it to context.
* @param accountPk public key of account
* @param line line number in TEAL file
* @param create create flag
* https://developer.algorand.org/articles/introducing-algorand-virtual-machine-avm-09-release/
*/
private _getAccountFromAddr(accountPk: Uint8Array, line: number): AccountStoreI {
private _getAccountFromAddr(
accountPk: Uint8Array,
line: number,
create: boolean
): AccountStoreI {
const txAccounts = this.runtime.ctx.tx.apat; // tx.Accounts array
const appID = this.runtime.ctx.tx.apid ?? 0;
if (this.tealVersion <= 3) {
Expand Down Expand Up @@ -145,7 +167,10 @@ export class Interpreter {
compareArray(accountPk, decodeAddress(getApplicationAddress(appID)).publicKey)
) {
const address = encodeAddress(pkBuffer);
const account = this.runtime.ctx.state.accounts.get(address);
const account = create
? this.createAccountIfAbsent(address)
: this.runtime.ctx.state.accounts.get(address);

return this.runtime.assertAccountDefined(address, account, line);
} else {
throw new RuntimeError(RUNTIME_ERRORS.TEAL.ADDR_NOT_FOUND_IN_TXN_ACCOUNT, {
Expand All @@ -158,12 +183,14 @@ export class Interpreter {
/**
* Queries account by accountIndex or `ctx.tx.snd` (if `accountIndex==0`).
* If account address is passed, then queries account by address.
* Throws exception if account is not found.
* When `create` flag is true we will throws exception if account is not found.
* When `create` flag is false we will create new account and add it to context.
* @param accountRef index of account to fetch from account list
* @param line line number
* @param create create flag, default is true
* NOTE: index 0 represents txn sender account
*/
getAccount(accountRef: StackElem, line: number): AccountStoreI {
getAccount(accountRef: StackElem, line: number, create = false): AccountStoreI {
let account: AccountStoreI | undefined;
let address: string;
if (typeof accountRef === "bigint") {
Expand All @@ -180,10 +207,12 @@ export class Interpreter {
throw new Error("pk Buffer not found");
}
address = encodeAddress(pkBuffer);
account = this.runtime.ctx.state.accounts.get(address);
account = create
? this.createAccountIfAbsent(address)
: this.runtime.ctx.state.accounts.get(address);
}
} else {
return this._getAccountFromAddr(accountRef, line);
return this._getAccountFromAddr(accountRef, line, create);
}

return this.runtime.assertAccountDefined(address, account, line);
Expand Down
68 changes: 68 additions & 0 deletions packages/runtime/src/interpreter/opcode-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { RUNTIME_ERRORS } from "../errors/errors-list";
import { RuntimeError } from "../errors/runtime-errors";
import { compareArray } from "../lib/compare";
import {
AcctParamQueryFields,
ALGORAND_MAX_LOGS_COUNT,
ALGORAND_MAX_LOGS_LENGTH,
AppParamDefined,
Expand Down Expand Up @@ -4562,3 +4563,70 @@ export class AppParamsGet extends Op {
}
}
}

export class AcctParamsGet extends Op {
readonly interpreter: Interpreter;
readonly line: number;
readonly field: string;
/**
* @param args Expected arguments: [account_param]
* @param line line number in TEAL file
* @param interpreter interpreter object
*/
constructor(args: string[], line: number, interpreter: Interpreter) {
super();
this.line = line;
this.interpreter = interpreter;
assertLen(args.length, 1, line);

if (
!AcctParamQueryFields[args[0]] ||
AcctParamQueryFields[args[0]].version > interpreter.tealVersion
) {
throw new RuntimeError(RUNTIME_ERRORS.TEAL.UNKNOWN_ACCT_FIELD, {
field: args[0],
line: line,
tealV: interpreter.tealVersion,
});
}

this.field = args[0];
}

execute(stack: Stack<StackElem>): void {
this.assertMinStackLen(stack, 1, this.line);

const acctAddress = this.assertAlgorandAddress(stack.pop(), this.line);

// get account from current context
// not `create` flag = true
const accountInfo = this.interpreter.getAccount(acctAddress, this.line, true);

let value: StackElem = 0n;
switch (this.field) {
case "AcctBalance": {
value = BigInt(accountInfo.balance());
break;
}
case "AcctMinBalance": {
value = BigInt(accountInfo.minBalance);
break;
}
case "AcctAuthAddr": {
if (accountInfo.getSpendAddress() === accountInfo.address) {
value = ZERO_ADDRESS;
} else {
value = Buffer.from(decodeAddress(accountInfo.getSpendAddress()).publicKey);
}
break;
}
}
stack.push(value);

if (accountInfo.balance() > 0) {
stack.push(1n);
} else {
stack.push(0n);
}
}
}
7 changes: 7 additions & 0 deletions packages/runtime/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ export const AppParamDefined: { [key: number]: Set<string> } = {

AppParamDefined[6] = cloneDeep(AppParamDefined[5]);

// param use for query acct_params_get opcode

export const AcctParamQueryFields: { [key: string]: { version: number } } = {
AcctBalance: { version: 6 },
AcctMinBalance: { version: 6 },
AcctAuthAddr: { version: 6 },
};
export const reDigit = /^\d+$/;
export const reDec = /^(0|[1-9]\d*)$/;
export const reHex = /^0x[0-9a-fA-F]+$/;
Expand Down
5 changes: 5 additions & 0 deletions packages/runtime/src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RUNTIME_ERRORS } from "../errors/errors-list";
import { RuntimeError } from "../errors/runtime-errors";
import { Interpreter } from "../interpreter/interpreter";
import {
AcctParamsGet,
Add,
Addr,
Addw,
Expand Down Expand Up @@ -374,6 +375,7 @@ opCodeMap[6] = {
divw: Divw,
bsqrt: Bsqrt,
gloadss: Gloadss,
acct_params_get: AcctParamsGet,
};

// list of opcodes with exactly one parameter.
Expand Down Expand Up @@ -430,6 +432,7 @@ const interpreterReqList = new Set([
"log",
"app_params_get",
"gloadss",
"acct_params_get",
]);

const signatureModeOps = new Set(["arg", "args", "arg_0", "arg_1", "arg_2", "arg_3"]);
Expand Down Expand Up @@ -460,6 +463,7 @@ const applicationModeOps = new Set([
"itxn",
"itxna",
"gloadss",
"acct_params_get",
]);

// opcodes allowed in both application and signature mode
Expand Down Expand Up @@ -575,6 +579,7 @@ const commonModeOps = new Set([
"divw",
"bsqrt",
"gloadss",
"acct_params_get",
]);

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/runtime/test/fixtures/teal-files/assets/teal-v6.teal
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma version 6
bsqrt
divw
gloadss
acct_params_get AcctBalance
Loading