Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: handle sdk transaction in runtime #601

Merged
merged 23 commits into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
93acd4f
add signer field to parseEncodedTxnToExecParams
vuvoth Feb 18, 2022
2fbb1ab
Merge branch 'develop' into handle-sdk-transaction-in-runtime
vuvoth Feb 21, 2022
77157ff
support algorand sdk makeAssetCreateTxn transaction in runtime
vuvoth Feb 23, 2022
007c97f
move convert encode Tx from itxn to txn
vuvoth Feb 23, 2022
d03071e
support makeApplicationCreationTxn in runtime
vuvoth Feb 23, 2022
ef3ddd1
add unit test for function encTxToExecParams
vuvoth Feb 24, 2022
fe615e9
Merge branch 'develop' into handle-sdk-transaction-in-runtime
vuvoth Feb 24, 2022
0abc22d
add test for execute sdk transaction in runtime
vuvoth Feb 24, 2022
0650e52
make better error messages in runtime/src/lib/txn.ts
vuvoth Feb 24, 2022
51255c6
Apply suggestions from code review
vuvoth Feb 25, 2022
828643a
use enum string for Txn Type compare
vuvoth Feb 25, 2022
99abc75
fix import
vuvoth Feb 25, 2022
bc91ff7
sync with remote branch
vuvoth Feb 25, 2022
fcbdf3a
fix review
vuvoth Feb 25, 2022
30f99ec
update CHANGELOG.md
vuvoth Feb 25, 2022
6efa4c1
chore:merge develop branch
vuvoth Feb 28, 2022
8283e79
update CHANGELOG.md
vuvoth Feb 28, 2022
1befdf9
Apply suggestions from code review
vuvoth Feb 28, 2022
a0bffad
make test description more clean
vuvoth Feb 28, 2022
84c4875
Update CHANGELOG.md
robert-zaremba Mar 1, 2022
0cc6920
use TransactionTypeEnum to compare type transaction
vuvoth Mar 1, 2022
c57acde
add TODO
vuvoth Mar 1, 2022
ec5ab8a
Merge branch 'develop' into handle-sdk-transaction-in-runtime
vuvoth Mar 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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