Skip to content

Commit

Permalink
feat: handle sdk transaction in runtime (#601)
Browse files Browse the repository at this point in the history
* add signer field to parseEncodedTxnToExecParams

* support algorand sdk makeAssetCreateTxn transaction in runtime

* move convert encode Tx from itxn to txn

* support makeApplicationCreationTxn in runtime

* add unit test for function encTxToExecParams

* add test for execute sdk transaction in runtime

* make better error messages in runtime/src/lib/txn.ts

* Apply suggestions from code review

Co-authored-by: Ratik Jindal <[email protected]>

* use enum string for Txn Type compare

* fix import

* fix review

* update CHANGELOG.md

* update CHANGELOG.md

* Apply suggestions from code review

Co-authored-by: sczembor <[email protected]>

* make test description more clean

* Update CHANGELOG.md

* use TransactionTypeEnum to compare type transaction

* add TODO

Co-authored-by: Ratik Jindal <[email protected]>
Co-authored-by: sczembor <[email protected]>
Co-authored-by: Robert Zaremba <[email protected]>
  • Loading branch information
4 people authored Mar 1, 2022
1 parent f4aca43 commit fa15fb1
Show file tree
Hide file tree
Showing 10 changed files with 593 additions and 172 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +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 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.
- Support RekeyTo field in the inner transaction for TEAL v6.
- Support `keyreg` transaction in inner transaction.
Expand Down
26 changes: 20 additions & 6 deletions packages/runtime/src/interpreter/opcode-list.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 { encTxToExecParams, txAppArg, txnSpecbyField } from "../lib/txn";
import { DecodingMode, EncodingType, StackElem, TEALStack, TxnType, TxOnComplete, TxReceipt } from "../types";
import { Interpreter } from "./interpreter";
import { Op } from "./opcode";
Expand Down Expand Up @@ -1616,8 +1616,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;
}
Expand Down Expand Up @@ -3925,8 +3925,22 @@ export class ITxnSubmit extends Op {
);
}

// initial contract account.
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)
const execParams = parseEncodedTxnToExecParams(this.interpreter.subTxn, this.interpreter, this.line);
// singer will be contractAccount
const execParams = encTxToExecParams(
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;

Expand Down
146 changes: 4 additions & 142 deletions packages/runtime/src/lib/itxn.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { types } from "@algo-builder/web";
import { 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<string>} = {
Expand Down Expand Up @@ -257,140 +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 (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
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;
}
Loading

0 comments on commit fa15fb1

Please sign in to comment.