From 93acd4f220d450a0325f78f721dcec6ea34d2d35 Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Fri, 18 Feb 2022 17:50:59 +0700 Subject: [PATCH 01/18] add signer field to parseEncodedTxnToExecParams --- packages/runtime/src/interpreter/opcode-list.ts | 17 ++++++++++++++--- packages/runtime/src/lib/itxn.ts | 10 +++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/runtime/src/interpreter/opcode-list.ts b/packages/runtime/src/interpreter/opcode-list.ts index 2497fc711..249de1195 100644 --- a/packages/runtime/src/interpreter/opcode-list.ts +++ b/packages/runtime/src/interpreter/opcode-list.ts @@ -1611,8 +1611,8 @@ export class Global extends Op { break; } case 'CreatorAddress': { - const appID = this.interpreter.runtime.ctx.tx.apid as number; - const app = this.interpreter.getApp(appID, this.line); + const appID = this.interpreter.runtime.ctx.tx.apid; + const app = this.interpreter.getApp(appID as number, this.line); result = decodeAddress(app.creator).publicKey; break; } @@ -3920,8 +3920,19 @@ export class ITxnSubmit extends Op { ); } + // initial contract account. + const appID = this.interpreter.runtime.ctx.tx.apid; + const contractAddress = getApplicationAddress(appID as number); + const contractAccount = { + addr: contractAddress, + sk: Buffer.from([]) + }; + // get execution txn params (parsed from encoded sdk txn obj) - const execParams = parseEncodedTxnToExecParams(this.interpreter.subTxn, this.interpreter, this.line); + // singer will be contractAccount + const execParams = parseEncodedTxnToExecParams( + contractAccount, this.interpreter.subTxn, this.interpreter, this.line + ); const baseCurrTx = this.interpreter.runtime.ctx.tx; const baseCurrTxGrp = this.interpreter.runtime.ctx.gtxs; diff --git a/packages/runtime/src/lib/itxn.ts b/packages/runtime/src/lib/itxn.ts index 6122b09fe..527842716 100644 --- a/packages/runtime/src/lib/itxn.ts +++ b/packages/runtime/src/lib/itxn.ts @@ -1,5 +1,5 @@ import { types } from "@algo-builder/web"; -import { decodeAddress, encodeAddress, getApplicationAddress } from "algosdk"; +import { Account as AccountSDK, decodeAddress, encodeAddress, getApplicationAddress } from "algosdk"; import cloneDeep from "lodash.clonedeep"; import { Interpreter } from ".."; @@ -259,16 +259,12 @@ const _getAddress = (addr?: Uint8Array): string | undefined => { // parse encoded txn obj to execParams (params passed by user in algob) /* eslint-disable sonarjs/cognitive-complexity */ -export function parseEncodedTxnToExecParams (tx: EncTx, +export function parseEncodedTxnToExecParams (singer: AccountSDK, tx: EncTx, interpreter: Interpreter, line: number): types.ExecParams { - // signer is the contract - const appID = interpreter.runtime.ctx.tx.apid ?? 0; - const appAddress = getApplicationAddress(appID); - // initial common fields const execParams: any = { sign: types.SignType.SecretKey, - fromAccount: { addr: appAddress, sk: Buffer.from([]) }, // signer is the contract + fromAccount: singer, // signer is the contract fromAccountAddr: encodeAddress(tx.snd), payFlags: { totalFee: tx.fee, From 77157ffd88ea5fc104202a65a4e4f79d4a3ec28f Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Wed, 23 Feb 2022 19:21:56 +0700 Subject: [PATCH 02/18] support algorand sdk makeAssetCreateTxn transaction in runtime --- packages/runtime/src/lib/txn.ts | 132 ++++++++++++++++++++++++++- packages/runtime/src/runtime.ts | 51 ++++++++--- packages/runtime/test/src/lib/txn.ts | 44 +++++++++ packages/runtime/test/src/runtime.ts | 53 ++++++++++- 4 files changed, 259 insertions(+), 21 deletions(-) create mode 100644 packages/runtime/test/src/lib/txn.ts diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index b0b915966..c03411cce 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -1,11 +1,12 @@ -import { parsing } from "@algo-builder/web"; -import { EncodedAssetParams, EncodedGlobalStateSchema, Transaction } from "algosdk"; +import { parsing, types } from "@algo-builder/web"; +import { AccountAddress } from "@algo-builder/web/build/types"; +import { encodeAddress, EncodedAssetParams, EncodedGlobalStateSchema, Transaction } from "algosdk"; import { RUNTIME_ERRORS } from "../errors/errors-list"; import { RuntimeError } from "../errors/runtime-errors"; import { Op } from "../interpreter/opcode"; -import { TxFieldDefaults, TxnFields } from "../lib/constants"; -import { EncTx, StackElem, TxField, TxnType } from "../types"; +import { TxFieldDefaults, TxnFields, ZERO_ADDRESS_STR } from "../lib/constants"; +import { Context, EncTx, RuntimeAccountI, StackElem, TxField, TxnType } from "../types"; export const assetTxnFields = new Set([ 'ConfigAssetTotal', @@ -191,3 +192,126 @@ export function isEncTxAssetConfig (txn: EncTx): boolean { (txn.caid !== undefined && txn.caid !== 0) && // assetIndex should not be 0 !isEncTxAssetDeletion(txn); // AND should not be asset deletion } + +export function transactionAndSignToExecParams ( + txAndSign: types.TransactionAndSign, ctx: Context, line?: number +): types.ExecParams { + const encTx = txAndSign.transaction.get_obj_for_encoding() as EncTx; + const sign = txAndSign.sign; + return sdkTransactionToExecParams(encTx, sign, ctx, line); +} + +/* eslint-disable sonarjs/cognitive-complexity */ +export function sdkTransactionToExecParams ( + encTx: EncTx, sign: types.Sign, ctx: Context, line?: number +): types.ExecParams { + const execParams: any = { + ...sign, + payFlags: {} as types.ExecParams + }; + + execParams.payFlags.totalFee = encTx.fee; + if (encTx.close) { + execParams.payFlags.closeRemainderTo = encodeAddress(encTx.close); + } + + switch (encTx.type) { + case 'pay': { + execParams.type = types.TransactionType.TransferAlgo; + execParams.toAccountAddr = + _getRuntimeAccountAddr(encTx.rcv, ctx, line) ?? ZERO_ADDRESS_STR; + execParams.amountMicroAlgos = encTx.amt ?? 0n; + execParams.payFlags.closeRemainderTo = _getRuntimeAccountAddr(encTx.close, ctx, line); + execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); + break; + } + case 'afrz': { + execParams.type = types.TransactionType.FreezeAsset; + execParams.assetID = encTx.faid; + execParams.freezeTarget = _getRuntimeAccountAddr(encTx.fadd, ctx, line); + execParams.freezeState = BigInt(encTx.afrz ?? 0n) === 1n; + execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); + break; + } + case 'axfer': { + if (encTx.asnd !== undefined) { // if 'AssetSender' is set, it is clawback transaction + execParams.type = types.TransactionType.RevokeAsset; + execParams.recipient = + _getRuntimeAccountAddr(encTx.arcv, ctx, line) ?? ZERO_ADDRESS_STR; + execParams.revocationTarget = _getRuntimeAccountAddr(encTx.asnd, ctx, line); + } else { // asset transfer + execParams.type = types.TransactionType.TransferAsset; + execParams.toAccountAddr = + _getRuntimeAccountAddr(encTx.arcv, ctx) ?? ZERO_ADDRESS_STR; + } + // set common fields (asset amount, index, closeRemTo) + execParams.amount = encTx.aamt ?? 0n; + execParams.assetID = encTx.xaid ?? 0; + execParams.payFlags.closeRemainderTo = _getRuntimeAccountAddr(encTx.aclose, ctx, line); + execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); + break; + } + + case 'acfg': { + if (isEncTxAssetDeletion(encTx)) { + execParams.type = types.TransactionType.DestroyAsset; + execParams.assetID = encTx.caid; + } else if (isEncTxAssetConfig(encTx)) { + // from the docs: all fields must be reset, otherwise they will be cleared + // https://developer.algorand.org/docs/get-details/dapps/smart-contracts/apps/#asset-configuration + execParams.type = types.TransactionType.ModifyAsset; + execParams.assetID = encTx.caid; + execParams.fields = { + manager: _getASAConfigAddr(encTx.apar?.m), + reserve: _getASAConfigAddr(encTx.apar?.r), + clawback: _getASAConfigAddr(encTx.apar?.c), + freeze: _getASAConfigAddr(encTx.apar?.f) + }; + } else { // if not delete or modify, it's ASA deployment + execParams.type = types.TransactionType.DeployASA; + execParams.asaName = encTx.apar?.an; + execParams.asaDef = { + name: encTx.apar?.an, + total: Number(encTx.apar?.t), + decimals: encTx.apar?.dc !== undefined ? Number(encTx.apar.dc) : 0, + defaultFrozen: BigInt(encTx.apar?.df ?? 0n) === 1n, + unitName: encTx.apar?.un, + url: encTx.apar?.au, + metadataHash: encTx.apar?.am, + manager: _getASAConfigAddr(encTx.apar?.m), + reserve: _getASAConfigAddr(encTx.apar?.r), + clawback: _getASAConfigAddr(encTx.apar?.c), + freeze: _getASAConfigAddr(encTx.apar?.f) + }; + } + } + }; + return execParams as types.ExecParams; +} + +const _getASAConfigAddr = (addr?: Uint8Array): string => { + if (addr) { + return encodeAddress(addr); + } + return ""; +}; + +const _getRuntimeAccount = (publickey: Buffer | undefined, + ctx: Context, line?: number): RuntimeAccountI | undefined => { + if (publickey === undefined) { return undefined; } + const address = encodeAddress(Uint8Array.from(publickey)); + const runtimeAcc = ctx.getAccount( + address + ); + return runtimeAcc.account; +}; + +const _getRuntimeAccountAddr = (publickey: Buffer | undefined, + ctx: Context, line?: number): AccountAddress | undefined => { + return _getRuntimeAccount(publickey, ctx, line)?.addr; +}; + +const _getAddress = (addr?: Uint8Array): string | undefined => { + if (addr) { return encodeAddress(addr); } + return undefined; +}; diff --git a/packages/runtime/src/runtime.ts b/packages/runtime/src/runtime.ts index 4ccf6c1c8..c7720d6be 100644 --- a/packages/runtime/src/runtime.ts +++ b/packages/runtime/src/runtime.ts @@ -14,6 +14,7 @@ import { ZERO_ADDRESS_STR } from "./lib/constants"; import { convertToString } from "./lib/parsing"; +import { transactionAndSignToExecParams } from "./lib/txn"; import { LogicSigAccount } from "./logicsig"; import { mockSuggestedParams } from "./mock/tx"; import { @@ -767,22 +768,40 @@ export class Runtime { * @param debugStack: if passed then TEAL Stack is logged to console after * each opcode execution (upto depth = debugStack) */ - executeTx (txnParams: types.ExecParams | types.ExecParams[], debugStack?: number): TxReceipt | TxReceipt[] { - const txnParameters = Array.isArray(txnParams) ? txnParams : [txnParams]; - for (const txn of txnParameters) { - switch (txn.type) { - case types.TransactionType.DeployASA: { - if (txn.asaDef === undefined) txn.asaDef = this.loadedAssetsDefs[txn.asaName]; - break; - } - case types.TransactionType.DeployApp: { - txn.approvalProg = new Uint8Array(32); // mock approval program - txn.clearProg = new Uint8Array(32); // mock clear program - break; + executeTx ( + txnParams: types.ExecParams | types.ExecParams[] | types.TransactionAndSign | types.TransactionAndSign[], + debugStack?: number + ): TxReceipt | TxReceipt[] { + const txnParamerters = Array.isArray(txnParams) ? txnParams : [txnParams]; + + let tx, gtxs; + + if (!types.isSDKTransactionAndSign(txnParamerters[0])) { + for (const txnParamerter of txnParamerters) { + const txn = txnParamerter as types.ExecParams; + switch (txn.type) { + case types.TransactionType.DeployASA: { + if (txn.asaDef === undefined) txn.asaDef = this.loadedAssetsDefs[txn.asaName]; + break; + } + case types.TransactionType.DeployApp: { + txn.approvalProg = new Uint8Array(32); // mock approval program + txn.clearProg = new Uint8Array(32); // mock clear program + break; + } } } + // get current txn and txn group (as encoded obj) + [tx, gtxs] = this.createTxnContext(txnParamerters as types.ExecParams[]); + } else { + const sdkTxns: EncTx[] = txnParamerters.map((txnParamerter): EncTx => { + const txn = txnParamerter as types.TransactionAndSign; + return txn.transaction.get_obj_for_encoding() as EncTx; + }); + tx = sdkTxns[0]; + gtxs = sdkTxns; } - const [tx, gtxs] = this.createTxnContext(txnParameters); // get current txn and txn group (as encoded obj) + // validate first and last rounds this.validateTxRound(gtxs); @@ -793,7 +812,11 @@ export class Runtime { // Run TEAL program associated with each transaction and // then execute the transaction without interacting with store. - const txReceipts = this.ctx.processTransactions(txnParameters); + const runtimeTxnParams: types.ExecParams[] = + txnParamerters.map( + (txn) => types.isSDKTransactionAndSign(txn) ? transactionAndSignToExecParams(txn, this.ctx) : txn); + + const txReceipts = this.ctx.processTransactions(runtimeTxnParams); // update store only if all the transactions are passed this.store = this.ctx.state; diff --git a/packages/runtime/test/src/lib/txn.ts b/packages/runtime/test/src/lib/txn.ts new file mode 100644 index 000000000..346b02068 --- /dev/null +++ b/packages/runtime/test/src/lib/txn.ts @@ -0,0 +1,44 @@ +import { types } from "@algo-builder/web"; +import { assert } from "chai"; + +import { AccountStore } from "../../../src"; +import { convertToBuffer } from "../../../src/lib/parsing"; +import { sdkTransactionToExecParams } from "../../../src/lib/txn"; +import { Runtime } from "../../../src/runtime"; +import { AccountStoreI } from "../../../src/types"; +import { useFixture } from "../../helpers/integration"; + +describe("Convert encode Tx to ExecParams", function () { + useFixture("asa-check"); + let john: AccountStoreI; + let runtime: Runtime; + + this.beforeEach(() => { + john = new AccountStore(1e9); + runtime = new Runtime([john]); + }); + + it("test create Asset Program", () => { + const sign: types.Sign = { + sign: types.SignType.SecretKey, + fromAccount: john.account + }; + const execParams: types.DeployASAParam = { + ...sign, + type: types.TransactionType.DeployASA, + asaName: 'gold', + payFlags: { totalFee: 1000 } + }; + + execParams.asaDef = runtime.loadedAssetsDefs[execParams.asaName]; + + const [encTx] = runtime.createTxnContext(execParams); + + // convert metadagaHash to buffer + if (execParams.asaDef?.metadataHash && typeof execParams.asaDef?.metadataHash === 'string') { + execParams.asaDef.metadataHash = convertToBuffer(execParams.asaDef.metadataHash); + } + + assert.deepEqual(sdkTransactionToExecParams(encTx, sign, runtime.ctx), execParams); + }); +}); diff --git a/packages/runtime/test/src/runtime.ts b/packages/runtime/test/src/runtime.ts index b8a0ad67f..e00a4785a 100644 --- a/packages/runtime/test/src/runtime.ts +++ b/packages/runtime/test/src/runtime.ts @@ -1,14 +1,15 @@ import { types } from "@algo-builder/web"; -import { AccountAddress, AlgoTransferParam } from "@algo-builder/web/build/types"; -import algosdk, { LogicSigAccount } from "algosdk"; +import { AlgoTransferParam } from "@algo-builder/web/build/types"; +import algosdk, { LogicSigAccount, makeAssetCreateTxnWithSuggestedParams } from "algosdk"; import { assert } from "chai"; import sinon from "sinon"; import { AccountStore } from "../../src/account"; import { RUNTIME_ERRORS } from "../../src/errors/errors-list"; import { ASSET_CREATION_FEE } from "../../src/lib/constants"; +import { mockSuggestedParams } from "../../src/mock/tx"; import { Runtime } from "../../src/runtime"; -import { AccountStoreI } from "../../src/types"; +import { AccountStoreI, TxReceipt } from "../../src/types"; import { useFixture } from "../helpers/integration"; import { expectRuntimeError } from "../helpers/runtime-errors"; import { elonMuskAccount } from "../mocks/account"; @@ -16,6 +17,52 @@ import { elonMuskAccount } from "../mocks/account"; const programName = "basic.teal"; const minBalance = BigInt(1e7); +describe("Execute SDK transaction", function () { + useFixture("asa-check"); + const fee = 1000; + + let alice: AccountStoreI; + let bob: AccountStoreI; + let alan: AccountStoreI; + + let runtime: Runtime; + + this.beforeEach(() => { + alice = new AccountStore(minBalance * 10n); + bob = new AccountStore(minBalance * 10n); + alan = new AccountStore(minBalance * 10n); + runtime = new Runtime([alice, bob, alan]); + }); + + it("Create ASA txn", () => { + const params = mockSuggestedParams({ totalFee: fee }, runtime.getRound()); + const txn = makeAssetCreateTxnWithSuggestedParams( + alice.address, undefined, 10000, 0, + false, + alice.address, alice.address, alice.address, alice.address, + "GOLD", "goal", + undefined, + undefined, + params, + undefined + ); + + runtime.executeTx({ + transaction: txn, + sign: { + sign: types.SignType.SecretKey, + fromAccount: alice.account + } + }) as TxReceipt; + + const asaDef = runtime.getAssetInfoFromName('goal'); + + assert.isDefined(asaDef); + assert.equal(asaDef?.creator, alice.address); + assert.equal(asaDef?.assetDef.manager, alice.address); + }); +}); + describe("Transfer Algo Transaction", function () { const amount = minBalance; const fee = 1000; From 007c97fc836dd08128f96ae549f0d44f6a188f8d Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Wed, 23 Feb 2022 22:18:41 +0700 Subject: [PATCH 03/18] move convert encode Tx from itxn to txn --- .../runtime/src/interpreter/opcode-list.ts | 24 +-- packages/runtime/src/lib/itxn.ts | 142 +----------------- packages/runtime/src/lib/txn.ts | 16 ++ 3 files changed, 35 insertions(+), 147 deletions(-) diff --git a/packages/runtime/src/interpreter/opcode-list.ts b/packages/runtime/src/interpreter/opcode-list.ts index 249de1195..1dd26b02d 100644 --- a/packages/runtime/src/interpreter/opcode-list.ts +++ b/packages/runtime/src/interpreter/opcode-list.ts @@ -1,6 +1,6 @@ /* eslint sonarjs/no-identical-functions: 0 */ /* eslint sonarjs/no-duplicate-string: 0 */ -import { parsing } from "@algo-builder/web"; +import { parsing, types } from "@algo-builder/web"; import algosdk, { ALGORAND_MIN_TX_FEE, decodeAddress, decodeUint64, encodeAddress, encodeUint64, getApplicationAddress, isValidAddress, modelsv2, verifyBytes } from "algosdk"; import { ec as EC } from "elliptic"; import { Message, sha256 } from "js-sha256"; @@ -19,13 +19,13 @@ import { MAX_UINT64, MAX_UINT128, MaxTEALVersion, TxArrFields, ZERO_ADDRESS } from "../lib/constants"; -import { parseEncodedTxnToExecParams, setInnerTxField } from "../lib/itxn"; +import { setInnerTxField } from "../lib/itxn"; import { assertLen, assertNumber, assertOnlyDigits, bigEndianBytesToBigInt, bigintToBigEndianBytes, convertToBuffer, convertToString, getEncoding, parseBinaryStrToBigInt } from "../lib/parsing"; import { Stack } from "../lib/stack"; -import { txAppArg, txnSpecbyField } from "../lib/txn"; +import { sdkTransactionToExecParams, txAppArg, txnSpecbyField } from "../lib/txn"; import { DecodingMode, EncodingType, StackElem, TEALStack, TxnType, TxOnComplete, TxReceipt } from "../types"; import { Interpreter } from "./interpreter"; import { Op } from "./opcode"; @@ -223,7 +223,7 @@ export class Arg extends Op { execute (stack: TEALStack): void { this.checkIndexBound( - this.index, this.interpreter.runtime.ctx.args as Uint8Array[], this.line); + this.index, this.interpreter.runtime.ctx.args, this.line); const argN = this.assertBytes(this.interpreter.runtime.ctx.args?.[this.index], this.line); stack.push(argN); } @@ -1612,7 +1612,7 @@ export class Global extends Op { } case 'CreatorAddress': { const appID = this.interpreter.runtime.ctx.tx.apid; - const app = this.interpreter.getApp(appID as number, this.line); + const app = this.interpreter.getApp(appID, this.line); result = decodeAddress(app.creator).publicKey; break; } @@ -3922,7 +3922,7 @@ export class ITxnSubmit extends Op { // initial contract account. const appID = this.interpreter.runtime.ctx.tx.apid; - const contractAddress = getApplicationAddress(appID as number); + const contractAddress = getApplicationAddress(appID); const contractAccount = { addr: contractAddress, sk: Buffer.from([]) @@ -3930,8 +3930,14 @@ export class ITxnSubmit extends Op { // get execution txn params (parsed from encoded sdk txn obj) // singer will be contractAccount - const execParams = parseEncodedTxnToExecParams( - contractAccount, this.interpreter.subTxn, this.interpreter, this.line + const execParams = sdkTransactionToExecParams( + this.interpreter.subTxn, + { + sign: types.SignType.SecretKey, + fromAccount: contractAccount + }, + this.interpreter.runtime.ctx, + this.line ); const baseCurrTx = this.interpreter.runtime.ctx.tx; const baseCurrTxGrp = this.interpreter.runtime.ctx.gtxs; @@ -4218,7 +4224,7 @@ export class Log extends Op { this.assertMinStackLen(stack, 1, this.line); const logByte = this.assertBytes(stack.pop(), this.line); const txID = this.interpreter.runtime.ctx.tx.txID; - const txReceipt = this.interpreter.runtime.ctx.state.txReceipts.get(txID) as TxReceipt; + const txReceipt = this.interpreter.runtime.ctx.state.txReceipts.get(txID); if (txReceipt.logs === undefined) { txReceipt.logs = []; } // max no. of logs exceeded diff --git a/packages/runtime/src/lib/itxn.ts b/packages/runtime/src/lib/itxn.ts index d8eabeb8f..3985fba23 100644 --- a/packages/runtime/src/lib/itxn.ts +++ b/packages/runtime/src/lib/itxn.ts @@ -1,15 +1,14 @@ -import { types } from "@algo-builder/web"; -import { Account as AccountSDK, decodeAddress, encodeAddress, getApplicationAddress } from "algosdk"; +import { decodeAddress } from "algosdk"; import cloneDeep from "lodash.clonedeep"; import { Interpreter } from ".."; import { RUNTIME_ERRORS } from "../errors/errors-list"; import { RuntimeError } from "../errors/runtime-errors"; import { Op } from "../interpreter/opcode"; -import { MaxTxnNoteBytes, TxnFields, TxnTypeMap, ZERO_ADDRESS_STR } from "../lib/constants"; -import { AccountAddress, EncTx, RuntimeAccountI, StackElem } from "../types"; +import { MaxTxnNoteBytes, TxnFields, TxnTypeMap } from "../lib/constants"; +import { EncTx, StackElem } from "../types"; import { convertToString } from "./parsing"; -import { assetTxnFields, isEncTxAssetConfig, isEncTxAssetDeletion } from "./txn"; +import { assetTxnFields } from "./txn"; // requires their type as number const numberTxnFields: {[key: number]: Set} = { @@ -257,136 +256,3 @@ export function setInnerTxField ( return subTxn; } - -const _getRuntimeAccount = (publickey: Buffer | undefined, - interpreter: Interpreter, line: number): RuntimeAccountI | undefined => { - if (publickey === undefined) { return undefined; } - const address = encodeAddress(Uint8Array.from(publickey)); - const runtimeAcc = interpreter.runtime.assertAccountDefined( - address, - interpreter.runtime.ctx.state.accounts.get(address), - line - ); - return runtimeAcc.account; -}; - -const _getRuntimeAccountAddr = (publickey: Buffer | undefined, - interpreter: Interpreter, line: number): AccountAddress | undefined => { - return _getRuntimeAccount(publickey, interpreter, line)?.addr; -}; - -const _getASAConfigAddr = (addr?: Uint8Array): string => { - if (addr) { - return encodeAddress(addr); - } - return ""; -}; - -const _getAddress = (addr?: Uint8Array): string | undefined => { - if (addr) { return encodeAddress(addr); } - return undefined; -}; - -// parse encoded txn obj to execParams (params passed by user in algob) -/* eslint-disable sonarjs/cognitive-complexity */ -export function parseEncodedTxnToExecParams (singer: AccountSDK, tx: EncTx, - interpreter: Interpreter, line: number): types.ExecParams { - // initial common fields - const execParams: any = { - sign: types.SignType.SecretKey, - fromAccount: singer, // signer is the contract - fromAccountAddr: encodeAddress(tx.snd), - payFlags: { - totalFee: tx.fee, - firstValid: tx.fv, - note: tx.note - } - }; - - switch (tx.type) { - case 'pay': { - execParams.type = types.TransactionType.TransferAlgo; - execParams.toAccountAddr = - _getRuntimeAccountAddr(tx.rcv, interpreter, line) ?? ZERO_ADDRESS_STR; - execParams.amountMicroAlgos = tx.amt ?? 0n; - execParams.payFlags.closeRemainderTo = _getRuntimeAccountAddr(tx.close, interpreter, line); - execParams.payFlags.rekeyTo = _getAddress(tx.rekey); - break; - } - case 'afrz': { - execParams.type = types.TransactionType.FreezeAsset; - execParams.assetID = tx.faid; - execParams.freezeTarget = _getRuntimeAccountAddr(tx.fadd, interpreter, line); - execParams.freezeState = BigInt(tx.afrz ?? 0n) === 1n; - execParams.payFlags.rekeyTo = _getAddress(tx.rekey); - break; - } - case 'axfer': { - if (tx.asnd !== undefined) { // if 'AssetSender' is set, it is clawback transaction - execParams.type = types.TransactionType.RevokeAsset; - execParams.recipient = - _getRuntimeAccountAddr(tx.arcv, interpreter, line) ?? ZERO_ADDRESS_STR; - execParams.revocationTarget = _getRuntimeAccountAddr(tx.asnd, interpreter, line); - } else { // asset transfer - execParams.type = types.TransactionType.TransferAsset; - execParams.toAccountAddr = - _getRuntimeAccountAddr(tx.arcv, interpreter, line) ?? ZERO_ADDRESS_STR; - } - // set common fields (asset amount, index, closeRemTo) - execParams.amount = tx.aamt ?? 0n; - execParams.assetID = tx.xaid ?? 0; - execParams.payFlags.closeRemainderTo = _getRuntimeAccountAddr(tx.aclose, interpreter, line); - execParams.payFlags.rekeyTo = _getAddress(tx.rekey); - break; - } - case 'acfg': { // can be asset modification, destroy, or deployment(create) - if (isEncTxAssetDeletion(tx)) { - execParams.type = types.TransactionType.DestroyAsset; - execParams.assetID = tx.caid; - } else if (isEncTxAssetConfig(tx)) { - // from the docs: all fields must be reset, otherwise they will be cleared - // https://developer.algorand.org/docs/get-details/dapps/smart-contracts/apps/#asset-configuration - execParams.type = types.TransactionType.ModifyAsset; - execParams.assetID = tx.caid; - execParams.fields = { - manager: _getASAConfigAddr(tx.apar?.m), - reserve: _getASAConfigAddr(tx.apar?.r), - clawback: _getASAConfigAddr(tx.apar?.c), - freeze: _getASAConfigAddr(tx.apar?.f) - }; - } else { // if not delete or modify, it's ASA deployment - execParams.type = types.TransactionType.DeployASA; - execParams.asaName = tx.apar?.an; - execParams.asaDef = { - name: tx.apar?.an, - total: tx.apar?.t, - decimals: tx.apar?.dc !== undefined ? Number(tx.apar.dc) : undefined, - defaultFrozen: BigInt(tx.apar?.df ?? 0n) === 1n, - unitName: tx.apar?.un, - url: tx.apar?.au, - metadataHash: tx.apar?.am, - manager: _getASAConfigAddr(tx.apar?.m), - reserve: _getASAConfigAddr(tx.apar?.r), - clawback: _getASAConfigAddr(tx.apar?.c), - freeze: _getASAConfigAddr(tx.apar?.f) - }; - } - execParams.payFlags.rekeyTo = _getAddress(tx.rekey); - break; - } - - case 'keyreg': - execParams.type = types.TransactionType.KeyRegistration; - execParams.voteKey = tx.votekey; - execParams.selectionKey = tx.selkey; - execParams.voteFirst = tx.votefst; - execParams.voteLast = tx.votelst; - execParams.voteKeyDilution = tx.votekd; - break; - default: { - throw new Error(`unsupported type for itxn_submit at line ${line}, for version ${interpreter.tealVersion}`); - } - } - - return execParams; -} diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index c03411cce..7779477e1 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -218,6 +218,7 @@ export function sdkTransactionToExecParams ( switch (encTx.type) { case 'pay': { execParams.type = types.TransactionType.TransferAlgo; + execParams.fromAccountAddr = _getAddress(encTx.snd); execParams.toAccountAddr = _getRuntimeAccountAddr(encTx.rcv, ctx, line) ?? ZERO_ADDRESS_STR; execParams.amountMicroAlgos = encTx.amt ?? 0n; @@ -284,6 +285,21 @@ export function sdkTransactionToExecParams ( freeze: _getASAConfigAddr(encTx.apar?.f) }; } + break; + } + + case 'keyreg': { + execParams.type = types.TransactionType.KeyRegistration; + execParams.voteKey = encTx.votekey; + execParams.selectionKey = encTx.selkey; + execParams.voteFirst = encTx.votefst; + execParams.voteLast = encTx.votelst; + execParams.voteKeyDilution = encTx.votekd; + break; + } + + default: { + throw new Error(`unsupported type for itxn_submit at line ${line}`); } }; return execParams as types.ExecParams; From d03071e7bca5531a4abd81fd82972c9686ad3720 Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Thu, 24 Feb 2022 00:20:31 +0700 Subject: [PATCH 04/18] support makeApplicationCreationTxn in runtime --- .../runtime/src/interpreter/opcode-list.ts | 8 ++-- packages/runtime/src/lib/txn.ts | 24 ++++++++++- packages/runtime/src/types.ts | 2 + packages/runtime/test/src/runtime.ts | 43 ++++++++++++++----- 4 files changed, 62 insertions(+), 15 deletions(-) diff --git a/packages/runtime/src/interpreter/opcode-list.ts b/packages/runtime/src/interpreter/opcode-list.ts index 1dd26b02d..150ef7084 100644 --- a/packages/runtime/src/interpreter/opcode-list.ts +++ b/packages/runtime/src/interpreter/opcode-list.ts @@ -223,7 +223,7 @@ export class Arg extends Op { execute (stack: TEALStack): void { this.checkIndexBound( - this.index, this.interpreter.runtime.ctx.args, this.line); + this.index, this.interpreter.runtime.ctx.args as Uint8Array[], this.line); const argN = this.assertBytes(this.interpreter.runtime.ctx.args?.[this.index], this.line); stack.push(argN); } @@ -1612,7 +1612,7 @@ export class Global extends Op { } case 'CreatorAddress': { const appID = this.interpreter.runtime.ctx.tx.apid; - const app = this.interpreter.getApp(appID, this.line); + const app = this.interpreter.getApp(appID as number, this.line); result = decodeAddress(app.creator).publicKey; break; } @@ -3921,7 +3921,7 @@ export class ITxnSubmit extends Op { } // initial contract account. - const appID = this.interpreter.runtime.ctx.tx.apid; + const appID = this.interpreter.runtime.ctx.tx.apid as number; const contractAddress = getApplicationAddress(appID); const contractAccount = { addr: contractAddress, @@ -4224,7 +4224,7 @@ export class Log extends Op { this.assertMinStackLen(stack, 1, this.line); const logByte = this.assertBytes(stack.pop(), this.line); const txID = this.interpreter.runtime.ctx.tx.txID; - const txReceipt = this.interpreter.runtime.ctx.state.txReceipts.get(txID); + const txReceipt = this.interpreter.runtime.ctx.state.txReceipts.get(txID) as TxReceipt; if (txReceipt.logs === undefined) { txReceipt.logs = []; } // max no. of logs exceeded diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index 7779477e1..597eb9f02 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -193,10 +193,19 @@ export function isEncTxAssetConfig (txn: EncTx): boolean { !isEncTxAssetDeletion(txn); // AND should not be asset deletion } +export function isEncTxApplicationCreate (txn: EncTx): boolean { + return txn.type === 'appl' && (txn.apan === 0 || txn.apan === undefined); +} + export function transactionAndSignToExecParams ( txAndSign: types.TransactionAndSign, ctx: Context, line?: number ): types.ExecParams { - const encTx = txAndSign.transaction.get_obj_for_encoding() as EncTx; + const transaction = txAndSign.transaction as any; + const encTx = transaction.get_obj_for_encoding() as EncTx; + // inject approval Program and clear program with string format. + // TODO: should create function to convert TEAL in Uint8Array to string format? + encTx.approvalProgram = transaction.approvalProgram; + encTx.clearProgram = transaction.clearProgram; const sign = txAndSign.sign; return sdkTransactionToExecParams(encTx, sign, ctx, line); } @@ -216,6 +225,19 @@ export function sdkTransactionToExecParams ( } switch (encTx.type) { + case 'appl': { + if (isEncTxApplicationCreate(encTx)) { + execParams.type = types.TransactionType.DeployApp; + execParams.approvalProgram = encTx.approvalProgram; + execParams.clearProgram = encTx.clearProgram; + execParams.localInts = encTx.apgs?.nui; + execParams.localBytes = encTx.apgs?.nbs; + execParams.globalInts = encTx.apgs?.nui; + execParams.globalBytes = encTx.apgs?.nbs; + } + break; + } + case 'pay': { execParams.type = types.TransactionType.TransferAlgo; execParams.fromAccountAddr = _getAddress(encTx.snd); diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index ebd983f5b..9e61b3480 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -23,6 +23,8 @@ export type TEALStack = IStack; export interface EncTx extends EncodedTransaction { txID: string + approvalProgram?: string + clearProgram?: string } export type TxField = keyof typeof TxnFields[2]; diff --git a/packages/runtime/test/src/runtime.ts b/packages/runtime/test/src/runtime.ts index e00a4785a..3671dc7fe 100644 --- a/packages/runtime/test/src/runtime.ts +++ b/packages/runtime/test/src/runtime.ts @@ -1,15 +1,16 @@ import { types } from "@algo-builder/web"; import { AlgoTransferParam } from "@algo-builder/web/build/types"; -import algosdk, { LogicSigAccount, makeAssetCreateTxnWithSuggestedParams } from "algosdk"; +import algosdk, { LogicSigAccount, makeApplicationCreateTxn, makeAssetCreateTxnWithSuggestedParams, OnApplicationComplete } from "algosdk"; import { assert } from "chai"; import sinon from "sinon"; +import { getProgram } from "../../src"; import { AccountStore } from "../../src/account"; import { RUNTIME_ERRORS } from "../../src/errors/errors-list"; import { ASSET_CREATION_FEE } from "../../src/lib/constants"; import { mockSuggestedParams } from "../../src/mock/tx"; import { Runtime } from "../../src/runtime"; -import { AccountStoreI, TxReceipt } from "../../src/types"; +import { AccountStoreI, EncTx, TxReceipt } from "../../src/types"; import { useFixture } from "../helpers/integration"; import { expectRuntimeError } from "../helpers/runtime-errors"; import { elonMuskAccount } from "../mocks/account"; @@ -18,20 +19,22 @@ const programName = "basic.teal"; const minBalance = BigInt(1e7); describe("Execute SDK transaction", function () { - useFixture("asa-check"); + useFixture("stateful"); const fee = 1000; let alice: AccountStoreI; - let bob: AccountStoreI; - let alan: AccountStoreI; let runtime: Runtime; + let sign: types.Sign; + this.beforeEach(() => { alice = new AccountStore(minBalance * 10n); - bob = new AccountStore(minBalance * 10n); - alan = new AccountStore(minBalance * 10n); - runtime = new Runtime([alice, bob, alan]); + runtime = new Runtime([alice]); + sign = { + sign: types.SignType.SecretKey, + fromAccount: alice.account + }; }); it("Create ASA txn", () => { @@ -40,7 +43,7 @@ describe("Execute SDK transaction", function () { alice.address, undefined, 10000, 0, false, alice.address, alice.address, alice.address, alice.address, - "GOLD", "goal", + "GOLD", "gold", undefined, undefined, params, @@ -55,12 +58,32 @@ describe("Execute SDK transaction", function () { } }) as TxReceipt; - const asaDef = runtime.getAssetInfoFromName('goal'); + const asaDef = runtime.getAssetInfoFromName('gold'); assert.isDefined(asaDef); assert.equal(asaDef?.creator, alice.address); assert.equal(asaDef?.assetDef.manager, alice.address); }); + + it("Create Application txn", () => { + const params = mockSuggestedParams({ totalFee: fee }, runtime.getRound()); + const txn: any = makeApplicationCreateTxn( + alice.address, params, OnApplicationComplete.NoOpOC, + new Uint8Array(32), new Uint8Array(32), + 1, 1, 1, 1 + ); + + // inject approval program and clear program with string format. + txn.approvalProgram = getProgram('counter-approval.teal'); + txn.clearProgram = getProgram('clear.teal'); + + assert.doesNotThrow( + () => runtime.executeTx({ + transaction: txn, + sign: sign + }) + ); + }); }); describe("Transfer Algo Transaction", function () { From ef3ddd18778e863d77aa7c9ecbc2ae4b0a93a428 Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Thu, 24 Feb 2022 19:54:50 +0700 Subject: [PATCH 05/18] add unit test for function encTxToExecParams --- .../runtime/src/interpreter/opcode-list.ts | 12 +- packages/runtime/src/lib/txn.ts | 37 ++-- packages/runtime/src/runtime.ts | 19 +- packages/runtime/test/src/lib/txn.ts | 186 ++++++++++++++++-- 4 files changed, 205 insertions(+), 49 deletions(-) diff --git a/packages/runtime/src/interpreter/opcode-list.ts b/packages/runtime/src/interpreter/opcode-list.ts index 150ef7084..31c2a0af0 100644 --- a/packages/runtime/src/interpreter/opcode-list.ts +++ b/packages/runtime/src/interpreter/opcode-list.ts @@ -25,7 +25,7 @@ import { convertToString, getEncoding, parseBinaryStrToBigInt } from "../lib/parsing"; import { Stack } from "../lib/stack"; -import { sdkTransactionToExecParams, txAppArg, txnSpecbyField } from "../lib/txn"; +import { encTxToExecParams, txAppArg, txnSpecbyField } from "../lib/txn"; import { DecodingMode, EncodingType, StackElem, TEALStack, TxnType, TxOnComplete, TxReceipt } from "../types"; import { Interpreter } from "./interpreter"; import { Op } from "./opcode"; @@ -223,7 +223,7 @@ export class Arg extends Op { execute (stack: TEALStack): void { this.checkIndexBound( - this.index, this.interpreter.runtime.ctx.args as Uint8Array[], this.line); + this.index, this.interpreter.runtime.ctx.args, this.line); const argN = this.assertBytes(this.interpreter.runtime.ctx.args?.[this.index], this.line); stack.push(argN); } @@ -1612,7 +1612,7 @@ export class Global extends Op { } case 'CreatorAddress': { const appID = this.interpreter.runtime.ctx.tx.apid; - const app = this.interpreter.getApp(appID as number, this.line); + const app = this.interpreter.getApp(appID, this.line); result = decodeAddress(app.creator).publicKey; break; } @@ -3921,7 +3921,7 @@ export class ITxnSubmit extends Op { } // initial contract account. - const appID = this.interpreter.runtime.ctx.tx.apid as number; + const appID = this.interpreter.runtime.ctx.tx.apid; const contractAddress = getApplicationAddress(appID); const contractAccount = { addr: contractAddress, @@ -3930,7 +3930,7 @@ export class ITxnSubmit extends Op { // get execution txn params (parsed from encoded sdk txn obj) // singer will be contractAccount - const execParams = sdkTransactionToExecParams( + const execParams = encTxToExecParams( this.interpreter.subTxn, { sign: types.SignType.SecretKey, @@ -4224,7 +4224,7 @@ export class Log extends Op { this.assertMinStackLen(stack, 1, this.line); const logByte = this.assertBytes(stack.pop(), this.line); const txID = this.interpreter.runtime.ctx.tx.txID; - const txReceipt = this.interpreter.runtime.ctx.state.txReceipts.get(txID) as TxReceipt; + const txReceipt = this.interpreter.runtime.ctx.state.txReceipts.get(txID); if (txReceipt.logs === undefined) { txReceipt.logs = []; } // max no. of logs exceeded diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index 597eb9f02..82baedbd9 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -1,12 +1,13 @@ import { parsing, types } from "@algo-builder/web"; import { AccountAddress } from "@algo-builder/web/build/types"; -import { encodeAddress, EncodedAssetParams, EncodedGlobalStateSchema, Transaction } from "algosdk"; +import algosdk, { encodeAddress, EncodedAssetParams, EncodedGlobalStateSchema, Transaction } from "algosdk"; import { RUNTIME_ERRORS } from "../errors/errors-list"; import { RuntimeError } from "../errors/runtime-errors"; import { Op } from "../interpreter/opcode"; import { TxFieldDefaults, TxnFields, ZERO_ADDRESS_STR } from "../lib/constants"; import { Context, EncTx, RuntimeAccountI, StackElem, TxField, TxnType } from "../types"; +import { convertToString } from "./parsing"; export const assetTxnFields = new Set([ 'ConfigAssetTotal', @@ -207,11 +208,11 @@ export function transactionAndSignToExecParams ( encTx.approvalProgram = transaction.approvalProgram; encTx.clearProgram = transaction.clearProgram; const sign = txAndSign.sign; - return sdkTransactionToExecParams(encTx, sign, ctx, line); + return encTxToExecParams(encTx, sign, ctx, line); } /* eslint-disable sonarjs/cognitive-complexity */ -export function sdkTransactionToExecParams ( +export function encTxToExecParams ( encTx: EncTx, sign: types.Sign, ctx: Context, line?: number ): types.ExecParams { const execParams: any = { @@ -220,9 +221,6 @@ export function sdkTransactionToExecParams ( }; execParams.payFlags.totalFee = encTx.fee; - if (encTx.close) { - execParams.payFlags.closeRemainderTo = encodeAddress(encTx.close); - } switch (encTx.type) { case 'appl': { @@ -244,8 +242,12 @@ export function sdkTransactionToExecParams ( execParams.toAccountAddr = _getRuntimeAccountAddr(encTx.rcv, ctx, line) ?? ZERO_ADDRESS_STR; execParams.amountMicroAlgos = encTx.amt ?? 0n; - execParams.payFlags.closeRemainderTo = _getRuntimeAccountAddr(encTx.close, ctx, line); - execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); + if (encTx.close) { + execParams.payFlags.closeRemainderTo = _getRuntimeAccountAddr(encTx.close, ctx, line); + } + if (encTx.rekey) { + execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); + } break; } case 'afrz': { @@ -253,7 +255,9 @@ export function sdkTransactionToExecParams ( execParams.assetID = encTx.faid; execParams.freezeTarget = _getRuntimeAccountAddr(encTx.fadd, ctx, line); execParams.freezeState = BigInt(encTx.afrz ?? 0n) === 1n; - execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); + if (encTx.rekey) { + execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); + } break; } case 'axfer': { @@ -267,11 +271,16 @@ export function sdkTransactionToExecParams ( execParams.toAccountAddr = _getRuntimeAccountAddr(encTx.arcv, ctx) ?? ZERO_ADDRESS_STR; } - // set common fields (asset amount, index, closeRemTo) + // set common fields (asset amount, index, closeRemainderTo) execParams.amount = encTx.aamt ?? 0n; execParams.assetID = encTx.xaid ?? 0; - execParams.payFlags.closeRemainderTo = _getRuntimeAccountAddr(encTx.aclose, ctx, line); - execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); + // option fields + if (encTx.aclose) { + execParams.payFlags.closeRemainderTo = _getRuntimeAccountAddr(encTx.aclose, ctx, line); + } + if (encTx.rekey) { + execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); + } break; } @@ -312,8 +321,8 @@ export function sdkTransactionToExecParams ( case 'keyreg': { execParams.type = types.TransactionType.KeyRegistration; - execParams.voteKey = encTx.votekey; - execParams.selectionKey = encTx.selkey; + execParams.voteKey = encTx.votekey?.toString('base64'); + execParams.selectionKey = encTx.selkey?.toString('base64'); execParams.voteFirst = encTx.votefst; execParams.voteLast = encTx.votelst; execParams.voteKeyDilution = encTx.votekd; diff --git a/packages/runtime/src/runtime.ts b/packages/runtime/src/runtime.ts index c7720d6be..befa5d304 100644 --- a/packages/runtime/src/runtime.ts +++ b/packages/runtime/src/runtime.ts @@ -776,7 +776,14 @@ export class Runtime { let tx, gtxs; - if (!types.isSDKTransactionAndSign(txnParamerters[0])) { + if (types.isSDKTransactionAndSign(txnParamerters[0])) { + const sdkTxns: EncTx[] = txnParamerters.map((txnParamerter): EncTx => { + const txn = txnParamerter as types.TransactionAndSign; + return txn.transaction.get_obj_for_encoding() as EncTx; + }); + tx = sdkTxns[0]; + gtxs = sdkTxns; + } else { for (const txnParamerter of txnParamerters) { const txn = txnParamerter as types.ExecParams; switch (txn.type) { @@ -793,13 +800,6 @@ export class Runtime { } // get current txn and txn group (as encoded obj) [tx, gtxs] = this.createTxnContext(txnParamerters as types.ExecParams[]); - } else { - const sdkTxns: EncTx[] = txnParamerters.map((txnParamerter): EncTx => { - const txn = txnParamerter as types.TransactionAndSign; - return txn.transaction.get_obj_for_encoding() as EncTx; - }); - tx = sdkTxns[0]; - gtxs = sdkTxns; } // validate first and last rounds @@ -814,7 +814,8 @@ export class Runtime { // then execute the transaction without interacting with store. const runtimeTxnParams: types.ExecParams[] = txnParamerters.map( - (txn) => types.isSDKTransactionAndSign(txn) ? transactionAndSignToExecParams(txn, this.ctx) : txn); + (txn) => types.isSDKTransactionAndSign(txn) ? transactionAndSignToExecParams(txn, this.ctx) : txn + ); const txReceipts = this.ctx.processTransactions(runtimeTxnParams); diff --git a/packages/runtime/test/src/lib/txn.ts b/packages/runtime/test/src/lib/txn.ts index 346b02068..245d6eb8f 100644 --- a/packages/runtime/test/src/lib/txn.ts +++ b/packages/runtime/test/src/lib/txn.ts @@ -1,44 +1,190 @@ import { types } from "@algo-builder/web"; +import { stringToBytes } from "@algo-builder/web/build/lib/parsing"; +import { Account } from "algosdk"; import { assert } from "chai"; +import { encodeBase64 } from "tweetnacl-ts"; import { AccountStore } from "../../../src"; import { convertToBuffer } from "../../../src/lib/parsing"; -import { sdkTransactionToExecParams } from "../../../src/lib/txn"; +import { encTxToExecParams } from "../../../src/lib/txn"; import { Runtime } from "../../../src/runtime"; -import { AccountStoreI } from "../../../src/types"; +import { AccountStoreI, EncTx } from "../../../src/types"; import { useFixture } from "../../helpers/integration"; describe("Convert encode Tx to ExecParams", function () { - useFixture("asa-check"); let john: AccountStoreI; - let runtime: Runtime; + let smith: AccountStoreI; + let runtime: Runtime; + let execParams: types.ExecParams; this.beforeEach(() => { john = new AccountStore(1e9); - runtime = new Runtime([john]); + smith = new AccountStore(1e9); + + runtime = new Runtime([john, smith]); }); - it("test create Asset Program", () => { + // helper - help convert and check param from EncTx to ExecParams + function assertConvertParams (runtime: Runtime, execParams: types.ExecParams): void { + const [encTx] = runtime.createTxnContext(execParams); const sign: types.Sign = { sign: types.SignType.SecretKey, - fromAccount: john.account + fromAccount: execParams.fromAccount }; - const execParams: types.DeployASAParam = { - ...sign, - type: types.TransactionType.DeployASA, - asaName: 'gold', - payFlags: { totalFee: 1000 } - }; - - execParams.asaDef = runtime.loadedAssetsDefs[execParams.asaName]; - - const [encTx] = runtime.createTxnContext(execParams); + if (execParams.type === types.TransactionType.DeployApp) { + encTx.approvalProgram = execParams.approvalProgram; + encTx.clearProgram = execParams.clearProgram; + } - // convert metadagaHash to buffer - if (execParams.asaDef?.metadataHash && typeof execParams.asaDef?.metadataHash === 'string') { + // convert metadataHash to buffer case Deploy ASA, easy to compare. + if (execParams.type === types.TransactionType.DeployASA && + execParams.asaDef?.metadataHash && typeof execParams.asaDef?.metadataHash === 'string' + ) { execParams.asaDef.metadataHash = convertToBuffer(execParams.asaDef.metadataHash); } - assert.deepEqual(sdkTransactionToExecParams(encTx, sign, runtime.ctx), execParams); + assert.deepEqual(encTxToExecParams(encTx, sign, runtime.ctx), execParams); + }; + + describe("pay transaction", function () { + it("convert Encode Tx(pay transaction) to ExecParams(TransferAlgo)", () => { + execParams = { + sign: types.SignType.SecretKey, + fromAccount: john.account, + type: types.TransactionType.TransferAlgo, + fromAccountAddr: john.address, + toAccountAddr: smith.address, + amountMicroAlgos: 1000n, + payFlags: { + totalFee: 1000, + closeRemainderTo: smith.address, + rekeyTo: smith.address + } + }; + + assertConvertParams(runtime, execParams); + }); + }); + + describe("asa Transactions", function () { + useFixture("asa-check"); + it("Deploy ASA", () => { + execParams = { + sign: types.SignType.SecretKey, + fromAccount: john.account, + type: types.TransactionType.DeployASA, + asaName: 'gold', + payFlags: { totalFee: 1000 } + }; + + execParams.asaDef = runtime.loadedAssetsDefs[execParams.asaName]; + + assertConvertParams(runtime, execParams); + }); + + it("Asset Freeze Transaction", () => { + execParams = { + sign: types.SignType.SecretKey, + fromAccount: john.account, + type: types.TransactionType.FreezeAsset, + payFlags: { + totalFee: 1000 + }, + assetID: 7, + freezeTarget: smith.address, + freezeState: true + }; + assertConvertParams(runtime, execParams); + }); + + it("Asset Transfer", () => { + execParams = { + sign: types.SignType.SecretKey, + fromAccount: john.account, + type: types.TransactionType.TransferAsset, + toAccountAddr: smith.address, + amount: 10, + assetID: 10, + payFlags: { + totalFee: 1000 + } + }; + + assertConvertParams(runtime, execParams); + }); + + it("Destroy Asset", () => { + execParams = { + sign: types.SignType.SecretKey, + fromAccount: john.account, + type: types.TransactionType.DestroyAsset, + assetID: 10, + payFlags: { + totalFee: 1000 + } + }; + + assertConvertParams(runtime, execParams); + }); + + it("ModifyAsset", () => { + execParams = { + sign: types.SignType.SecretKey, + fromAccount: john.account, + type: types.TransactionType.ModifyAsset, + assetID: 10, + fields: { + clawback: smith.address, + freeze: smith.address, + manager: john.address, + reserve: smith.address + }, + payFlags: { + totalFee: 1000 + } + }; + + assertConvertParams(runtime, execParams); + }); + }); + + describe("keyreg transaction", function () { + it("EncTx(keyreg tx) to ExecParam", () => { + execParams = { + type: types.TransactionType.KeyRegistration, // payment + sign: types.SignType.SecretKey, + fromAccount: john.account, + voteKey: encodeBase64(stringToBytes('this-is-vote-key')), + selectionKey: encodeBase64(stringToBytes("this-is-selection-key")), + voteFirst: 43, + voteLast: 1000, + voteKeyDilution: 5, + payFlags: { totalFee: 1000 } + }; + + assertConvertParams(runtime, execParams); + }); + }); + + describe("appl transaction", function () { + useFixture('stateful'); + it("EncTx(deploy tx) to ExecParam(deploy Tx)", () => { + execParams = { + sign: types.SignType.SecretKey, + fromAccount: john.account, + type: types.TransactionType.DeployApp, + approvalProgram: "counter-approval.teal", + clearProgram: "clear.teal", + globalBytes: 10, + globalInts: 10, + localBytes: 10, + localInts: 10, + payFlags: { + totalFee: 1000 + } + }; + + assertConvertParams(runtime, execParams); + }); }); }); From 0abc22da9c21f4812f97891478f14f9f95f86294 Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Fri, 25 Feb 2022 00:42:03 +0700 Subject: [PATCH 06/18] add test for execute sdk transaction in runtime --- packages/runtime/src/lib/txn.ts | 2 +- .../runtime/test/integration/SDKTxnRuntime.ts | 148 ++++++++++++++++++ .../test/src/interpreter/inner-transaction.ts | 2 + packages/runtime/test/src/lib/txn.ts | 14 +- packages/runtime/test/src/runtime.ts | 74 +-------- 5 files changed, 157 insertions(+), 83 deletions(-) create mode 100644 packages/runtime/test/integration/SDKTxnRuntime.ts diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index 82baedbd9..1173f5f1f 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -309,7 +309,7 @@ export function encTxToExecParams ( defaultFrozen: BigInt(encTx.apar?.df ?? 0n) === 1n, unitName: encTx.apar?.un, url: encTx.apar?.au, - metadataHash: encTx.apar?.am, + metadataHash: encTx.apar?.am ? convertToString(encTx.apar?.am) : undefined, manager: _getASAConfigAddr(encTx.apar?.m), reserve: _getASAConfigAddr(encTx.apar?.r), clawback: _getASAConfigAddr(encTx.apar?.c), diff --git a/packages/runtime/test/integration/SDKTxnRuntime.ts b/packages/runtime/test/integration/SDKTxnRuntime.ts new file mode 100644 index 000000000..def977e3a --- /dev/null +++ b/packages/runtime/test/integration/SDKTxnRuntime.ts @@ -0,0 +1,148 @@ +import { types } from "@algo-builder/web"; +import { mkTransaction } from "@algo-builder/web/build/lib/txn"; +import { ASADef } from "@algo-builder/web/build/types"; +import algosdk, { makeApplicationCreateTxn, makeAssetCreateTxnWithSuggestedParams, OnApplicationComplete, Transaction } from "algosdk"; +import { assert } from "chai"; + +import { getProgram, loadASAFile } from "../../src"; +import { AccountStore } from "../../src/account"; +import { convertToString } from "../../src/lib/parsing"; +import { mockSuggestedParams } from "../../src/mock/tx"; +import { Runtime } from "../../src/runtime"; +import { AccountStoreI, TxReceipt } from "../../src/types"; +import { useFixture } from "../helpers/integration"; + +const minBalance = BigInt(1e7); + +// TODO: add more test for all transaction types. +describe("Execute SDK transaction", function () { + const fee = 1000; + + let alice: AccountStoreI; + let smith: AccountStoreI; + let runtime: Runtime; + + let execParams: types.ExecParams; + + function mkTransactionAndSign (runtime: Runtime, execParams: types.ExecParams): types.TransactionAndSign { + const suggestedParams = mockSuggestedParams(execParams.payFlags, runtime.getRound()); + const transaction = mkTransaction(execParams, suggestedParams) as any; + let sign: types.Sign; + + // extract `sign` from execParams + if (execParams.sign === types.SignType.SecretKey) { + sign = { + sign: execParams.sign, + fromAccount: execParams.fromAccount + }; + } else { + sign = { + sign: execParams.sign, + lsig: execParams.lsig, + fromAccountAddr: execParams.fromAccountAddr + }; + } + + // inject approval and clear program in string format to transaction object. + // TODO: Should we create disassemble method to convert Uint8Array program format to string??? + if (execParams.type === types.TransactionType.DeployApp) { + transaction.approvalProgram = execParams.approvalProgram; + transaction.clearProgram = execParams.clearProgram; + } + return { + transaction, + sign + }; + } + + this.beforeEach(() => { + alice = new AccountStore(minBalance * 10n); + smith = new AccountStore(minBalance * 10n); + runtime = new Runtime([alice, smith]); + }); + + describe("ASA transaction", function () { + useFixture('asa-check'); + it("Deploy ASA transaction", () => { + const asaName = 'gold'; + const asaDef = runtime.loadedAssetsDefs[asaName]; + + execParams = { + sign: types.SignType.SecretKey, + fromAccount: alice.account, + type: types.TransactionType.DeployASA, + asaName, + asaDef, + payFlags: { + totalFee: fee + } + }; + + const txAndSign = mkTransactionAndSign(runtime, execParams); + assert.doesNotThrow(() => runtime.executeTx(txAndSign)); + + const asaInfo = runtime.getAssetInfoFromName(asaName); + if (asaInfo) { + assert.isDefined(asaInfo); + assert.equal(asaInfo.creator, alice.address); + assert.isFalse(asaInfo.deleted); + assert.equal(asaDef.name, asaInfo.assetDef.name); + assert.equal(asaDef.defaultFrozen, asaInfo.assetDef.defaultFrozen); + assert.equal(asaDef.decimals, asaInfo.assetDef.decimals); + assert.equal(asaDef.total, asaInfo.assetDef.total); + assert.equal(asaDef.clawback, asaInfo.assetDef.clawback); + assert.equal(asaDef.freeze, asaInfo.assetDef.freeze); + assert.equal(asaDef.manager, asaInfo.assetDef.manager); + assert.equal(asaDef.reserve, asaInfo.assetDef.reserve); + } + }); + }); + + describe("Application Transaction", function () { + useFixture('stateful'); + let execParams: types.ExecParams; + this.beforeEach(() => { + execParams = { + type: types.TransactionType.DeployApp, + sign: types.SignType.SecretKey, + fromAccount: alice.account, + approvalProgram: 'counter-approval.teal', + clearProgram: 'clear.teal', + localBytes: 10, + localInts: 10, + globalBytes: 10, + globalInts: 10, + payFlags: { + totalFee: fee + } + }; + + const txAndSign = mkTransactionAndSign(runtime, execParams); + runtime.executeTx(txAndSign); + }); + + it("Check application after deploy", () => { + const appInfo = runtime.getAppInfoFromName('counter-approval.teal', 'clear.teal'); + assert.isDefined(appInfo); + }); + }); + + describe("Payment transaciton", function () { + it("Transfer ALGO Transaction", () => { + execParams = { + sign: types.SignType.SecretKey, + type: types.TransactionType.TransferAlgo, + fromAccount: alice.account, + toAccountAddr: smith.address, + amountMicroAlgos: 1000n, + payFlags: { + totalFee: fee + } + }; + + const txAndSign = mkTransactionAndSign(runtime, execParams); + + assert.doesNotThrow(() => runtime.executeTx(txAndSign)); + }); + }); +}); diff --git a/packages/runtime/test/src/interpreter/inner-transaction.ts b/packages/runtime/test/src/interpreter/inner-transaction.ts index 525d1066b..06d709c15 100644 --- a/packages/runtime/test/src/interpreter/inner-transaction.ts +++ b/packages/runtime/test/src/interpreter/inner-transaction.ts @@ -1036,6 +1036,8 @@ describe("Inner Transactions", function () { itxn_field ConfigAssetName byte "https://gold.rush/" itxn_field ConfigAssetURL + byte "12312442142141241244444411111133" + itxn_field ConfigAssetMetadataHash itxn_submit int 1 `; diff --git a/packages/runtime/test/src/lib/txn.ts b/packages/runtime/test/src/lib/txn.ts index 398ac8bbb..c08581716 100644 --- a/packages/runtime/test/src/lib/txn.ts +++ b/packages/runtime/test/src/lib/txn.ts @@ -27,23 +27,17 @@ describe("Convert encode Tx to ExecParams", function () { // helper - help convert and check param from EncTx to ExecParams function assertConvertParams (runtime: Runtime, execParams: types.ExecParams): void { const [encTx] = runtime.createTxnContext(execParams); - const sign: types.Sign = { + const sign = { sign: types.SignType.SecretKey, - fromAccount: execParams.fromAccount as Account + fromAccount: execParams.fromAccount }; + // add approvalProgram and clearProgram to encTx if (execParams.type === types.TransactionType.DeployApp) { encTx.approvalProgram = execParams.approvalProgram; encTx.clearProgram = execParams.clearProgram; } - // convert metadataHash to buffer case Deploy ASA, easy to compare. - if (execParams.type === types.TransactionType.DeployASA && - execParams.asaDef?.metadataHash && typeof execParams.asaDef?.metadataHash === 'string' - ) { - execParams.asaDef.metadataHash = convertToBuffer(execParams.asaDef.metadataHash); - } - - assert.deepEqual(encTxToExecParams(encTx, sign, runtime.ctx), execParams); + assert.deepEqual(encTxToExecParams(encTx, sign as types.Sign, runtime.ctx), execParams); }; describe("pay transaction", function () { diff --git a/packages/runtime/test/src/runtime.ts b/packages/runtime/test/src/runtime.ts index 30a6d828b..35f02fc9a 100644 --- a/packages/runtime/test/src/runtime.ts +++ b/packages/runtime/test/src/runtime.ts @@ -1,16 +1,14 @@ import { types } from "@algo-builder/web"; import { AlgoTransferParam } from "@algo-builder/web/build/types"; -import algosdk, { LogicSigAccount, makeApplicationCreateTxn, makeAssetCreateTxnWithSuggestedParams, OnApplicationComplete } from "algosdk"; +import algosdk, { LogicSigAccount } from "algosdk"; import { assert } from "chai"; import sinon from "sinon"; -import { getProgram } from "../../src"; import { AccountStore } from "../../src/account"; import { RUNTIME_ERRORS } from "../../src/errors/errors-list"; import { ASSET_CREATION_FEE } from "../../src/lib/constants"; -import { mockSuggestedParams } from "../../src/mock/tx"; import { Runtime } from "../../src/runtime"; -import { AccountStoreI, EncTx, TxReceipt } from "../../src/types"; +import { AccountStoreI } from "../../src/types"; import { useFixture } from "../helpers/integration"; import { expectRuntimeError } from "../helpers/runtime-errors"; import { elonMuskAccount } from "../mocks/account"; @@ -18,74 +16,6 @@ import { elonMuskAccount } from "../mocks/account"; const programName = "basic.teal"; const minBalance = BigInt(1e7); -describe("Execute SDK transaction", function () { - useFixture("stateful"); - const fee = 1000; - - let alice: AccountStoreI; - - let runtime: Runtime; - - let sign: types.Sign; - - this.beforeEach(() => { - alice = new AccountStore(minBalance * 10n); - runtime = new Runtime([alice]); - sign = { - sign: types.SignType.SecretKey, - fromAccount: alice.account - }; - }); - - it("Create ASA txn", () => { - const params = mockSuggestedParams({ totalFee: fee }, runtime.getRound()); - const txn = makeAssetCreateTxnWithSuggestedParams( - alice.address, undefined, 10000, 0, - false, - alice.address, alice.address, alice.address, alice.address, - "GOLD", "gold", - undefined, - undefined, - params, - undefined - ); - - runtime.executeTx({ - transaction: txn, - sign: { - sign: types.SignType.SecretKey, - fromAccount: alice.account - } - }) as TxReceipt; - - const asaDef = runtime.getAssetInfoFromName('gold'); - - assert.isDefined(asaDef); - assert.equal(asaDef?.creator, alice.address); - assert.equal(asaDef?.assetDef.manager, alice.address); - }); - - it("Create Application txn", () => { - const params = mockSuggestedParams({ totalFee: fee }, runtime.getRound()); - const txn: any = makeApplicationCreateTxn( - alice.address, params, OnApplicationComplete.NoOpOC, - new Uint8Array(32), new Uint8Array(32), - 1, 1, 1, 1 - ); - - // inject approval program and clear program with string format. - txn.approvalProgram = getProgram('counter-approval.teal'); - txn.clearProgram = getProgram('clear.teal'); - - assert.doesNotThrow( - () => runtime.executeTx({ - transaction: txn, - sign: sign - }) - ); - }); -}); - describe("Transfer Algo Transaction", function () { const amount = minBalance; const fee = 1000; From 0650e5283ddf92633dab54ba8888034c0bd56cef Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Fri, 25 Feb 2022 01:13:24 +0700 Subject: [PATCH 07/18] make better error messages in runtime/src/lib/txn.ts --- packages/runtime/src/lib/txn.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index 1173f5f1f..391213bfb 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -1,6 +1,6 @@ import { parsing, types } from "@algo-builder/web"; import { AccountAddress } from "@algo-builder/web/build/types"; -import algosdk, { encodeAddress, EncodedAssetParams, EncodedGlobalStateSchema, Transaction } from "algosdk"; +import { encodeAddress, EncodedAssetParams, EncodedGlobalStateSchema, Transaction } from "algosdk"; import { RUNTIME_ERRORS } from "../errors/errors-list"; import { RuntimeError } from "../errors/runtime-errors"; @@ -330,7 +330,13 @@ export function encTxToExecParams ( } default: { - throw new Error(`unsupported type for itxn_submit at line ${line}`); + // if line is defined => called from ItxnSubmit + // => throw error with itxn_submit + if (line) { + throw new Error(`unsupported type for itxn_submit at line ${line}`); + } else { + throw new Error("Can't convert encode tx to execParams"); + } } }; return execParams as types.ExecParams; From 51255c6ee5c365ad5b52a72fcbdda0dd23092d81 Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Fri, 25 Feb 2022 18:06:10 +0700 Subject: [PATCH 08/18] Apply suggestions from code review Co-authored-by: Ratik Jindal --- packages/runtime/test/integration/SDKTxnRuntime.ts | 6 +++--- packages/runtime/test/src/lib/txn.ts | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/runtime/test/integration/SDKTxnRuntime.ts b/packages/runtime/test/integration/SDKTxnRuntime.ts index def977e3a..b4e16cc73 100644 --- a/packages/runtime/test/integration/SDKTxnRuntime.ts +++ b/packages/runtime/test/integration/SDKTxnRuntime.ts @@ -12,10 +12,10 @@ import { Runtime } from "../../src/runtime"; import { AccountStoreI, TxReceipt } from "../../src/types"; import { useFixture } from "../helpers/integration"; -const minBalance = BigInt(1e7); +const minBalance = BigInt(10 * 1e6); // TODO: add more test for all transaction types. -describe("Execute SDK transaction", function () { +describe("Execute SDK transaction object using runtime", function () { const fee = 1000; let alice: AccountStoreI; @@ -121,7 +121,7 @@ describe("Execute SDK transaction", function () { runtime.executeTx(txAndSign); }); - it("Check application after deploy", () => { + it("Check application exists after deployment", () => { const appInfo = runtime.getAppInfoFromName('counter-approval.teal', 'clear.teal'); assert.isDefined(appInfo); }); diff --git a/packages/runtime/test/src/lib/txn.ts b/packages/runtime/test/src/lib/txn.ts index c08581716..8d0f1a13b 100644 --- a/packages/runtime/test/src/lib/txn.ts +++ b/packages/runtime/test/src/lib/txn.ts @@ -11,7 +11,7 @@ import { Runtime } from "../../../src/runtime"; import { AccountStoreI, EncTx } from "../../../src/types"; import { useFixture } from "../../helpers/integration"; -describe("Convert encode Tx to ExecParams", function () { +describe("Convert encoded Txn to ExecParams", function () { let john: AccountStoreI; let smith: AccountStoreI; @@ -72,7 +72,6 @@ describe("Convert encode Tx to ExecParams", function () { }; execParams.asaDef = runtime.loadedAssetsDefs[execParams.asaName]; - assertConvertParams(runtime, execParams); }); From 828643ab5ddd79c38e315365693846065ee3c9ad Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Fri, 25 Feb 2022 20:34:24 +0700 Subject: [PATCH 09/18] use enum string for Txn Type compare --- packages/runtime/src/lib/txn.ts | 13 ++++++------- packages/runtime/src/types.ts | 10 ++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index 391213bfb..25fac7435 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -1,12 +1,11 @@ import { parsing, types } from "@algo-builder/web"; -import { AccountAddress } from "@algo-builder/web/build/types"; import { encodeAddress, EncodedAssetParams, EncodedGlobalStateSchema, Transaction } from "algosdk"; import { RUNTIME_ERRORS } from "../errors/errors-list"; import { RuntimeError } from "../errors/runtime-errors"; import { Op } from "../interpreter/opcode"; import { TxFieldDefaults, TxnFields, ZERO_ADDRESS_STR } from "../lib/constants"; -import { Context, EncTx, RuntimeAccountI, StackElem, TxField, TxnType } from "../types"; +import { Context, EncTx, EncTxnType, RuntimeAccountI, StackElem, TxField, TxnType } from "../types"; import { convertToString } from "./parsing"; export const assetTxnFields = new Set([ @@ -52,7 +51,7 @@ export function parseToStackElem (a: unknown, field: TxField): StackElem { * https://github.com/algorand/js-algorand-sdk/blob/e07d99a2b6bd91c4c19704f107cfca398aeb9619/src/transaction.ts#L528 */ export function checkIfAssetDeletionTx (txn: Transaction): boolean { - return txn.type === 'acfg' && // type should be asset config + return ((txn.type as string) === EncTxnType.acfg) && // type should be asset config (txn.assetIndex > 0) && // assetIndex should not be 0 !(txn.assetClawback || txn.assetFreeze || txn.assetManager || txn.assetReserve); // fields should be empty } @@ -179,7 +178,7 @@ export function txAppArg (txField: TxField, tx: EncTx, idx: number, op: Op, * https://github.com/algorand/js-algorand-sdk/blob/e07d99a2b6bd91c4c19704f107cfca398aeb9619/src/transaction.ts#L528 */ export function isEncTxAssetDeletion (txn: EncTx): boolean { - return txn.type === 'acfg' && // type should be asset config + return txn.type === EncTxnType.acfg && // type should be asset config (txn.caid !== undefined && txn.caid !== 0) && // assetIndex should not be 0 !(txn.apar?.m ?? txn.apar?.r ?? txn.apar?.f ?? txn.apar?.c); // fields should be empty } @@ -189,13 +188,13 @@ export function isEncTxAssetDeletion (txn: EncTx): boolean { * @param txn Encoded EncTx Object */ export function isEncTxAssetConfig (txn: EncTx): boolean { - return txn.type === 'acfg' && // type should be asset config + return txn.type === EncTxnType.acfg && // type should be asset config (txn.caid !== undefined && txn.caid !== 0) && // assetIndex should not be 0 !isEncTxAssetDeletion(txn); // AND should not be asset deletion } export function isEncTxApplicationCreate (txn: EncTx): boolean { - return txn.type === 'appl' && (txn.apan === 0 || txn.apan === undefined); + return txn.type === EncTxnType.appl && (txn.apan === 0 || txn.apan === undefined); } export function transactionAndSignToExecParams ( @@ -360,7 +359,7 @@ const _getRuntimeAccount = (publickey: Buffer | undefined, }; const _getRuntimeAccountAddr = (publickey: Buffer | undefined, - ctx: Context, line?: number): AccountAddress | undefined => { + ctx: Context, line?: number): types.AccountAddress | undefined => { return _getRuntimeAccount(publickey, ctx, line)?.addr; }; diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 9e61b3480..23acb7bd4 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -39,6 +39,16 @@ export enum TxnType { appl = '6' // ApplicationCall } +export enum EncTxnType { + unknown = 'unknow', + pay = 'pay', + keyreg = 'keyreg', + acfg = 'acfg', + axfer = 'axfer', + afrz = 'afrz', + appl = 'appl' +} + export enum GlobalField { MinTxnFee, // micro Algos MinBalance, // micro Algos From 99abc75b21a263c0828ff58b7bc1636d6e0a1a47 Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Fri, 25 Feb 2022 20:42:01 +0700 Subject: [PATCH 10/18] fix import --- .../runtime/test/integration/SDKTxnRuntime.ts | 11 +++------- packages/runtime/test/src/lib/txn.ts | 22 +++++++++---------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/runtime/test/integration/SDKTxnRuntime.ts b/packages/runtime/test/integration/SDKTxnRuntime.ts index def977e3a..22b532525 100644 --- a/packages/runtime/test/integration/SDKTxnRuntime.ts +++ b/packages/runtime/test/integration/SDKTxnRuntime.ts @@ -1,15 +1,10 @@ -import { types } from "@algo-builder/web"; -import { mkTransaction } from "@algo-builder/web/build/lib/txn"; -import { ASADef } from "@algo-builder/web/build/types"; -import algosdk, { makeApplicationCreateTxn, makeAssetCreateTxnWithSuggestedParams, OnApplicationComplete, Transaction } from "algosdk"; +import { tx as webTx, types } from "@algo-builder/web"; import { assert } from "chai"; -import { getProgram, loadASAFile } from "../../src"; import { AccountStore } from "../../src/account"; -import { convertToString } from "../../src/lib/parsing"; import { mockSuggestedParams } from "../../src/mock/tx"; import { Runtime } from "../../src/runtime"; -import { AccountStoreI, TxReceipt } from "../../src/types"; +import { AccountStoreI } from "../../src/types"; import { useFixture } from "../helpers/integration"; const minBalance = BigInt(1e7); @@ -26,7 +21,7 @@ describe("Execute SDK transaction", function () { function mkTransactionAndSign (runtime: Runtime, execParams: types.ExecParams): types.TransactionAndSign { const suggestedParams = mockSuggestedParams(execParams.payFlags, runtime.getRound()); - const transaction = mkTransaction(execParams, suggestedParams) as any; + const transaction = webTx.mkTransaction(execParams, suggestedParams) as any; let sign: types.Sign; // extract `sign` from execParams diff --git a/packages/runtime/test/src/lib/txn.ts b/packages/runtime/test/src/lib/txn.ts index c08581716..40b26d8ab 100644 --- a/packages/runtime/test/src/lib/txn.ts +++ b/packages/runtime/test/src/lib/txn.ts @@ -1,14 +1,12 @@ import { types } from "@algo-builder/web"; import { stringToBytes } from "@algo-builder/web/build/lib/parsing"; -import { Account } from "algosdk"; import { assert } from "chai"; import { encodeBase64 } from "tweetnacl-ts"; import { AccountStore } from "../../../src"; -import { convertToBuffer } from "../../../src/lib/parsing"; import { encTxToExecParams } from "../../../src/lib/txn"; import { Runtime } from "../../../src/runtime"; -import { AccountStoreI, EncTx } from "../../../src/types"; +import { AccountStoreI } from "../../../src/types"; import { useFixture } from "../../helpers/integration"; describe("Convert encode Tx to ExecParams", function () { @@ -25,7 +23,7 @@ describe("Convert encode Tx to ExecParams", function () { }); // helper - help convert and check param from EncTx to ExecParams - function assertConvertParams (runtime: Runtime, execParams: types.ExecParams): void { + function assertEncTxConvertedToExecParam (runtime: Runtime, execParams: types.ExecParams): void { const [encTx] = runtime.createTxnContext(execParams); const sign = { sign: types.SignType.SecretKey, @@ -56,7 +54,7 @@ describe("Convert encode Tx to ExecParams", function () { } }; - assertConvertParams(runtime, execParams); + assertEncTxConvertedToExecParam(runtime, execParams); }); }); @@ -73,7 +71,7 @@ describe("Convert encode Tx to ExecParams", function () { execParams.asaDef = runtime.loadedAssetsDefs[execParams.asaName]; - assertConvertParams(runtime, execParams); + assertEncTxConvertedToExecParam(runtime, execParams); }); it("Asset Freeze Transaction", () => { @@ -88,7 +86,7 @@ describe("Convert encode Tx to ExecParams", function () { freezeTarget: smith.address, freezeState: true }; - assertConvertParams(runtime, execParams); + assertEncTxConvertedToExecParam(runtime, execParams); }); it("Asset Transfer", () => { @@ -104,7 +102,7 @@ describe("Convert encode Tx to ExecParams", function () { } }; - assertConvertParams(runtime, execParams); + assertEncTxConvertedToExecParam(runtime, execParams); }); it("Destroy Asset", () => { @@ -118,7 +116,7 @@ describe("Convert encode Tx to ExecParams", function () { } }; - assertConvertParams(runtime, execParams); + assertEncTxConvertedToExecParam(runtime, execParams); }); it("ModifyAsset", () => { @@ -138,7 +136,7 @@ describe("Convert encode Tx to ExecParams", function () { } }; - assertConvertParams(runtime, execParams); + assertEncTxConvertedToExecParam(runtime, execParams); }); }); @@ -156,7 +154,7 @@ describe("Convert encode Tx to ExecParams", function () { payFlags: { totalFee: 1000 } }; - assertConvertParams(runtime, execParams); + assertEncTxConvertedToExecParam(runtime, execParams); }); }); @@ -178,7 +176,7 @@ describe("Convert encode Tx to ExecParams", function () { } }; - assertConvertParams(runtime, execParams); + assertEncTxConvertedToExecParam(runtime, execParams); }); }); }); From fcbdf3a58bd18d82550321716e0a4515cd9ff212 Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Fri, 25 Feb 2022 21:07:22 +0700 Subject: [PATCH 11/18] fix review --- .../runtime/src/interpreter/opcode-list.ts | 9 +++---- packages/runtime/src/lib/txn.ts | 24 +++++++++++++------ packages/runtime/src/types.ts | 3 +++ .../runtime/test/integration/SDKTxnRuntime.ts | 1 + packages/runtime/test/src/runtime.ts | 5 ++-- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/packages/runtime/src/interpreter/opcode-list.ts b/packages/runtime/src/interpreter/opcode-list.ts index e825944b5..d3977662f 100644 --- a/packages/runtime/src/interpreter/opcode-list.ts +++ b/packages/runtime/src/interpreter/opcode-list.ts @@ -3921,12 +3921,9 @@ export class ITxnSubmit extends Op { } // initial contract account. - const appID = this.interpreter.runtime.ctx.tx.apid; - const contractAddress = getApplicationAddress(appID as number); - const contractAccount = { - addr: contractAddress, - sk: Buffer.from([]) - }; + const appID = this.interpreter.runtime.ctx.tx.apid as number; + const contractAddress = getApplicationAddress(appID); + const contractAccount = this.interpreter.runtime.getAccount(contractAddress).account; // get execution txn params (parsed from encoded sdk txn obj) // singer will be contractAccount diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index 25fac7435..239f01f96 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -193,12 +193,22 @@ export function isEncTxAssetConfig (txn: EncTx): boolean { !isEncTxAssetDeletion(txn); // AND should not be asset deletion } +/** + * Check if given encoded transaction obj is appl creation + * @param txn Encoded EncTx Object + */ export function isEncTxApplicationCreate (txn: EncTx): boolean { return txn.type === EncTxnType.appl && (txn.apan === 0 || txn.apan === undefined); } +/** + * + * @param txAndSign transaction and sign + * @param ctx context which is tx and sign apply + * @returns ExecParams object equivalent with txAndSign + */ export function transactionAndSignToExecParams ( - txAndSign: types.TransactionAndSign, ctx: Context, line?: number + txAndSign: types.TransactionAndSign, ctx: Context ): types.ExecParams { const transaction = txAndSign.transaction as any; const encTx = transaction.get_obj_for_encoding() as EncTx; @@ -207,7 +217,7 @@ export function transactionAndSignToExecParams ( encTx.approvalProgram = transaction.approvalProgram; encTx.clearProgram = transaction.clearProgram; const sign = txAndSign.sign; - return encTxToExecParams(encTx, sign, ctx, line); + return encTxToExecParams(encTx, sign, ctx); } /* eslint-disable sonarjs/cognitive-complexity */ @@ -222,7 +232,7 @@ export function encTxToExecParams ( execParams.payFlags.totalFee = encTx.fee; switch (encTx.type) { - case 'appl': { + case EncTxnType.appl: { if (isEncTxApplicationCreate(encTx)) { execParams.type = types.TransactionType.DeployApp; execParams.approvalProgram = encTx.approvalProgram; @@ -235,7 +245,7 @@ export function encTxToExecParams ( break; } - case 'pay': { + case EncTxnType.pay: { execParams.type = types.TransactionType.TransferAlgo; execParams.fromAccountAddr = _getAddress(encTx.snd); execParams.toAccountAddr = @@ -249,7 +259,7 @@ export function encTxToExecParams ( } break; } - case 'afrz': { + case EncTxnType.afrz: { execParams.type = types.TransactionType.FreezeAsset; execParams.assetID = encTx.faid; execParams.freezeTarget = _getRuntimeAccountAddr(encTx.fadd, ctx, line); @@ -283,7 +293,7 @@ export function encTxToExecParams ( break; } - case 'acfg': { + case EncTxnType.acfg: { if (isEncTxAssetDeletion(encTx)) { execParams.type = types.TransactionType.DestroyAsset; execParams.assetID = encTx.caid; @@ -318,7 +328,7 @@ export function encTxToExecParams ( break; } - case 'keyreg': { + case EncTxnType.keyreg: { execParams.type = types.TransactionType.KeyRegistration; execParams.voteKey = encTx.votekey?.toString('base64'); execParams.selectionKey = encTx.selkey?.toString('base64'); diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 23acb7bd4..40bbbb02b 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -23,8 +23,11 @@ export type TEALStack = IStack; export interface EncTx extends EncodedTransaction { txID: string + // user should push raw string TEAL code - not compiled code + // for approvalProgram and clearProgram approvalProgram?: string clearProgram?: string + } export type TxField = keyof typeof TxnFields[2]; diff --git a/packages/runtime/test/integration/SDKTxnRuntime.ts b/packages/runtime/test/integration/SDKTxnRuntime.ts index 78e61c570..a54275296 100644 --- a/packages/runtime/test/integration/SDKTxnRuntime.ts +++ b/packages/runtime/test/integration/SDKTxnRuntime.ts @@ -10,6 +10,7 @@ import { useFixture } from "../helpers/integration"; const minBalance = BigInt(10 * 1e6); // TODO: add more test for all transaction types. +// https://www.pivotaltracker.com/n/projects/2452320/stories/181383052 describe("Execute SDK transaction object using runtime", function () { const fee = 1000; diff --git a/packages/runtime/test/src/runtime.ts b/packages/runtime/test/src/runtime.ts index 35f02fc9a..79230e491 100644 --- a/packages/runtime/test/src/runtime.ts +++ b/packages/runtime/test/src/runtime.ts @@ -1,5 +1,4 @@ import { types } from "@algo-builder/web"; -import { AlgoTransferParam } from "@algo-builder/web/build/types"; import algosdk, { LogicSigAccount } from "algosdk"; import { assert } from "chai"; import sinon from "sinon"; @@ -134,7 +133,7 @@ describe("Transfer Algo Transaction", function () { this.beforeEach(function () { externalAccount = new AccountStore(0).account; - const transferAlgoTx: AlgoTransferParam = { + const transferAlgoTx: types.AlgoTransferParam = { type: types.TransactionType.TransferAlgo, sign: types.SignType.SecretKey, fromAccount: alice.account, @@ -155,7 +154,7 @@ describe("Transfer Algo Transaction", function () { }); it("Can create transaction from external account", () => { - const transferAlgoTx: AlgoTransferParam = { + const transferAlgoTx: types.AlgoTransferParam = { type: types.TransactionType.TransferAlgo, sign: types.SignType.SecretKey, fromAccount: externalRuntimeAccount.account, From 30f99ec9f69946cf98220cde49df1f1545cd80e1 Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Fri, 25 Feb 2022 22:12:15 +0700 Subject: [PATCH 12/18] update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 653d69b58..9a55f3541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ Added: Changed: - `bond-token-flow` test to also use runtime.defaultAccounts. (see [example](https://github.com/scale-it/algo-builder/blob/develop/examples/bond/test/bond-token-flow.js)) - The `compile.ts` has been updated and now the tealCode is stored in cache when `scTmplParams` are used to compile TEAL with hardcoded params. +- Support execute `transactionAndSign` in Runtime [#601](https://github.com/scale-it/algo-builder/pull/601). + ### API breaking - We have updated the default behaviour of algob deployer for loading data from checkpoint to be queried by "app/lsig" name (note: passing name is required). The existing functionality has been moved to `ByFile` functions (legacy functions based on file querying): From 8283e79b349b77a65c77b90531256698f1dbc203 Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Mon, 28 Feb 2022 13:34:56 +0700 Subject: [PATCH 13/18] update CHANGELOG.md --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f278638f6..f3edfe3bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,11 +23,8 @@ Added: Changed: - `bond-token-flow` test to also use runtime.defaultAccounts. (see [example](https://github.com/scale-it/algo-builder/blob/develop/examples/bond/test/bond-token-flow.js)) - The `compile.ts` has been updated and now the tealCode is stored in cache when `scTmplParams` are used to compile TEAL with hardcoded params. -<<<<<<< HEAD - Support execute `transactionAndSign` in Runtime [#601](https://github.com/scale-it/algo-builder/pull/601). -======= - Added support for checking against opcode their execution mode in runtime. For eg. `arg` can only be run in *signature* mode, and parser will reject the execution if run in application mode. ->>>>>>> develop ### API breaking From 1befdf9d32f1a9f95ea0f0d647cd33b6a18186fe Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Mon, 28 Feb 2022 23:10:39 +0700 Subject: [PATCH 14/18] Apply suggestions from code review Co-authored-by: sczembor <43810037+sczembor@users.noreply.github.com> --- CHANGELOG.md | 2 +- packages/runtime/src/lib/txn.ts | 2 +- packages/runtime/test/integration/SDKTxnRuntime.ts | 6 +++--- packages/runtime/test/src/lib/txn.ts | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3edfe3bd..b9a33f275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ Added: Changed: - `bond-token-flow` test to also use runtime.defaultAccounts. (see [example](https://github.com/scale-it/algo-builder/blob/develop/examples/bond/test/bond-token-flow.js)) - The `compile.ts` has been updated and now the tealCode is stored in cache when `scTmplParams` are used to compile TEAL with hardcoded params. -- Support execute `transactionAndSign` in Runtime [#601](https://github.com/scale-it/algo-builder/pull/601). +- Support execution of `transactionAndSign` in Runtime [#601](https://github.com/scale-it/algo-builder/pull/601). - Added support for checking against opcode their execution mode in runtime. For eg. `arg` can only be run in *signature* mode, and parser will reject the execution if run in application mode. ### API breaking diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index 239f01f96..8d8c45b27 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -194,7 +194,7 @@ export function isEncTxAssetConfig (txn: EncTx): boolean { } /** - * Check if given encoded transaction obj is appl creation + * Check if given encoded transaction object is app creation * @param txn Encoded EncTx Object */ export function isEncTxApplicationCreate (txn: EncTx): boolean { diff --git a/packages/runtime/test/integration/SDKTxnRuntime.ts b/packages/runtime/test/integration/SDKTxnRuntime.ts index a54275296..54f04e893 100644 --- a/packages/runtime/test/integration/SDKTxnRuntime.ts +++ b/packages/runtime/test/integration/SDKTxnRuntime.ts @@ -11,7 +11,7 @@ const minBalance = BigInt(10 * 1e6); // TODO: add more test for all transaction types. // https://www.pivotaltracker.com/n/projects/2452320/stories/181383052 -describe("Execute SDK transaction object using runtime", function () { +describe("Should execute SDK transaction object using runtime", function () { const fee = 1000; let alice: AccountStoreI; @@ -59,7 +59,7 @@ describe("Execute SDK transaction object using runtime", function () { describe("ASA transaction", function () { useFixture('asa-check'); - it("Deploy ASA transaction", () => { + it("Should deploy ASA transaction", () => { const asaName = 'gold'; const asaDef = runtime.loadedAssetsDefs[asaName]; @@ -124,7 +124,7 @@ describe("Execute SDK transaction object using runtime", function () { }); describe("Payment transaciton", function () { - it("Transfer ALGO Transaction", () => { + it("Should transfer ALGO transaction", () => { execParams = { sign: types.SignType.SecretKey, type: types.TransactionType.TransferAlgo, diff --git a/packages/runtime/test/src/lib/txn.ts b/packages/runtime/test/src/lib/txn.ts index a4d5a86f0..d235bbda3 100644 --- a/packages/runtime/test/src/lib/txn.ts +++ b/packages/runtime/test/src/lib/txn.ts @@ -39,7 +39,7 @@ describe("Convert encoded Txn to ExecParams", function () { }; describe("pay transaction", function () { - it("convert Encode Tx(pay transaction) to ExecParams(TransferAlgo)", () => { + it("Should convert Encode Tx(pay transaction) to ExecParams(TransferAlgo)", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -60,7 +60,7 @@ describe("Convert encoded Txn to ExecParams", function () { describe("asa Transactions", function () { useFixture("asa-check"); - it("Deploy ASA", () => { + it("Should deploy ASA", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -74,7 +74,7 @@ describe("Convert encoded Txn to ExecParams", function () { assertEncTxConvertedToExecParam(runtime, execParams); }); - it("Asset Freeze Transaction", () => { + it("Should asset Freeze Transaction", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -89,7 +89,7 @@ describe("Convert encoded Txn to ExecParams", function () { assertEncTxConvertedToExecParam(runtime, execParams); }); - it("Asset Transfer", () => { + it("Should transfer asset", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -105,7 +105,7 @@ describe("Convert encoded Txn to ExecParams", function () { assertEncTxConvertedToExecParam(runtime, execParams); }); - it("Destroy Asset", () => { + it("Should destroy asset", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -119,7 +119,7 @@ describe("Convert encoded Txn to ExecParams", function () { assertEncTxConvertedToExecParam(runtime, execParams); }); - it("ModifyAsset", () => { + it("Should modify asset", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, From a0bffad9deddc47ad85165e3799b1c1e1f4b226f Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Mon, 28 Feb 2022 23:47:43 +0700 Subject: [PATCH 15/18] make test description more clean --- packages/runtime/src/lib/txn.ts | 24 +++++++++---------- .../runtime/test/integration/SDKTxnRuntime.ts | 2 +- packages/runtime/test/src/lib/txn.ts | 24 +++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index 8d8c45b27..18a29a1c5 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -249,10 +249,10 @@ export function encTxToExecParams ( execParams.type = types.TransactionType.TransferAlgo; execParams.fromAccountAddr = _getAddress(encTx.snd); execParams.toAccountAddr = - _getRuntimeAccountAddr(encTx.rcv, ctx, line) ?? ZERO_ADDRESS_STR; + getRuntimeAccountAddr(encTx.rcv, ctx, line) ?? ZERO_ADDRESS_STR; execParams.amountMicroAlgos = encTx.amt ?? 0n; if (encTx.close) { - execParams.payFlags.closeRemainderTo = _getRuntimeAccountAddr(encTx.close, ctx, line); + execParams.payFlags.closeRemainderTo = getRuntimeAccountAddr(encTx.close, ctx, line); } if (encTx.rekey) { execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); @@ -262,7 +262,7 @@ export function encTxToExecParams ( case EncTxnType.afrz: { execParams.type = types.TransactionType.FreezeAsset; execParams.assetID = encTx.faid; - execParams.freezeTarget = _getRuntimeAccountAddr(encTx.fadd, ctx, line); + execParams.freezeTarget = getRuntimeAccountAddr(encTx.fadd, ctx, line); execParams.freezeState = BigInt(encTx.afrz ?? 0n) === 1n; if (encTx.rekey) { execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); @@ -273,19 +273,19 @@ export function encTxToExecParams ( if (encTx.asnd !== undefined) { // if 'AssetSender' is set, it is clawback transaction execParams.type = types.TransactionType.RevokeAsset; execParams.recipient = - _getRuntimeAccountAddr(encTx.arcv, ctx, line) ?? ZERO_ADDRESS_STR; - execParams.revocationTarget = _getRuntimeAccountAddr(encTx.asnd, ctx, line); + getRuntimeAccountAddr(encTx.arcv, ctx, line) ?? ZERO_ADDRESS_STR; + execParams.revocationTarget = getRuntimeAccountAddr(encTx.asnd, ctx, line); } else { // asset transfer execParams.type = types.TransactionType.TransferAsset; execParams.toAccountAddr = - _getRuntimeAccountAddr(encTx.arcv, ctx) ?? ZERO_ADDRESS_STR; + getRuntimeAccountAddr(encTx.arcv, ctx) ?? ZERO_ADDRESS_STR; } // set common fields (asset amount, index, closeRemainderTo) execParams.amount = encTx.aamt ?? 0n; execParams.assetID = encTx.xaid ?? 0; // option fields if (encTx.aclose) { - execParams.payFlags.closeRemainderTo = _getRuntimeAccountAddr(encTx.aclose, ctx, line); + execParams.payFlags.closeRemainderTo = getRuntimeAccountAddr(encTx.aclose, ctx, line); } if (encTx.rekey) { execParams.payFlags.rekeyTo = _getAddress(encTx.rekey); @@ -358,19 +358,19 @@ const _getASAConfigAddr = (addr?: Uint8Array): string => { return ""; }; -const _getRuntimeAccount = (publickey: Buffer | undefined, +const getRuntimeAccount = (publicKey: Buffer | undefined, ctx: Context, line?: number): RuntimeAccountI | undefined => { - if (publickey === undefined) { return undefined; } - const address = encodeAddress(Uint8Array.from(publickey)); + if (publicKey === undefined) { return undefined; } + const address = encodeAddress(Uint8Array.from(publicKey)); const runtimeAcc = ctx.getAccount( address ); return runtimeAcc.account; }; -const _getRuntimeAccountAddr = (publickey: Buffer | undefined, +const getRuntimeAccountAddr = (publickey: Buffer | undefined, ctx: Context, line?: number): types.AccountAddress | undefined => { - return _getRuntimeAccount(publickey, ctx, line)?.addr; + return getRuntimeAccount(publickey, ctx, line)?.addr; }; const _getAddress = (addr?: Uint8Array): string | undefined => { diff --git a/packages/runtime/test/integration/SDKTxnRuntime.ts b/packages/runtime/test/integration/SDKTxnRuntime.ts index 54f04e893..30d4fa1b5 100644 --- a/packages/runtime/test/integration/SDKTxnRuntime.ts +++ b/packages/runtime/test/integration/SDKTxnRuntime.ts @@ -117,7 +117,7 @@ describe("Should execute SDK transaction object using runtime", function () { runtime.executeTx(txAndSign); }); - it("Check application exists after deployment", () => { + it("Should check if application exists after deployment", () => { const appInfo = runtime.getAppInfoFromName('counter-approval.teal', 'clear.teal'); assert.isDefined(appInfo); }); diff --git a/packages/runtime/test/src/lib/txn.ts b/packages/runtime/test/src/lib/txn.ts index d235bbda3..d7f834857 100644 --- a/packages/runtime/test/src/lib/txn.ts +++ b/packages/runtime/test/src/lib/txn.ts @@ -38,8 +38,8 @@ describe("Convert encoded Txn to ExecParams", function () { assert.deepEqual(encTxToExecParams(encTx, sign as types.Sign, runtime.ctx), execParams); }; - describe("pay transaction", function () { - it("Should convert Encode Tx(pay transaction) to ExecParams(TransferAlgo)", () => { + describe("Case pay transaction types", function () { + it("Should convert SDK Payment Txn(pay) to ExecParams(TransferAlgo)", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -58,9 +58,9 @@ describe("Convert encoded Txn to ExecParams", function () { }); }); - describe("asa Transactions", function () { + describe("Case acfg,axfer,afrz transaction types", function () { useFixture("asa-check"); - it("Should deploy ASA", () => { + it("Should convert SDK Deploy ASA Txn to ExecParams(DeployASA)", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -74,7 +74,7 @@ describe("Convert encoded Txn to ExecParams", function () { assertEncTxConvertedToExecParam(runtime, execParams); }); - it("Should asset Freeze Transaction", () => { + it("Should convert SDK FreezeAsset ASA Txn to ExecParams(FreezeAsset)", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -89,7 +89,7 @@ describe("Convert encoded Txn to ExecParams", function () { assertEncTxConvertedToExecParam(runtime, execParams); }); - it("Should transfer asset", () => { + it("Should convert SDK Transfer ASA Txn to ExecParams(TransferAsset)", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -105,7 +105,7 @@ describe("Convert encoded Txn to ExecParams", function () { assertEncTxConvertedToExecParam(runtime, execParams); }); - it("Should destroy asset", () => { + it("Should convert SDK Destroy ASA Txn to ExecParams(DestroyAsset)", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -119,7 +119,7 @@ describe("Convert encoded Txn to ExecParams", function () { assertEncTxConvertedToExecParam(runtime, execParams); }); - it("Should modify asset", () => { + it("Should convert SDK Modify ASA Txn to ExecParams(ModifyAsset)", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, @@ -140,8 +140,8 @@ describe("Convert encoded Txn to ExecParams", function () { }); }); - describe("keyreg transaction", function () { - it("EncTx(keyreg tx) to ExecParam", () => { + describe("Case keyreg transaction type", function () { + it("should convert SDK Keyreg Txn to ExecParams(KeyRegistration)", () => { execParams = { type: types.TransactionType.KeyRegistration, // payment sign: types.SignType.SecretKey, @@ -158,9 +158,9 @@ describe("Convert encoded Txn to ExecParams", function () { }); }); - describe("appl transaction", function () { + describe("Case appl transaction type", function () { useFixture('stateful'); - it("EncTx(deploy tx) to ExecParam(deploy Tx)", () => { + it("should convert SDK Deploy Application Txn to ExecParams(DeployApp)", () => { execParams = { sign: types.SignType.SecretKey, fromAccount: john.account, From 84c487539ac1fac92dee580e2cf3514bfaa6bb63 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Tue, 1 Mar 2022 10:29:24 +0100 Subject: [PATCH 16/18] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a33f275..240c49de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ Added: Changed: - `bond-token-flow` test to also use runtime.defaultAccounts. (see [example](https://github.com/scale-it/algo-builder/blob/develop/examples/bond/test/bond-token-flow.js)) - The `compile.ts` has been updated and now the tealCode is stored in cache when `scTmplParams` are used to compile TEAL with hardcoded params. -- Support execution of `transactionAndSign` in Runtime [#601](https://github.com/scale-it/algo-builder/pull/601). +- Support execution of algo-sdk-js `transactionAndSign` in Runtime [#601](https://github.com/scale-it/algo-builder/pull/601). - Added support for checking against opcode their execution mode in runtime. For eg. `arg` can only be run in *signature* mode, and parser will reject the execution if run in application mode. ### API breaking From 0cc6920c9f77b934dc5912de3950ceae313f725f Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Tue, 1 Mar 2022 21:25:04 +0700 Subject: [PATCH 17/18] use TransactionTypeEnum to compare type transaction --- packages/runtime/src/lib/txn.ts | 24 ++++++++++++------------ packages/runtime/src/types.ts | 10 ---------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/packages/runtime/src/lib/txn.ts b/packages/runtime/src/lib/txn.ts index 18a29a1c5..cab564005 100644 --- a/packages/runtime/src/lib/txn.ts +++ b/packages/runtime/src/lib/txn.ts @@ -4,8 +4,8 @@ import { encodeAddress, EncodedAssetParams, EncodedGlobalStateSchema, Transactio import { RUNTIME_ERRORS } from "../errors/errors-list"; import { RuntimeError } from "../errors/runtime-errors"; import { Op } from "../interpreter/opcode"; -import { TxFieldDefaults, TxnFields, ZERO_ADDRESS_STR } from "../lib/constants"; -import { Context, EncTx, EncTxnType, RuntimeAccountI, StackElem, TxField, TxnType } from "../types"; +import { TransactionTypeEnum, TxFieldDefaults, TxnFields, ZERO_ADDRESS_STR } from "../lib/constants"; +import { Context, EncTx, RuntimeAccountI, StackElem, TxField, TxnType } from "../types"; import { convertToString } from "./parsing"; export const assetTxnFields = new Set([ @@ -51,7 +51,7 @@ export function parseToStackElem (a: unknown, field: TxField): StackElem { * https://github.com/algorand/js-algorand-sdk/blob/e07d99a2b6bd91c4c19704f107cfca398aeb9619/src/transaction.ts#L528 */ export function checkIfAssetDeletionTx (txn: Transaction): boolean { - return ((txn.type as string) === EncTxnType.acfg) && // type should be asset config + return (String(txn.type) === TransactionTypeEnum.ASSET_CONFIG) && // type should be asset config (txn.assetIndex > 0) && // assetIndex should not be 0 !(txn.assetClawback || txn.assetFreeze || txn.assetManager || txn.assetReserve); // fields should be empty } @@ -178,7 +178,7 @@ export function txAppArg (txField: TxField, tx: EncTx, idx: number, op: Op, * https://github.com/algorand/js-algorand-sdk/blob/e07d99a2b6bd91c4c19704f107cfca398aeb9619/src/transaction.ts#L528 */ export function isEncTxAssetDeletion (txn: EncTx): boolean { - return txn.type === EncTxnType.acfg && // type should be asset config + return txn.type === TransactionTypeEnum.ASSET_CONFIG && // type should be asset config (txn.caid !== undefined && txn.caid !== 0) && // assetIndex should not be 0 !(txn.apar?.m ?? txn.apar?.r ?? txn.apar?.f ?? txn.apar?.c); // fields should be empty } @@ -188,7 +188,7 @@ export function isEncTxAssetDeletion (txn: EncTx): boolean { * @param txn Encoded EncTx Object */ export function isEncTxAssetConfig (txn: EncTx): boolean { - return txn.type === EncTxnType.acfg && // type should be asset config + return txn.type === TransactionTypeEnum.ASSET_CONFIG && // type should be asset config (txn.caid !== undefined && txn.caid !== 0) && // assetIndex should not be 0 !isEncTxAssetDeletion(txn); // AND should not be asset deletion } @@ -198,7 +198,7 @@ export function isEncTxAssetConfig (txn: EncTx): boolean { * @param txn Encoded EncTx Object */ export function isEncTxApplicationCreate (txn: EncTx): boolean { - return txn.type === EncTxnType.appl && (txn.apan === 0 || txn.apan === undefined); + return txn.type === TransactionTypeEnum.APPLICATION_CALL && (txn.apan === 0 || txn.apan === undefined); } /** @@ -232,7 +232,7 @@ export function encTxToExecParams ( execParams.payFlags.totalFee = encTx.fee; switch (encTx.type) { - case EncTxnType.appl: { + case TransactionTypeEnum.APPLICATION_CALL: { if (isEncTxApplicationCreate(encTx)) { execParams.type = types.TransactionType.DeployApp; execParams.approvalProgram = encTx.approvalProgram; @@ -245,7 +245,7 @@ export function encTxToExecParams ( break; } - case EncTxnType.pay: { + case TransactionTypeEnum.PAYMENT: { execParams.type = types.TransactionType.TransferAlgo; execParams.fromAccountAddr = _getAddress(encTx.snd); execParams.toAccountAddr = @@ -259,7 +259,7 @@ export function encTxToExecParams ( } break; } - case EncTxnType.afrz: { + case TransactionTypeEnum.ASSET_FREEZE: { execParams.type = types.TransactionType.FreezeAsset; execParams.assetID = encTx.faid; execParams.freezeTarget = getRuntimeAccountAddr(encTx.fadd, ctx, line); @@ -269,7 +269,7 @@ export function encTxToExecParams ( } break; } - case 'axfer': { + case TransactionTypeEnum.ASSET_TRANSFER: { if (encTx.asnd !== undefined) { // if 'AssetSender' is set, it is clawback transaction execParams.type = types.TransactionType.RevokeAsset; execParams.recipient = @@ -293,7 +293,7 @@ export function encTxToExecParams ( break; } - case EncTxnType.acfg: { + case TransactionTypeEnum.ASSET_CONFIG: { if (isEncTxAssetDeletion(encTx)) { execParams.type = types.TransactionType.DestroyAsset; execParams.assetID = encTx.caid; @@ -328,7 +328,7 @@ export function encTxToExecParams ( break; } - case EncTxnType.keyreg: { + case TransactionTypeEnum.KEY_REGISTRATION: { execParams.type = types.TransactionType.KeyRegistration; execParams.voteKey = encTx.votekey?.toString('base64'); execParams.selectionKey = encTx.selkey?.toString('base64'); diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 40bbbb02b..33a9cf0d0 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -42,16 +42,6 @@ export enum TxnType { appl = '6' // ApplicationCall } -export enum EncTxnType { - unknown = 'unknow', - pay = 'pay', - keyreg = 'keyreg', - acfg = 'acfg', - axfer = 'axfer', - afrz = 'afrz', - appl = 'appl' -} - export enum GlobalField { MinTxnFee, // micro Algos MinBalance, // micro Algos From c57acdeaf9d704a35780cb6e730d384384152876 Mon Sep 17 00:00:00 2001 From: Vu Vo Date: Tue, 1 Mar 2022 21:29:05 +0700 Subject: [PATCH 18/18] add TODO --- packages/runtime/src/runtime.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/runtime/src/runtime.ts b/packages/runtime/src/runtime.ts index 5b7a8880a..945cb22d6 100644 --- a/packages/runtime/src/runtime.ts +++ b/packages/runtime/src/runtime.ts @@ -812,6 +812,8 @@ export class Runtime { txnParams: types.ExecParams | types.ExecParams[] | types.TransactionAndSign | types.TransactionAndSign[], debugStack?: number ): TxReceipt | TxReceipt[] { + // TODO: union above and create new type in task below: + // https://www.pivotaltracker.com/n/projects/2452320/stories/181295625 const txnParamerters = Array.isArray(txnParams) ? txnParams : [txnParams]; let tx, gtxs;