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

base64_decode opcode from TEALv7 #653

Merged
merged 10 commits into from
Apr 29, 2022
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Added:

- Added support for saving smart contract template params in ASCCache.

- Teal v7 support:
- opcode `base64decode` ([##653](https://github.com/scale-it/algo-builder/pull/653))

- `algob test` now runs tests in `test` directory and all its subdirectories. Before only the files inside `test directory where run `.

Expand All @@ -60,6 +62,7 @@ Added:
console.log("txn1 information: ", receipts[2]);
```


### Template improvements

- We updated the examples/DAO design. We removed treasury Smart Signature to simplify deposit management. Now a DAO app is managing voting, deposits and treasury.
Expand Down
15 changes: 14 additions & 1 deletion packages/runtime/src/errors/errors-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ by an index that does not exist.`,
},
PRAGMA_VERSION_ERROR: {
number: 1017,
message: "Pragma version Error - Expected version: %expected%, got: %got%, Line: %line%",
message:
"Pragma version Error - Expected version up to: %expected%, got: %got%, Line: %line%",
title: PARSE_ERROR,
description: ``,
},
Expand Down Expand Up @@ -398,6 +399,18 @@ maximun uint128`,
title: "itxn_next without itxn_begin",
description: `itxn_next without itxn_begin`,
},
UNKNOWN_ENCODING: {
number: 1058,
message: "Encoding e must be {URLEncoding, StdEncoding}, got :%encoding%, at line %line%",
title: "Unknown encoding",
description: "Unknown encoding",
},
INVALID_BASE64URL: {
number: 1059,
message: "Invalid Base64Url Error - value %val% is not base64Url, Line: %line%",
title: PARSE_ERROR,
description: `value %exp% is not base64Url`,
},
};

const runtimeGeneralErrors = {
Expand Down
9 changes: 7 additions & 2 deletions packages/runtime/src/interpreter/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,8 @@ export class Interpreter {

while (this.instructionIndex < this.instructions.length) {
const instruction = this.instructions[this.instructionIndex];
instruction.execute(this.stack);
//TODO this should return cost
const costFromExecute = instruction.execute(this.stack);

if (
this.runtime.ctx.isInnerTx &&
Expand All @@ -550,7 +551,11 @@ export class Interpreter {

// for teal version >= 4, cost is calculated dynamically at the time of execution
// for teal version < 4, cost is handled statically during parsing
this.cost += this.lineToCost[instruction.line];
if (costFromExecute === undefined) {
this.cost += this.lineToCost[instruction.line];
} else {
this.cost = costFromExecute;
}
if (this.tealVersion < 4) {
txReceipt.gas = this.gas;
}
Expand Down
59 changes: 58 additions & 1 deletion packages/runtime/src/interpreter/opcode-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import {
import { addInnerTransaction, calculateInnerTxCredit, setInnerTxField } from "../lib/itxn";
import { bigintSqrt } from "../lib/math";
import {
assertBase64,
assertBase64Url,
assertLen,
assertNumber,
assertOnlyDigits,
Expand All @@ -61,6 +63,7 @@ import {
txnSpecByField,
} from "../lib/txn";
import {
Base64Encoding,
DecodingMode,
EncodingType,
EncTx,
Expand Down Expand Up @@ -98,7 +101,7 @@ export class Pragma extends Op {
interpreter.tealVersion = this.version;
} else {
throw new RuntimeError(RUNTIME_ERRORS.TEAL.PRAGMA_VERSION_ERROR, {
expected: "till #4",
expected: MaxTEALVersion,
got: args.join(" "),
line: line,
});
Expand Down Expand Up @@ -4754,3 +4757,57 @@ export class Gitxnas extends Gtxnas {
super.execute(stack);
}
}

/**
* Takes the last value from stack and if base64encoded, decodes it acording to the
* encoding e and pushes it back to the stack, otherwise throws an error
*/
export class Base64Decode extends Op {
readonly line: number;
readonly encoding: Base64Encoding;

/**
* Asserts 1 argument is passed.
* @param args Expected arguments: [e], where e = {URLEncoding, StdEncoding}.
* @param line line number in TEAL file
*/
constructor(args: string[], line: number) {
super();
this.line = line;
assertLen(args.length, 1, line);
const argument = args[0];
switch (argument) {
case "URLEncoding": {
this.encoding = 0;
break;
}
case "StdEncoding": {
this.encoding = 1;
break;
}
default: {
throw new RuntimeError(RUNTIME_ERRORS.TEAL.UNKNOWN_ENCODING, {
encoding: argument,
line: this.line,
});
}
}
}

execute(stack: TEALStack): void {
this.assertMinStackLen(stack, 1, this.line);
const last = this.assertBytes(stack.pop(), this.line);
const enc = new TextDecoder("utf-8");
const decoded = enc.decode(last);
switch (this.encoding) {
case Base64Encoding.URL:
assertBase64Url(convertToString(last), this.line);
stack.push(new Uint8Array(Buffer.from(decoded.toString(), "base64url")));
break;
case Base64Encoding.STD:
assertBase64(convertToString(last), this.line);
stack.push(new Uint8Array(Buffer.from(decoded.toString(), "base64")));
break;
}
}
}
36 changes: 35 additions & 1 deletion packages/runtime/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const MAX_CONCAT_SIZE = 4096;
export const ALGORAND_MIN_TX_FEE = 1000;
// https://github.com/algorand/go-algorand/blob/master/config/consensus.go#L659
export const ALGORAND_ACCOUNT_MIN_BALANCE = 0.1e6; // 0.1 ALGO
export const MaxTEALVersion = 6;
export const MaxTEALVersion = 7;
export const MinVersionSupportC2CCall = 6;

// values taken from: https://developer.algorand.org/docs/features/asc1/stateful/#minimum-balance-requirement-for-a-smart-contract
Expand Down Expand Up @@ -152,6 +152,10 @@ TxnFields[6] = {
...TxnFields[5],
};

TxnFields[7] = {
...TxnFields[6],
};

export const ITxnFields: { [key: number]: { [key: string]: keyOfEncTx | null } } = {
1: {},
2: {},
Expand All @@ -169,6 +173,10 @@ ITxnFields[6] = {
...ITxnFields[5],
};

ITxnFields[7] = {
...ITxnFields[6],
};

// transaction fields of type array
export const TxArrFields: { [key: number]: Set<string> } = {
1: new Set(),
Expand All @@ -178,6 +186,7 @@ TxArrFields[3] = new Set([...TxArrFields[2], "Assets", "Applications"]);
TxArrFields[4] = cloneDeep(TxArrFields[3]);
TxArrFields[5] = cloneDeep(TxArrFields[4]);
TxArrFields[6] = cloneDeep(TxArrFields[5]);
TxArrFields[7] = cloneDeep(TxArrFields[6]);

// itxn fields of type array
export const ITxArrFields: { [key: number]: Set<string> } = {
Expand All @@ -189,6 +198,7 @@ export const ITxArrFields: { [key: number]: Set<string> } = {
};

ITxArrFields[6] = cloneDeep(ITxArrFields[5]);
ITxArrFields[7] = cloneDeep(ITxArrFields[6]);

export const TxFieldDefaults: { [key: string]: any } = {
Sender: ZERO_ADDRESS,
Expand Down Expand Up @@ -275,6 +285,7 @@ AssetParamMap[5] = {
};

AssetParamMap[6] = { ...AssetParamMap[5] };
AssetParamMap[7] = { ...AssetParamMap[6] };

// app param use for app_params_get opcode
export const AppParamDefined: { [key: number]: Set<string> } = {
Expand All @@ -296,6 +307,7 @@ export const AppParamDefined: { [key: number]: Set<string> } = {
};

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

// param use for query acct_params_get opcode

Expand All @@ -322,6 +334,19 @@ export const reOct = /^0[0-8]+$/;
*/
export const reBase64 = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;

/** is Base64Url regex
* ^ # Start of input
* ([0-9a-zA-Z_-]{4})* # Groups of 4 valid characters decode
* # to 24 bits of data for each group
* ( # Either ending with:
* ([0-9a-zA-Z_-]{2}==) # two valid characters followed by ==
* | # , or
* ([0-9a-zA-Z_-]{3}=) # three valid characters followed by =
* )? # , or nothing
* $ # End of input
*/
export const reBase64Url = /^([0-9a-zA-Z_-]{4})*(([0-9a-zA-Z_-]{2}==)|([0-9a-zA-Z_-]{3}=))?$/;

// A-Z and 2-7 repeated, with optional `=` at the end
export const reBase32 = /^[A-Z2-7]+=*$/;

Expand Down Expand Up @@ -376,6 +401,10 @@ GlobalFields[6] = {
CallerApplicationAddress: null,
};

GlobalFields[7] = {
...GlobalFields[6],
};

// creating map for opcodes whose cost is other than 1
export const OpGasCost: { [key: number]: { [key: string]: number } } = {
// version => opcode => cost
Expand Down Expand Up @@ -432,6 +461,11 @@ OpGasCost[6] = {
...OpGasCost[5],
bsqrt: 40,
};
OpGasCost[7] = {
...OpGasCost[6],
//TODO: calculate the cost for the base64_decode opcode
// https://github.com/algorand/go-algorand/blob/master/data/transactions/logic/TEAL_opcodes.md#base64_decode-e
};

export const enum MathOp {
// arithmetic
Expand Down
13 changes: 12 additions & 1 deletion packages/runtime/src/lib/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as base32 from "hi-base32";
import { RUNTIME_ERRORS } from "../errors/errors-list";
import { RuntimeError } from "../errors/runtime-errors";
import { EncodingType } from "../types";
import { reBase32, reBase64, reDec, reDigit, reHex, reOct } from "./constants";
import { reBase32, reBase64, reBase64Url, reDec, reDigit, reHex, reOct } from "./constants";

/**
* assert if string contains digits only
Expand Down Expand Up @@ -68,6 +68,17 @@ export function assertBase64(str: string, line: number): void {
}
}

/**
* Checks if string is base64Url
* @param str : string that needs to be checked
* @param line : line number in TEAL file
*/
export function assertBase64Url(str: string, line: number): void {
if (!reBase64Url.test(str)) {
throw new RuntimeError(RUNTIME_ERRORS.TEAL.INVALID_BASE64URL, { val: str, line: line });
}
}

/**
* Checks if string is base32
* @param str : string that needs to be checked
Expand Down
10 changes: 10 additions & 0 deletions packages/runtime/src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
Args,
Assert,
Balance,
Base64Decode,
BitLen,
BitwiseAnd,
BitwiseNot,
Expand Down Expand Up @@ -388,6 +389,14 @@ opCodeMap[6] = {
itxnas: ITxnas,
};

/**
* TEALv7
*/
opCodeMap[7] = {
...opCodeMap[6],
base64_decode: Base64Decode,
};

// list of opcodes with exactly one parameter.
const interpreterReqList = new Set([
"#pragma",
Expand Down Expand Up @@ -448,6 +457,7 @@ const interpreterReqList = new Set([
"gitxna",
"gitxnas",
"itxnas",
"base64_decode",
]);

const signatureModeOps = new Set(["arg", "args", "arg_0", "arg_1", "arg_2", "arg_3"]);
Expand Down
5 changes: 5 additions & 0 deletions packages/runtime/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,8 @@ export interface SCParams {
export interface ReplaceParams {
[key: string]: string;
}

export enum Base64Encoding {
URL = 0,
STD = 1,
}
2 changes: 2 additions & 0 deletions packages/runtime/test/fixtures/teal-files/assets/teal-v7.teal
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#pragma version 7
base64_decode URLEncoding
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#pragma version 7
#pragma version 8
int 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#pragma version 7
int 1
Loading