From 21853e228ab808fc10fad2760a62d2196d2dbbfd Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 24 Aug 2023 09:19:56 -0400 Subject: [PATCH 1/9] generated code --- src/client/v2/algod/models/types.ts | 582 +++++++++++++++++++++++++++- 1 file changed, 580 insertions(+), 2 deletions(-) diff --git a/src/client/v2/algod/models/types.ts b/src/client/v2/algod/models/types.ts index e2c8e50dc..c53554930 100644 --- a/src/client/v2/algod/models/types.ts +++ b/src/client/v2/algod/models/types.ts @@ -784,6 +784,53 @@ export class Application extends BaseModel { } } +/** + * References an account's local state for an application. + */ +export class ApplicationLocalReference extends BaseModel { + /** + * Address of the account with the local state. + */ + public account: string; + + /** + * Application ID of the local state application. + */ + public app: number | bigint; + + /** + * Creates a new `ApplicationLocalReference` object. + * @param account - Address of the account with the local state. + * @param app - Application ID of the local state application. + */ + constructor({ account, app }: { account: string; app: number | bigint }) { + super(); + this.account = account; + this.app = app; + + this.attribute_map = { + account: 'account', + app: 'app', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding( + data: Record + ): ApplicationLocalReference { + /* eslint-disable dot-notation */ + if (typeof data['account'] === 'undefined') + throw new Error(`Response is missing required field 'account': ${data}`); + if (typeof data['app'] === 'undefined') + throw new Error(`Response is missing required field 'app': ${data}`); + return new ApplicationLocalReference({ + account: data['account'], + app: data['app'], + }); + /* eslint-enable dot-notation */ + } +} + /** * Stores local state associated with an application. */ @@ -984,6 +1031,108 @@ export class ApplicationParams extends BaseModel { } } +/** + * An operation against an application's global/local/box state. + */ +export class ApplicationStateOperation extends BaseModel { + /** + * Type of application state. Value `g` is **global state**, `l` is **local + * state**, `b` is **boxes**. + */ + public appStateType: string; + + /** + * The key (name) of the global/local/box state. + */ + public key: Uint8Array; + + /** + * Operation type. Value `w` is **write**, `d` is **delete**. + */ + public operation: string; + + /** + * For local state changes, the address of the account associated with the local + * state. + */ + public account?: string; + + /** + * Represents an AVM value. + */ + public newValue?: AvmValue; + + /** + * Creates a new `ApplicationStateOperation` object. + * @param appStateType - Type of application state. Value `g` is **global state**, `l` is **local + * state**, `b` is **boxes**. + * @param key - The key (name) of the global/local/box state. + * @param operation - Operation type. Value `w` is **write**, `d` is **delete**. + * @param account - For local state changes, the address of the account associated with the local + * state. + * @param newValue - Represents an AVM value. + */ + constructor({ + appStateType, + key, + operation, + account, + newValue, + }: { + appStateType: string; + key: string | Uint8Array; + operation: string; + account?: string; + newValue?: AvmValue; + }) { + super(); + this.appStateType = appStateType; + this.key = + typeof key === 'string' + ? new Uint8Array(Buffer.from(key, 'base64')) + : key; + this.operation = operation; + this.account = account; + this.newValue = newValue; + + this.attribute_map = { + appStateType: 'app-state-type', + key: 'key', + operation: 'operation', + account: 'account', + newValue: 'new-value', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding( + data: Record + ): ApplicationStateOperation { + /* eslint-disable dot-notation */ + if (typeof data['app-state-type'] === 'undefined') + throw new Error( + `Response is missing required field 'app-state-type': ${data}` + ); + if (typeof data['key'] === 'undefined') + throw new Error(`Response is missing required field 'key': ${data}`); + if (typeof data['operation'] === 'undefined') + throw new Error( + `Response is missing required field 'operation': ${data}` + ); + return new ApplicationStateOperation({ + appStateType: data['app-state-type'], + key: data['key'], + operation: data['operation'], + account: data['account'], + newValue: + typeof data['new-value'] !== 'undefined' + ? AvmValue.from_obj_for_encoding(data['new-value']) + : undefined, + }); + /* eslint-enable dot-notation */ + } +} + /** * Specifies maximums on the number of each type that may be stored. */ @@ -1164,6 +1313,53 @@ export class AssetHolding extends BaseModel { } } +/** + * References an asset held by an account. + */ +export class AssetHoldingReference extends BaseModel { + /** + * Address of the account holding the asset. + */ + public account: string; + + /** + * Asset ID of the holding. + */ + public asset: number | bigint; + + /** + * Creates a new `AssetHoldingReference` object. + * @param account - Address of the account holding the asset. + * @param asset - Asset ID of the holding. + */ + constructor({ account, asset }: { account: string; asset: number | bigint }) { + super(); + this.account = account; + this.asset = asset; + + this.attribute_map = { + account: 'account', + asset: 'asset', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding( + data: Record + ): AssetHoldingReference { + /* eslint-disable dot-notation */ + if (typeof data['account'] === 'undefined') + throw new Error(`Response is missing required field 'account': ${data}`); + if (typeof data['asset'] === 'undefined') + throw new Error(`Response is missing required field 'asset': ${data}`); + return new AssetHoldingReference({ + account: data['account'], + asset: data['asset'], + }); + /* eslint-enable dot-notation */ + } +} + /** * AssetParams specifies the parameters for an asset. * (apar) when part of an AssetConfig transaction. @@ -1547,6 +1743,42 @@ export class BlockResponse extends BaseModel { } } +/** + * Top level transaction IDs in a block. + */ +export class BlockTxidsResponse extends BaseModel { + /** + * Block transaction IDs. + */ + public blocktxids: string[]; + + /** + * Creates a new `BlockTxidsResponse` object. + * @param blocktxids - Block transaction IDs. + */ + constructor({ blocktxids }: { blocktxids: string[] }) { + super(); + this.blocktxids = blocktxids; + + this.attribute_map = { + blocktxids: 'blockTxids', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(data: Record): BlockTxidsResponse { + /* eslint-disable dot-notation */ + if (!Array.isArray(data['blockTxids'])) + throw new Error( + `Response is missing required array field 'blockTxids': ${data}` + ); + return new BlockTxidsResponse({ + blocktxids: data['blockTxids'], + }); + /* eslint-enable dot-notation */ + } +} + /** * Box name and its content. */ @@ -1654,6 +1886,60 @@ export class BoxDescriptor extends BaseModel { } } +/** + * References a box of an application. + */ +export class BoxReference extends BaseModel { + /** + * Application ID which this box belongs to + */ + public app: number | bigint; + + /** + * Base64 encoded box name + */ + public name: Uint8Array; + + /** + * Creates a new `BoxReference` object. + * @param app - Application ID which this box belongs to + * @param name - Base64 encoded box name + */ + constructor({ + app, + name, + }: { + app: number | bigint; + name: string | Uint8Array; + }) { + super(); + this.app = app; + this.name = + typeof name === 'string' + ? new Uint8Array(Buffer.from(name, 'base64')) + : name; + + this.attribute_map = { + app: 'app', + name: 'name', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(data: Record): BoxReference { + /* eslint-disable dot-notation */ + if (typeof data['app'] === 'undefined') + throw new Error(`Response is missing required field 'app': ${data}`); + if (typeof data['name'] === 'undefined') + throw new Error(`Response is missing required field 'name': ${data}`); + return new BoxReference({ + app: data['app'], + name: data['name'], + }); + /* eslint-enable dot-notation */ + } +} + /** * Box names of an application */ @@ -3483,7 +3769,7 @@ export class SimulateRequest extends BaseModel { public txnGroups: SimulateRequestTransactionGroup[]; /** - * Allow transactions without signatures to be simulated as if they had correct + * Allows transactions without signatures to be simulated as if they had correct * signatures. */ public allowEmptySignatures?: boolean; @@ -3493,6 +3779,11 @@ export class SimulateRequest extends BaseModel { */ public allowMoreLogging?: boolean; + /** + * Allows access to unnamed resources during simulation. + */ + public allowUnnamedResources?: boolean; + /** * An object that configures simulation execution trace. */ @@ -3506,9 +3797,10 @@ export class SimulateRequest extends BaseModel { /** * Creates a new `SimulateRequest` object. * @param txnGroups - The transaction groups to simulate. - * @param allowEmptySignatures - Allow transactions without signatures to be simulated as if they had correct + * @param allowEmptySignatures - Allows transactions without signatures to be simulated as if they had correct * signatures. * @param allowMoreLogging - Lifts limits on log opcode usage during simulation. + * @param allowUnnamedResources - Allows access to unnamed resources during simulation. * @param execTraceConfig - An object that configures simulation execution trace. * @param extraOpcodeBudget - Applies extra opcode budget during simulation for each transaction group. */ @@ -3516,12 +3808,14 @@ export class SimulateRequest extends BaseModel { txnGroups, allowEmptySignatures, allowMoreLogging, + allowUnnamedResources, execTraceConfig, extraOpcodeBudget, }: { txnGroups: SimulateRequestTransactionGroup[]; allowEmptySignatures?: boolean; allowMoreLogging?: boolean; + allowUnnamedResources?: boolean; execTraceConfig?: SimulateTraceConfig; extraOpcodeBudget?: number | bigint; }) { @@ -3529,6 +3823,7 @@ export class SimulateRequest extends BaseModel { this.txnGroups = txnGroups; this.allowEmptySignatures = allowEmptySignatures; this.allowMoreLogging = allowMoreLogging; + this.allowUnnamedResources = allowUnnamedResources; this.execTraceConfig = execTraceConfig; this.extraOpcodeBudget = extraOpcodeBudget; @@ -3536,6 +3831,7 @@ export class SimulateRequest extends BaseModel { txnGroups: 'txn-groups', allowEmptySignatures: 'allow-empty-signatures', allowMoreLogging: 'allow-more-logging', + allowUnnamedResources: 'allow-unnamed-resources', execTraceConfig: 'exec-trace-config', extraOpcodeBudget: 'extra-opcode-budget', }; @@ -3554,6 +3850,7 @@ export class SimulateRequest extends BaseModel { ), allowEmptySignatures: data['allow-empty-signatures'], allowMoreLogging: data['allow-more-logging'], + allowUnnamedResources: data['allow-unnamed-resources'], execTraceConfig: typeof data['exec-trace-config'] !== 'undefined' ? SimulateTraceConfig.from_obj_for_encoding(data['exec-trace-config']) @@ -3729,6 +4026,12 @@ export class SimulateTraceConfig extends BaseModel { */ public stackChange?: boolean; + /** + * A boolean option enabling returning application state changes (global, local, + * and box changes) with the execution trace during simulation. + */ + public stateChange?: boolean; + /** * Creates a new `SimulateTraceConfig` object. * @param enable - A boolean option for opting in execution trace features simulation endpoint. @@ -3736,25 +4039,31 @@ export class SimulateTraceConfig extends BaseModel { * trace during simulation. * @param stackChange - A boolean option enabling returning stack changes together with execution trace * during simulation. + * @param stateChange - A boolean option enabling returning application state changes (global, local, + * and box changes) with the execution trace during simulation. */ constructor({ enable, scratchChange, stackChange, + stateChange, }: { enable?: boolean; scratchChange?: boolean; stackChange?: boolean; + stateChange?: boolean; }) { super(); this.enable = enable; this.scratchChange = scratchChange; this.stackChange = stackChange; + this.stateChange = stateChange; this.attribute_map = { enable: 'enable', scratchChange: 'scratch-change', stackChange: 'stack-change', + stateChange: 'state-change', }; } @@ -3765,6 +4074,7 @@ export class SimulateTraceConfig extends BaseModel { enable: data['enable'], scratchChange: data['scratch-change'], stackChange: data['stack-change'], + stateChange: data['state-change'], }); /* eslint-enable dot-notation */ } @@ -3803,6 +4113,19 @@ export class SimulateTransactionGroupResult extends BaseModel { */ public failureMessage?: string; + /** + * These are resources that were accessed by this group that would normally have + * caused failure, but were allowed in simulation. Depending on where this object + * is in the response, the unnamed resources it contains may or may not qualify for + * group resource sharing. If this is a field in SimulateTransactionGroupResult, + * the resources do qualify, but if this is a field in SimulateTransactionResult, + * they do not qualify. In order to make this group valid for actual submission, + * resources that qualify for group sharing can be made available by any + * transaction of the group; otherwise, resources must be placed in the same + * transaction which accessed them. + */ + public unnamedResourcesAccessed?: SimulateUnnamedResourcesAccessed; + /** * Creates a new `SimulateTransactionGroupResult` object. * @param txnResults - Simulation result for individual transactions @@ -3814,6 +4137,15 @@ export class SimulateTransactionGroupResult extends BaseModel { * indicate deeper inner transactions. * @param failureMessage - If present, indicates that the transaction group failed and specifies why that * happened + * @param unnamedResourcesAccessed - These are resources that were accessed by this group that would normally have + * caused failure, but were allowed in simulation. Depending on where this object + * is in the response, the unnamed resources it contains may or may not qualify for + * group resource sharing. If this is a field in SimulateTransactionGroupResult, + * the resources do qualify, but if this is a field in SimulateTransactionResult, + * they do not qualify. In order to make this group valid for actual submission, + * resources that qualify for group sharing can be made available by any + * transaction of the group; otherwise, resources must be placed in the same + * transaction which accessed them. */ constructor({ txnResults, @@ -3821,12 +4153,14 @@ export class SimulateTransactionGroupResult extends BaseModel { appBudgetConsumed, failedAt, failureMessage, + unnamedResourcesAccessed, }: { txnResults: SimulateTransactionResult[]; appBudgetAdded?: number | bigint; appBudgetConsumed?: number | bigint; failedAt?: (number | bigint)[]; failureMessage?: string; + unnamedResourcesAccessed?: SimulateUnnamedResourcesAccessed; }) { super(); this.txnResults = txnResults; @@ -3834,6 +4168,7 @@ export class SimulateTransactionGroupResult extends BaseModel { this.appBudgetConsumed = appBudgetConsumed; this.failedAt = failedAt; this.failureMessage = failureMessage; + this.unnamedResourcesAccessed = unnamedResourcesAccessed; this.attribute_map = { txnResults: 'txn-results', @@ -3841,6 +4176,7 @@ export class SimulateTransactionGroupResult extends BaseModel { appBudgetConsumed: 'app-budget-consumed', failedAt: 'failed-at', failureMessage: 'failure-message', + unnamedResourcesAccessed: 'unnamed-resources-accessed', }; } @@ -3861,6 +4197,12 @@ export class SimulateTransactionGroupResult extends BaseModel { appBudgetConsumed: data['app-budget-consumed'], failedAt: data['failed-at'], failureMessage: data['failure-message'], + unnamedResourcesAccessed: + typeof data['unnamed-resources-accessed'] !== 'undefined' + ? SimulateUnnamedResourcesAccessed.from_obj_for_encoding( + data['unnamed-resources-accessed'] + ) + : undefined, }); /* eslint-enable dot-notation */ } @@ -3893,6 +4235,19 @@ export class SimulateTransactionResult extends BaseModel { */ public logicSigBudgetConsumed?: number | bigint; + /** + * These are resources that were accessed by this group that would normally have + * caused failure, but were allowed in simulation. Depending on where this object + * is in the response, the unnamed resources it contains may or may not qualify for + * group resource sharing. If this is a field in SimulateTransactionGroupResult, + * the resources do qualify, but if this is a field in SimulateTransactionResult, + * they do not qualify. In order to make this group valid for actual submission, + * resources that qualify for group sharing can be made available by any + * transaction of the group; otherwise, resources must be placed in the same + * transaction which accessed them. + */ + public unnamedResourcesAccessed?: SimulateUnnamedResourcesAccessed; + /** * Creates a new `SimulateTransactionResult` object. * @param txnResult - Details about a pending transaction. If the transaction was recently confirmed, @@ -3902,29 +4257,42 @@ export class SimulateTransactionResult extends BaseModel { * @param execTrace - The execution trace of calling an app or a logic sig, containing the inner app * call trace in a recursive way. * @param logicSigBudgetConsumed - Budget used during execution of a logic sig transaction. + * @param unnamedResourcesAccessed - These are resources that were accessed by this group that would normally have + * caused failure, but were allowed in simulation. Depending on where this object + * is in the response, the unnamed resources it contains may or may not qualify for + * group resource sharing. If this is a field in SimulateTransactionGroupResult, + * the resources do qualify, but if this is a field in SimulateTransactionResult, + * they do not qualify. In order to make this group valid for actual submission, + * resources that qualify for group sharing can be made available by any + * transaction of the group; otherwise, resources must be placed in the same + * transaction which accessed them. */ constructor({ txnResult, appBudgetConsumed, execTrace, logicSigBudgetConsumed, + unnamedResourcesAccessed, }: { txnResult: PendingTransactionResponse; appBudgetConsumed?: number | bigint; execTrace?: SimulationTransactionExecTrace; logicSigBudgetConsumed?: number | bigint; + unnamedResourcesAccessed?: SimulateUnnamedResourcesAccessed; }) { super(); this.txnResult = txnResult; this.appBudgetConsumed = appBudgetConsumed; this.execTrace = execTrace; this.logicSigBudgetConsumed = logicSigBudgetConsumed; + this.unnamedResourcesAccessed = unnamedResourcesAccessed; this.attribute_map = { txnResult: 'txn-result', appBudgetConsumed: 'app-budget-consumed', execTrace: 'exec-trace', logicSigBudgetConsumed: 'logic-sig-budget-consumed', + unnamedResourcesAccessed: 'unnamed-resources-accessed', }; } @@ -3949,6 +4317,147 @@ export class SimulateTransactionResult extends BaseModel { ) : undefined, logicSigBudgetConsumed: data['logic-sig-budget-consumed'], + unnamedResourcesAccessed: + typeof data['unnamed-resources-accessed'] !== 'undefined' + ? SimulateUnnamedResourcesAccessed.from_obj_for_encoding( + data['unnamed-resources-accessed'] + ) + : undefined, + }); + /* eslint-enable dot-notation */ + } +} + +/** + * These are resources that were accessed by this group that would normally have + * caused failure, but were allowed in simulation. Depending on where this object + * is in the response, the unnamed resources it contains may or may not qualify for + * group resource sharing. If this is a field in SimulateTransactionGroupResult, + * the resources do qualify, but if this is a field in SimulateTransactionResult, + * they do not qualify. In order to make this group valid for actual submission, + * resources that qualify for group sharing can be made available by any + * transaction of the group; otherwise, resources must be placed in the same + * transaction which accessed them. + */ +export class SimulateUnnamedResourcesAccessed extends BaseModel { + /** + * The unnamed accounts that were referenced. The order of this array is arbitrary. + */ + public accounts?: string[]; + + /** + * The unnamed application local states that were referenced. The order of this + * array is arbitrary. + */ + public appLocals?: ApplicationLocalReference[]; + + /** + * The unnamed applications that were referenced. The order of this array is + * arbitrary. + */ + public apps?: (number | bigint)[]; + + /** + * The unnamed asset holdings that were referenced. The order of this array is + * arbitrary. + */ + public assetHoldings?: AssetHoldingReference[]; + + /** + * The unnamed assets that were referenced. The order of this array is arbitrary. + */ + public assets?: (number | bigint)[]; + + /** + * The unnamed boxes that were referenced. The order of this array is arbitrary. + */ + public boxes?: BoxReference[]; + + /** + * The number of extra box references used to increase the IO budget. This is in + * addition to the references defined in the input transaction group and any + * referenced to unnamed boxes. + */ + public extraBoxRefs?: number | bigint; + + /** + * Creates a new `SimulateUnnamedResourcesAccessed` object. + * @param accounts - The unnamed accounts that were referenced. The order of this array is arbitrary. + * @param appLocals - The unnamed application local states that were referenced. The order of this + * array is arbitrary. + * @param apps - The unnamed applications that were referenced. The order of this array is + * arbitrary. + * @param assetHoldings - The unnamed asset holdings that were referenced. The order of this array is + * arbitrary. + * @param assets - The unnamed assets that were referenced. The order of this array is arbitrary. + * @param boxes - The unnamed boxes that were referenced. The order of this array is arbitrary. + * @param extraBoxRefs - The number of extra box references used to increase the IO budget. This is in + * addition to the references defined in the input transaction group and any + * referenced to unnamed boxes. + */ + constructor({ + accounts, + appLocals, + apps, + assetHoldings, + assets, + boxes, + extraBoxRefs, + }: { + accounts?: string[]; + appLocals?: ApplicationLocalReference[]; + apps?: (number | bigint)[]; + assetHoldings?: AssetHoldingReference[]; + assets?: (number | bigint)[]; + boxes?: BoxReference[]; + extraBoxRefs?: number | bigint; + }) { + super(); + this.accounts = accounts; + this.appLocals = appLocals; + this.apps = apps; + this.assetHoldings = assetHoldings; + this.assets = assets; + this.boxes = boxes; + this.extraBoxRefs = extraBoxRefs; + + this.attribute_map = { + accounts: 'accounts', + appLocals: 'app-locals', + apps: 'apps', + assetHoldings: 'asset-holdings', + assets: 'assets', + boxes: 'boxes', + extraBoxRefs: 'extra-box-refs', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding( + data: Record + ): SimulateUnnamedResourcesAccessed { + /* eslint-disable dot-notation */ + return new SimulateUnnamedResourcesAccessed({ + accounts: data['accounts'], + appLocals: + typeof data['app-locals'] !== 'undefined' + ? data['app-locals'].map( + ApplicationLocalReference.from_obj_for_encoding + ) + : undefined, + apps: data['apps'], + assetHoldings: + typeof data['asset-holdings'] !== 'undefined' + ? data['asset-holdings'].map( + AssetHoldingReference.from_obj_for_encoding + ) + : undefined, + assets: data['assets'], + boxes: + typeof data['boxes'] !== 'undefined' + ? data['boxes'].map(BoxReference.from_obj_for_encoding) + : undefined, + extraBoxRefs: data['extra-box-refs'], }); /* eslint-enable dot-notation */ } @@ -3966,6 +4475,11 @@ export class SimulationEvalOverrides extends BaseModel { */ public allowEmptySignatures?: boolean; + /** + * If true, allows access to unnamed resources during simulation. + */ + public allowUnnamedResources?: boolean; + /** * The extra opcode budget added to each transaction group during simulation */ @@ -3985,29 +4499,34 @@ export class SimulationEvalOverrides extends BaseModel { * Creates a new `SimulationEvalOverrides` object. * @param allowEmptySignatures - If true, transactions without signatures are allowed and simulated as if they * were properly signed. + * @param allowUnnamedResources - If true, allows access to unnamed resources during simulation. * @param extraOpcodeBudget - The extra opcode budget added to each transaction group during simulation * @param maxLogCalls - The maximum log calls one can make during simulation * @param maxLogSize - The maximum byte number to log during simulation */ constructor({ allowEmptySignatures, + allowUnnamedResources, extraOpcodeBudget, maxLogCalls, maxLogSize, }: { allowEmptySignatures?: boolean; + allowUnnamedResources?: boolean; extraOpcodeBudget?: number | bigint; maxLogCalls?: number | bigint; maxLogSize?: number | bigint; }) { super(); this.allowEmptySignatures = allowEmptySignatures; + this.allowUnnamedResources = allowUnnamedResources; this.extraOpcodeBudget = extraOpcodeBudget; this.maxLogCalls = maxLogCalls; this.maxLogSize = maxLogSize; this.attribute_map = { allowEmptySignatures: 'allow-empty-signatures', + allowUnnamedResources: 'allow-unnamed-resources', extraOpcodeBudget: 'extra-opcode-budget', maxLogCalls: 'max-log-calls', maxLogSize: 'max-log-size', @@ -4021,6 +4540,7 @@ export class SimulationEvalOverrides extends BaseModel { /* eslint-disable dot-notation */ return new SimulationEvalOverrides({ allowEmptySignatures: data['allow-empty-signatures'], + allowUnnamedResources: data['allow-unnamed-resources'], extraOpcodeBudget: data['extra-opcode-budget'], maxLogCalls: data['max-log-calls'], maxLogSize: data['max-log-size'], @@ -4058,6 +4578,11 @@ export class SimulationOpcodeTraceUnit extends BaseModel { */ public stackPopCount?: number | bigint; + /** + * The operations against the current application's states. + */ + public stateChanges?: ApplicationStateOperation[]; + /** * Creates a new `SimulationOpcodeTraceUnit` object. * @param pc - The program counter of the current opcode being evaluated. @@ -4065,6 +4590,7 @@ export class SimulationOpcodeTraceUnit extends BaseModel { * @param spawnedInners - The indexes of the traces for inner transactions spawned by this opcode, if any. * @param stackAdditions - The values added by this opcode to the stack. * @param stackPopCount - The number of deleted stack values by this opcode. + * @param stateChanges - The operations against the current application's states. */ constructor({ pc, @@ -4072,12 +4598,14 @@ export class SimulationOpcodeTraceUnit extends BaseModel { spawnedInners, stackAdditions, stackPopCount, + stateChanges, }: { pc: number | bigint; scratchChanges?: ScratchChange[]; spawnedInners?: (number | bigint)[]; stackAdditions?: AvmValue[]; stackPopCount?: number | bigint; + stateChanges?: ApplicationStateOperation[]; }) { super(); this.pc = pc; @@ -4085,6 +4613,7 @@ export class SimulationOpcodeTraceUnit extends BaseModel { this.spawnedInners = spawnedInners; this.stackAdditions = stackAdditions; this.stackPopCount = stackPopCount; + this.stateChanges = stateChanges; this.attribute_map = { pc: 'pc', @@ -4092,6 +4621,7 @@ export class SimulationOpcodeTraceUnit extends BaseModel { spawnedInners: 'spawned-inners', stackAdditions: 'stack-additions', stackPopCount: 'stack-pop-count', + stateChanges: 'state-changes', }; } @@ -4114,6 +4644,12 @@ export class SimulationOpcodeTraceUnit extends BaseModel { ? data['stack-additions'].map(AvmValue.from_obj_for_encoding) : undefined, stackPopCount: data['stack-pop-count'], + stateChanges: + typeof data['state-changes'] !== 'undefined' + ? data['state-changes'].map( + ApplicationStateOperation.from_obj_for_encoding + ) + : undefined, }); /* eslint-enable dot-notation */ } @@ -4124,11 +4660,21 @@ export class SimulationOpcodeTraceUnit extends BaseModel { * call trace in a recursive way. */ export class SimulationTransactionExecTrace extends BaseModel { + /** + * SHA512_256 hash digest of the approval program executed in transaction. + */ + public approvalProgramHash?: Uint8Array; + /** * Program trace that contains a trace of opcode effects in an approval program. */ public approvalProgramTrace?: SimulationOpcodeTraceUnit[]; + /** + * SHA512_256 hash digest of the clear state program executed in transaction. + */ + public clearStateProgramHash?: Uint8Array; + /** * Program trace that contains a trace of opcode effects in a clear state program. */ @@ -4140,6 +4686,11 @@ export class SimulationTransactionExecTrace extends BaseModel { */ public innerTrace?: SimulationTransactionExecTrace[]; + /** + * SHA512_256 hash digest of the logic sig executed in transaction. + */ + public logicSigHash?: Uint8Array; + /** * Program trace that contains a trace of opcode effects in a logic sig. */ @@ -4147,33 +4698,57 @@ export class SimulationTransactionExecTrace extends BaseModel { /** * Creates a new `SimulationTransactionExecTrace` object. + * @param approvalProgramHash - SHA512_256 hash digest of the approval program executed in transaction. * @param approvalProgramTrace - Program trace that contains a trace of opcode effects in an approval program. + * @param clearStateProgramHash - SHA512_256 hash digest of the clear state program executed in transaction. * @param clearStateProgramTrace - Program trace that contains a trace of opcode effects in a clear state program. * @param innerTrace - An array of SimulationTransactionExecTrace representing the execution trace of * any inner transactions executed. + * @param logicSigHash - SHA512_256 hash digest of the logic sig executed in transaction. * @param logicSigTrace - Program trace that contains a trace of opcode effects in a logic sig. */ constructor({ + approvalProgramHash, approvalProgramTrace, + clearStateProgramHash, clearStateProgramTrace, innerTrace, + logicSigHash, logicSigTrace, }: { + approvalProgramHash?: string | Uint8Array; approvalProgramTrace?: SimulationOpcodeTraceUnit[]; + clearStateProgramHash?: string | Uint8Array; clearStateProgramTrace?: SimulationOpcodeTraceUnit[]; innerTrace?: SimulationTransactionExecTrace[]; + logicSigHash?: string | Uint8Array; logicSigTrace?: SimulationOpcodeTraceUnit[]; }) { super(); + this.approvalProgramHash = + typeof approvalProgramHash === 'string' + ? new Uint8Array(Buffer.from(approvalProgramHash, 'base64')) + : approvalProgramHash; this.approvalProgramTrace = approvalProgramTrace; + this.clearStateProgramHash = + typeof clearStateProgramHash === 'string' + ? new Uint8Array(Buffer.from(clearStateProgramHash, 'base64')) + : clearStateProgramHash; this.clearStateProgramTrace = clearStateProgramTrace; this.innerTrace = innerTrace; + this.logicSigHash = + typeof logicSigHash === 'string' + ? new Uint8Array(Buffer.from(logicSigHash, 'base64')) + : logicSigHash; this.logicSigTrace = logicSigTrace; this.attribute_map = { + approvalProgramHash: 'approval-program-hash', approvalProgramTrace: 'approval-program-trace', + clearStateProgramHash: 'clear-state-program-hash', clearStateProgramTrace: 'clear-state-program-trace', innerTrace: 'inner-trace', + logicSigHash: 'logic-sig-hash', logicSigTrace: 'logic-sig-trace', }; } @@ -4184,12 +4759,14 @@ export class SimulationTransactionExecTrace extends BaseModel { ): SimulationTransactionExecTrace { /* eslint-disable dot-notation */ return new SimulationTransactionExecTrace({ + approvalProgramHash: data['approval-program-hash'], approvalProgramTrace: typeof data['approval-program-trace'] !== 'undefined' ? data['approval-program-trace'].map( SimulationOpcodeTraceUnit.from_obj_for_encoding ) : undefined, + clearStateProgramHash: data['clear-state-program-hash'], clearStateProgramTrace: typeof data['clear-state-program-trace'] !== 'undefined' ? data['clear-state-program-trace'].map( @@ -4202,6 +4779,7 @@ export class SimulationTransactionExecTrace extends BaseModel { SimulationTransactionExecTrace.from_obj_for_encoding ) : undefined, + logicSigHash: data['logic-sig-hash'], logicSigTrace: typeof data['logic-sig-trace'] !== 'undefined' ? data['logic-sig-trace'].map( From 35bf0108a1b5f16c28157b2cc26526c701fb8bb7 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 24 Aug 2023 10:41:43 -0400 Subject: [PATCH 2/9] change test branch --- .test-env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.test-env b/.test-env index a321a7f1e..1dd085f08 100644 --- a/.test-env +++ b/.test-env @@ -1,6 +1,6 @@ # Configs for testing repo download: SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" -SDK_TESTING_BRANCH="V2" +SDK_TESTING_BRANCH="simulate-state-change" SDK_TESTING_HARNESS="test-harness" INSTALL_ONLY=0 From 346594b887524f5ec38d88acaf107443c42944bf Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 24 Aug 2023 12:40:38 -0400 Subject: [PATCH 3/9] step implementation? --- tests/cucumber/integration.tags | 1 + tests/cucumber/steps/steps.js | 84 +++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/tests/cucumber/integration.tags b/tests/cucumber/integration.tags index fc6d7852b..eefd3bdd0 100644 --- a/tests/cucumber/integration.tags +++ b/tests/cucumber/integration.tags @@ -17,3 +17,4 @@ @simulate.lift_log_limits @simulate.extra_opcode_budget @simulate.exec_trace_with_stack_scratch +@simulate.exec_trace_with_state_change_and_hash diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 21d2ac3a0..502dfe3f8 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -4827,6 +4827,7 @@ module.exports = function getSteps(options) { enable: true, scratchChange: optionList.includes('scratch'), stackChange: optionList.includes('stack'), + stateChange: optionList.includes('state'), } ); } @@ -4935,6 +4936,89 @@ module.exports = function getSteps(options) { } ); + Then( + '{int}th unit in the {string} trace at txn-groups path {string} should write to {string} state {string} with new value {string}.', + async function ( + unitIndex, + traceType, + txnGroupPath, + stateType, + stateName, + newValue + ) { + const unitFinder = (txnGroupPathStr, traceTypeStr, unitIndexInt) => { + const txnGroupPathSplit = txnGroupPathStr + .split(',') + .filter((r) => r !== '') + .map(Number); + assert.ok(txnGroupPathSplit.length > 0); + + let traces = this.simulateResponse.txnGroups[0].txnResults[ + txnGroupPathSplit[0] + ].execTrace; + assert.ok(traces); + + for (let i = 1; i < txnGroupPathSplit.length; i++) { + traces = traces.innerTrace[txnGroupPathSplit[i]]; + assert.ok(traces); + } + + let trace = traces.approvalProgramTrace; + + if (traceTypeStr === 'approval') { + trace = traces.approvalProgramTrace; + } else if (traceTypeStr === 'clearState') { + trace = traces.clearStateProgramTrace; + } else if (traceTypeStr === 'logic') { + trace = traces.logicSigTrace; + } + const changeUnit = trace[unitIndexInt]; + return changeUnit; + }; + + const avmValueCheck = (stringLiteral, avmValue) => { + const [avmType, value] = stringLiteral.split(':'); + + if (avmType === 'uint64') { + assert.equal(avmValue.type, 2); + assert.ok(avmValue.uint); + assert.equal(avmValue.uint, BigInt(value)); + } else if (avmType === 'bytes') { + assert.equal(avmValue.type, 1); + assert.ok(avmValue.bytes); + assert.deepEqual( + avmValue.bytes, + makeUint8Array(Buffer.from(value, 'base64')) + ); + } else { + assert.fail('avmType should be either uint64 or bytes'); + } + }; + + assert.ok(this.simulateResponse); + + const changeUnit = unitFinder(txnGroupPath, traceType, unitIndex); + assert.ok(changeUnit.stateChanges); + + if (stateType === 'global') { + assert.deepEqual(changeUnit.stateChanges[0].appStateType, 'g'); + } else if (stateType === 'local') { + assert.deepEqual(changeUnit.stateChanges[0].appStateType, 'l'); + } else if (stateType === 'box') { + assert.deepEqual(changeUnit.stateChanges[0].appStateType, 'b'); + } else { + assert.fail('state type can only be one of local/global/box'); + } + + assert.deepEqual( + changeUnit.stateChanges[0].key, + new Uint8Array(Buffer.from(stateName)) + ); + assert.ok(changeUnit.stateChanges[0].newValue); + avmValueCheck(newValue, changeUnit.stateChanges[0].newValue); + } + ); + When('we make a Ready call', async function () { await this.v2Client.ready().do(); }); From 8a1e7f506df49012057aede0ce0e847d13b7a0ea Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 24 Aug 2023 14:45:30 -0400 Subject: [PATCH 4/9] step impl for hash --- tests/cucumber/steps/steps.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 502dfe3f8..d46b45919 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -5019,6 +5019,41 @@ module.exports = function getSteps(options) { } ); + Then( + '{string} hash at txn-groups path {string} should be {string}.', + async function (traceType, txnGroupPath, b64ProgHash) { + const txnGroupPathSplit = txnGroupPath + .split(',') + .filter((r) => r !== '') + .map(Number); + assert.ok(txnGroupPathSplit.length > 0); + + let traces = this.simulateResponse.txnGroups[0].txnResults[ + txnGroupPathSplit[0] + ].execTrace; + assert.ok(traces); + + for (let i = 1; i < txnGroupPathSplit.length; i++) { + traces = traces.innerTrace[txnGroupPathSplit[i]]; + assert.ok(traces); + } + + let hash = traces.approvalProgramHash; + + if (traceType === 'approval') { + hash = traces.approvalProgramHash; + } else if (traceType === 'clearState') { + hash = traces.clearStateProgramHash; + } else if (traceType === 'logic') { + hash = traces.logicSigHash; + } + assert.deepEqual( + hash, + makeUint8Array(Buffer.from(b64ProgHash, 'base64')) + ); + } + ); + When('we make a Ready call', async function () { await this.v2Client.ready().do(); }); From 9e2f6a895859d817affacb17fbbeb4e3d59ffa40 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 12 Sep 2023 11:53:11 -0400 Subject: [PATCH 5/9] Update types and cucumber step impl --- src/client/v2/algod/models/types.ts | 251 ++++++++++++++++++++++++++++ tests/cucumber/steps/steps.js | 40 +++-- 2 files changed, 280 insertions(+), 11 deletions(-) diff --git a/src/client/v2/algod/models/types.ts b/src/client/v2/algod/models/types.ts index c53554930..3392591e0 100644 --- a/src/client/v2/algod/models/types.ts +++ b/src/client/v2/algod/models/types.ts @@ -784,6 +784,136 @@ export class Application extends BaseModel { } } +/** + * An application's initial global/local/box states that were accessed during + * simulation. + */ +export class ApplicationInitialStates extends BaseModel { + /** + * Application index. + */ + public id: number | bigint; + + /** + * An application's global/local/box state. + */ + public appBoxes?: ApplicationKVStorage; + + /** + * An application's global/local/box state. + */ + public appGlobals?: ApplicationKVStorage; + + /** + * An application's initial local states tied to different accounts. + */ + public appLocals?: ApplicationKVStorage[]; + + /** + * Creates a new `ApplicationInitialStates` object. + * @param id - Application index. + * @param appBoxes - An application's global/local/box state. + * @param appGlobals - An application's global/local/box state. + * @param appLocals - An application's initial local states tied to different accounts. + */ + constructor({ + id, + appBoxes, + appGlobals, + appLocals, + }: { + id: number | bigint; + appBoxes?: ApplicationKVStorage; + appGlobals?: ApplicationKVStorage; + appLocals?: ApplicationKVStorage[]; + }) { + super(); + this.id = id; + this.appBoxes = appBoxes; + this.appGlobals = appGlobals; + this.appLocals = appLocals; + + this.attribute_map = { + id: 'id', + appBoxes: 'app-boxes', + appGlobals: 'app-globals', + appLocals: 'app-locals', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding( + data: Record + ): ApplicationInitialStates { + /* eslint-disable dot-notation */ + if (typeof data['id'] === 'undefined') + throw new Error(`Response is missing required field 'id': ${data}`); + return new ApplicationInitialStates({ + id: data['id'], + appBoxes: + typeof data['app-boxes'] !== 'undefined' + ? ApplicationKVStorage.from_obj_for_encoding(data['app-boxes']) + : undefined, + appGlobals: + typeof data['app-globals'] !== 'undefined' + ? ApplicationKVStorage.from_obj_for_encoding(data['app-globals']) + : undefined, + appLocals: + typeof data['app-locals'] !== 'undefined' + ? data['app-locals'].map(ApplicationKVStorage.from_obj_for_encoding) + : undefined, + }); + /* eslint-enable dot-notation */ + } +} + +/** + * An application's global/local/box state. + */ +export class ApplicationKVStorage extends BaseModel { + /** + * Key-Value pairs representing application states. + */ + public kvs: AvmKeyValue[]; + + /** + * The address of the account associated with the local state. + */ + public account?: string; + + /** + * Creates a new `ApplicationKVStorage` object. + * @param kvs - Key-Value pairs representing application states. + * @param account - The address of the account associated with the local state. + */ + constructor({ kvs, account }: { kvs: AvmKeyValue[]; account?: string }) { + super(); + this.kvs = kvs; + this.account = account; + + this.attribute_map = { + kvs: 'kvs', + account: 'account', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding( + data: Record + ): ApplicationKVStorage { + /* eslint-disable dot-notation */ + if (!Array.isArray(data['kvs'])) + throw new Error( + `Response is missing required array field 'kvs': ${data}` + ); + return new ApplicationKVStorage({ + kvs: data['kvs'].map(AvmKeyValue.from_obj_for_encoding), + account: data['account'], + }); + /* eslint-enable dot-notation */ + } +} + /** * References an account's local state for an application. */ @@ -1593,6 +1723,51 @@ export class AssetParams extends BaseModel { } } +/** + * Represents an AVM key-value pair in an application store. + */ +export class AvmKeyValue extends BaseModel { + public key: Uint8Array; + + /** + * Represents an AVM value. + */ + public value: AvmValue; + + /** + * Creates a new `AvmKeyValue` object. + * @param key - + * @param value - Represents an AVM value. + */ + constructor({ key, value }: { key: string | Uint8Array; value: AvmValue }) { + super(); + this.key = + typeof key === 'string' + ? new Uint8Array(Buffer.from(key, 'base64')) + : key; + this.value = value; + + this.attribute_map = { + key: 'key', + value: 'value', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(data: Record): AvmKeyValue { + /* eslint-disable dot-notation */ + if (typeof data['key'] === 'undefined') + throw new Error(`Response is missing required field 'key': ${data}`); + if (typeof data['value'] === 'undefined') + throw new Error(`Response is missing required field 'value': ${data}`); + return new AvmKeyValue({ + key: data['key'], + value: AvmValue.from_obj_for_encoding(data['value']), + }); + /* eslint-enable dot-notation */ + } +} + /** * Represents an AVM value. */ @@ -3759,6 +3934,51 @@ export class ScratchChange extends BaseModel { } } +/** + * Initial states of resources that were accessed during simulation. + */ +export class SimulateInitialStates extends BaseModel { + /** + * The initial states of accessed application before simulation. The order of this + * array is arbitrary. + */ + public appInitialStates?: ApplicationInitialStates[]; + + /** + * Creates a new `SimulateInitialStates` object. + * @param appInitialStates - The initial states of accessed application before simulation. The order of this + * array is arbitrary. + */ + constructor({ + appInitialStates, + }: { + appInitialStates?: ApplicationInitialStates[]; + }) { + super(); + this.appInitialStates = appInitialStates; + + this.attribute_map = { + appInitialStates: 'app-initial-states', + }; + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding( + data: Record + ): SimulateInitialStates { + /* eslint-disable dot-notation */ + return new SimulateInitialStates({ + appInitialStates: + typeof data['app-initial-states'] !== 'undefined' + ? data['app-initial-states'].map( + ApplicationInitialStates.from_obj_for_encoding + ) + : undefined, + }); + /* eslint-enable dot-notation */ + } +} + /** * Request type for simulation endpoint. */ @@ -3794,6 +4014,14 @@ export class SimulateRequest extends BaseModel { */ public extraOpcodeBudget?: number | bigint; + /** + * If provided, specifies the round preceding the simulation. State changes through + * this round will be used to run this simulation. Usually only the 4 most recent + * rounds will be available (controlled by the node config value MaxAcctLookback). + * If not specified, defaults to the latest available round. + */ + public round?: number | bigint; + /** * Creates a new `SimulateRequest` object. * @param txnGroups - The transaction groups to simulate. @@ -3803,6 +4031,10 @@ export class SimulateRequest extends BaseModel { * @param allowUnnamedResources - Allows access to unnamed resources during simulation. * @param execTraceConfig - An object that configures simulation execution trace. * @param extraOpcodeBudget - Applies extra opcode budget during simulation for each transaction group. + * @param round - If provided, specifies the round preceding the simulation. State changes through + * this round will be used to run this simulation. Usually only the 4 most recent + * rounds will be available (controlled by the node config value MaxAcctLookback). + * If not specified, defaults to the latest available round. */ constructor({ txnGroups, @@ -3811,6 +4043,7 @@ export class SimulateRequest extends BaseModel { allowUnnamedResources, execTraceConfig, extraOpcodeBudget, + round, }: { txnGroups: SimulateRequestTransactionGroup[]; allowEmptySignatures?: boolean; @@ -3818,6 +4051,7 @@ export class SimulateRequest extends BaseModel { allowUnnamedResources?: boolean; execTraceConfig?: SimulateTraceConfig; extraOpcodeBudget?: number | bigint; + round?: number | bigint; }) { super(); this.txnGroups = txnGroups; @@ -3826,6 +4060,7 @@ export class SimulateRequest extends BaseModel { this.allowUnnamedResources = allowUnnamedResources; this.execTraceConfig = execTraceConfig; this.extraOpcodeBudget = extraOpcodeBudget; + this.round = round; this.attribute_map = { txnGroups: 'txn-groups', @@ -3834,6 +4069,7 @@ export class SimulateRequest extends BaseModel { allowUnnamedResources: 'allow-unnamed-resources', execTraceConfig: 'exec-trace-config', extraOpcodeBudget: 'extra-opcode-budget', + round: 'round', }; } @@ -3856,6 +4092,7 @@ export class SimulateRequest extends BaseModel { ? SimulateTraceConfig.from_obj_for_encoding(data['exec-trace-config']) : undefined, extraOpcodeBudget: data['extra-opcode-budget'], + round: data['round'], }); /* eslint-enable dot-notation */ } @@ -3931,6 +4168,11 @@ export class SimulateResponse extends BaseModel { */ public execTraceConfig?: SimulateTraceConfig; + /** + * Initial states of resources that were accessed during simulation. + */ + public initialStates?: SimulateInitialStates; + /** * Creates a new `SimulateResponse` object. * @param lastRound - The round immediately preceding this simulation. State changes through this @@ -3941,6 +4183,7 @@ export class SimulateResponse extends BaseModel { * parameters is present, then evaluation parameters may differ from standard * evaluation in certain ways. * @param execTraceConfig - An object that configures simulation execution trace. + * @param initialStates - Initial states of resources that were accessed during simulation. */ constructor({ lastRound, @@ -3948,12 +4191,14 @@ export class SimulateResponse extends BaseModel { version, evalOverrides, execTraceConfig, + initialStates, }: { lastRound: number | bigint; txnGroups: SimulateTransactionGroupResult[]; version: number | bigint; evalOverrides?: SimulationEvalOverrides; execTraceConfig?: SimulateTraceConfig; + initialStates?: SimulateInitialStates; }) { super(); this.lastRound = lastRound; @@ -3961,6 +4206,7 @@ export class SimulateResponse extends BaseModel { this.version = version; this.evalOverrides = evalOverrides; this.execTraceConfig = execTraceConfig; + this.initialStates = initialStates; this.attribute_map = { lastRound: 'last-round', @@ -3968,6 +4214,7 @@ export class SimulateResponse extends BaseModel { version: 'version', evalOverrides: 'eval-overrides', execTraceConfig: 'exec-trace-config', + initialStates: 'initial-states', }; } @@ -4000,6 +4247,10 @@ export class SimulateResponse extends BaseModel { typeof data['exec-trace-config'] !== 'undefined' ? SimulateTraceConfig.from_obj_for_encoding(data['exec-trace-config']) : undefined, + initialStates: + typeof data['initial-states'] !== 'undefined' + ? SimulateInitialStates.from_obj_for_encoding(data['initial-states']) + : undefined, }); /* eslint-enable dot-notation */ } diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index d46b45919..c04f7c9f3 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -3028,6 +3028,8 @@ module.exports = function getSteps(options) { return algosdk.OnApplicationComplete.NoOpOC; case 'update': return algosdk.OnApplicationComplete.UpdateApplicationOC; + case 'create-and-optin': + return algosdk.OnApplicationComplete.OptInOC; case 'optin': return algosdk.OnApplicationComplete.OptInOC; case 'delete': @@ -3369,7 +3371,10 @@ module.exports = function getSteps(options) { extraPages, boxesCommaSeparatedString ) { - if (operationString === 'create') { + if ( + operationString === 'create' || + operationString === 'create-and-optin' + ) { this.currentApplicationIndex = 0; } @@ -4964,7 +4969,6 @@ module.exports = function getSteps(options) { } let trace = traces.approvalProgramTrace; - if (traceTypeStr === 'approval') { trace = traces.approvalProgramTrace; } else if (traceTypeStr === 'clearState') { @@ -4972,6 +4976,12 @@ module.exports = function getSteps(options) { } else if (traceTypeStr === 'logic') { trace = traces.logicSigTrace; } + + assert.ok( + unitIndexInt < trace.length, + `unitIndexInt (${unitIndexInt}) < trace.length (${trace.length})` + ); + const changeUnit = trace[unitIndexInt]; return changeUnit; }; @@ -4999,23 +5009,31 @@ module.exports = function getSteps(options) { const changeUnit = unitFinder(txnGroupPath, traceType, unitIndex); assert.ok(changeUnit.stateChanges); + assert.strictEqual(changeUnit.stateChanges.length, 1); + const stateChange = changeUnit.stateChanges[0]; if (stateType === 'global') { - assert.deepEqual(changeUnit.stateChanges[0].appStateType, 'g'); + assert.strictEqual(stateChange.appStateType, 'g'); + assert.ok(!stateChange.account); } else if (stateType === 'local') { - assert.deepEqual(changeUnit.stateChanges[0].appStateType, 'l'); + assert.strictEqual(stateChange.appStateType, 'l'); + assert.ok(stateChange.account); + algosdk.decodeAddress(stateChange.account); } else if (stateType === 'box') { - assert.deepEqual(changeUnit.stateChanges[0].appStateType, 'b'); + assert.strictEqual(stateChange.appStateType, 'b'); + assert.ok(!stateChange.account); } else { assert.fail('state type can only be one of local/global/box'); } - assert.deepEqual( - changeUnit.stateChanges[0].key, - new Uint8Array(Buffer.from(stateName)) + assert.strictEqual(stateChange.operation, 'w'); + + assert.deepStrictEqual( + stateChange.key, + makeUint8Array(Buffer.from(stateName)) ); - assert.ok(changeUnit.stateChanges[0].newValue); - avmValueCheck(newValue, changeUnit.stateChanges[0].newValue); + assert.ok(stateChange.newValue); + avmValueCheck(newValue, stateChange.newValue); } ); @@ -5047,7 +5065,7 @@ module.exports = function getSteps(options) { } else if (traceType === 'logic') { hash = traces.logicSigHash; } - assert.deepEqual( + assert.deepStrictEqual( hash, makeUint8Array(Buffer.from(b64ProgHash, 'base64')) ); From 3d98cdb94e4b1ae08c5815d20d86b028b81843ca Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 12 Sep 2023 12:32:41 -0400 Subject: [PATCH 6/9] Test boxes too --- tests/cucumber/steps/steps.js | 62 ++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index c04f7c9f3..f879832a2 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -3860,7 +3860,8 @@ module.exports = function getSteps(options) { localBytes, localInts, extraPages, - note + note, + boxesCommaSeparatedString ) { // open and load in approval program let approvalProgramBytes; @@ -3915,6 +3916,11 @@ module.exports = function getSteps(options) { methodArgs.push(typeToDecode.decode(encodedArg)); } + let boxes; + if (boxesCommaSeparatedString !== '') { + boxes = splitAndProcessBoxReferences(boxesCommaSeparatedString); + } + this.composer.addMethodCall({ appID: this.currentApplicationIndex, method: this.method, @@ -3930,6 +3936,7 @@ module.exports = function getSteps(options) { numLocalByteSlices: localBytes, extraPages, note, + boxes, signer: this.transactionSigner, }); } @@ -3947,6 +3954,8 @@ module.exports = function getSteps(options) { undefined, undefined, undefined, + undefined, + undefined, undefined ); } @@ -3965,11 +3974,33 @@ module.exports = function getSteps(options) { undefined, undefined, undefined, + undefined, + undefined, undefined ); } ); + When( + 'I add a method call with the transient account, the current application, suggested params, on complete {string}, current transaction signer, current method arguments, boxes {string}.', + async function (onComplete, boxesCommaSeparatedString) { + await addMethodCallToComposer.call( + this, + this.transientAccount.addr, + onComplete, + '', + '', + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + boxesCommaSeparatedString + ); + } + ); + When( 'I add a method call with the transient account, the current application, suggested params, on complete {string}, current transaction signer, current method arguments, approval-program {string}, clear-program {string}, global-bytes {int}, global-ints {int}, local-bytes {int}, local-ints {int}, extra-pages {int}.', async function ( @@ -3992,7 +4023,9 @@ module.exports = function getSteps(options) { parseInt(globalInts, 10), parseInt(localBytes, 10), parseInt(localInts, 10), - parseInt(extraPages, 10) + parseInt(extraPages, 10), + undefined, + undefined ); } ); @@ -4019,7 +4052,9 @@ module.exports = function getSteps(options) { parseInt(globalInts, 10), parseInt(localBytes, 10), parseInt(localInts, 10), - parseInt(extraPages, 10) + parseInt(extraPages, 10), + undefined, + undefined ); } ); @@ -4038,6 +4073,8 @@ module.exports = function getSteps(options) { undefined, undefined, undefined, + undefined, + undefined, undefined ); } @@ -4057,6 +4094,8 @@ module.exports = function getSteps(options) { undefined, undefined, undefined, + undefined, + undefined, undefined ); } @@ -4081,7 +4120,8 @@ module.exports = function getSteps(options) { undefined, undefined, undefined, - nonce + nonce, + undefined ); } ); @@ -4995,11 +5035,15 @@ module.exports = function getSteps(options) { assert.equal(avmValue.uint, BigInt(value)); } else if (avmType === 'bytes') { assert.equal(avmValue.type, 1); - assert.ok(avmValue.bytes); - assert.deepEqual( - avmValue.bytes, - makeUint8Array(Buffer.from(value, 'base64')) - ); + if (value.length > 0) { + assert.ok(avmValue.bytes); + assert.deepStrictEqual( + avmValue.bytes, + makeUint8Array(Buffer.from(value, 'base64')) + ); + } else { + assert.equal(avmValue.bytes, undefined); + } } else { assert.fail('avmType should be either uint64 or bytes'); } From 84d710a33809979f211d904dd9e115be04acad82 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 12 Sep 2023 14:21:16 -0400 Subject: [PATCH 7/9] Test initial state --- tests/cucumber/steps/steps.js | 166 ++++++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 46 deletions(-) diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index f879832a2..094eb64f2 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -4763,9 +4763,9 @@ module.exports = function getSteps(options) { Then( 'the simulation should succeed without any failure message', - async function () { + function () { for (const txnGroup of this.simulateResponse.txnGroups) { - assert.deepStrictEqual(undefined, txnGroup.failedMessage); + assert.equal(txnGroup.failureMessage, undefined); } } ); @@ -4878,6 +4878,31 @@ module.exports = function getSteps(options) { } ); + function avmValueCheck(expectedStringLiteral, actualAvmValue) { + const [expectedAvmType, expectedValue] = expectedStringLiteral.split(':'); + + if (expectedAvmType === 'uint64') { + assert.equal(actualAvmValue.type, 2); + if (expectedValue === 0) { + assert.equal(actualAvmValue.uint, undefined); + } else { + assert.equal(actualAvmValue.uint, BigInt(expectedValue)); + } + } else if (expectedAvmType === 'bytes') { + assert.equal(actualAvmValue.type, 1); + if (expectedValue.length === 0) { + assert.equal(actualAvmValue.bytes, undefined); + } else { + assert.deepStrictEqual( + actualAvmValue.bytes, + makeUint8Array(Buffer.from(expectedValue, 'base64')) + ); + } + } else { + assert.fail('expectedAvmType should be either uint64 or bytes'); + } + } + Then( '{int}th unit in the {string} trace at txn-groups path {string} should add value {string} to stack, pop {int} values from stack, write value {string} to scratch slot {string}.', async function ( @@ -4919,25 +4944,6 @@ module.exports = function getSteps(options) { return changeUnit; }; - const avmValueCheck = (stringLiteral, avmValue) => { - const [avmType, value] = stringLiteral.split(':'); - - if (avmType === 'uint64') { - assert.equal(avmValue.type, 2); - assert.ok(avmValue.uint); - assert.equal(avmValue.uint, BigInt(value)); - } else if (avmType === 'bytes') { - assert.equal(avmValue.type, 1); - assert.ok(avmValue.bytes); - assert.deepEqual( - avmValue.bytes, - makeUint8Array(Buffer.from(value, 'base64')) - ); - } else { - assert.fail('avmType should be either uint64 or bytes'); - } - }; - assert.ok(this.simulateResponse); const changeUnit = unitFinder(txnGroupPath, traceType, unitIndex); @@ -4981,9 +4987,100 @@ module.exports = function getSteps(options) { } ); + Then( + 'the current application initial {string} state should be empty.', + function (stateType) { + assert.ok(this.simulateResponse.initialStates); + assert.ok(this.simulateResponse.initialStates.appInitialStates); + let initialAppState = null; + let found = false; + for (const entry of this.simulateResponse.initialStates + .appInitialStates) { + if (entry.id !== this.currentApplicationIndex) { + continue; + } + initialAppState = entry; + found = true; + break; + } + assert.ok(found); + if (initialAppState) { + switch (stateType) { + case 'local': + assert.ok(!initialAppState.appLocals); + break; + case 'global': + assert.ok(!initialAppState.appGlobals); + break; + case 'box': + assert.ok(!initialAppState.appBoxes); + break; + default: + assert.fail('state type can only be one of local/global/box'); + } + } + } + ); + + Then( + 'the current application initial {string} state should contain {string} with value {string}.', + function (stateType, keyStr, valueStr) { + assert.ok(this.simulateResponse.initialStates); + assert.ok(this.simulateResponse.initialStates.appInitialStates); + let initialAppState = null; + for (const entry of this.simulateResponse.initialStates + .appInitialStates) { + if (entry.id !== this.currentApplicationIndex) { + continue; + } + initialAppState = entry; + break; + } + assert.ok(initialAppState); + let kvs = null; + switch (stateType) { + case 'local': + assert.ok(initialAppState.appLocals); + assert.strictEqual(initialAppState.appLocals.length, 1); + assert.ok(initialAppState.appLocals[0].account); + algosdk.decodeAddress(initialAppState.appLocals[0].account); + assert.ok(initialAppState.appLocals[0].kvs); + kvs = initialAppState.appLocals[0].kvs; + break; + case 'global': + assert.ok(initialAppState.appGlobals); + assert.ok(!initialAppState.appGlobals.account); + assert.ok(initialAppState.appGlobals.kvs); + kvs = initialAppState.appGlobals.kvs; + break; + case 'box': + assert.ok(initialAppState.appBoxes); + assert.ok(!initialAppState.appBoxes.account); + assert.ok(initialAppState.appBoxes.kvs); + kvs = initialAppState.appBoxes.kvs; + break; + default: + assert.fail('state type can only be one of local/global/box'); + } + assert.ok(kvs.length > 0); + + const binaryKey = Buffer.from(keyStr); + + let actualValue = null; + for (const kv of kvs) { + if (binaryKey.equals(kv.key)) { + actualValue = kv.value; + break; + } + } + assert.ok(actualValue); + avmValueCheck(valueStr, actualValue); + } + ); + Then( '{int}th unit in the {string} trace at txn-groups path {string} should write to {string} state {string} with new value {string}.', - async function ( + function ( unitIndex, traceType, txnGroupPath, @@ -5026,29 +5123,6 @@ module.exports = function getSteps(options) { return changeUnit; }; - const avmValueCheck = (stringLiteral, avmValue) => { - const [avmType, value] = stringLiteral.split(':'); - - if (avmType === 'uint64') { - assert.equal(avmValue.type, 2); - assert.ok(avmValue.uint); - assert.equal(avmValue.uint, BigInt(value)); - } else if (avmType === 'bytes') { - assert.equal(avmValue.type, 1); - if (value.length > 0) { - assert.ok(avmValue.bytes); - assert.deepStrictEqual( - avmValue.bytes, - makeUint8Array(Buffer.from(value, 'base64')) - ); - } else { - assert.equal(avmValue.bytes, undefined); - } - } else { - assert.fail('avmType should be either uint64 or bytes'); - } - }; - assert.ok(this.simulateResponse); const changeUnit = unitFinder(txnGroupPath, traceType, unitIndex); @@ -5083,7 +5157,7 @@ module.exports = function getSteps(options) { Then( '{string} hash at txn-groups path {string} should be {string}.', - async function (traceType, txnGroupPath, b64ProgHash) { + function (traceType, txnGroupPath, b64ProgHash) { const txnGroupPathSplit = txnGroupPath .split(',') .filter((r) => r !== '') From aa924371014c635c57fd45dfaa0067b07f73a61a Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 12 Sep 2023 14:40:28 -0400 Subject: [PATCH 8/9] brower buffer package is strict --- tests/cucumber/steps/steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 094eb64f2..cca191fef 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -5068,7 +5068,7 @@ module.exports = function getSteps(options) { let actualValue = null; for (const kv of kvs) { - if (binaryKey.equals(kv.key)) { + if (binaryKey.equals(Buffer.from(kv.key))) { actualValue = kv.value; break; } From 11e5486ddbbb0fe4941a2a129f980e740733a9bc Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 13 Sep 2023 14:55:22 -0400 Subject: [PATCH 9/9] Update .test-env Co-authored-by: Hang Su <87964331+ahangsu@users.noreply.github.com> --- .test-env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.test-env b/.test-env index 1dd085f08..a321a7f1e 100644 --- a/.test-env +++ b/.test-env @@ -1,6 +1,6 @@ # Configs for testing repo download: SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" -SDK_TESTING_BRANCH="simulate-state-change" +SDK_TESTING_BRANCH="V2" SDK_TESTING_HARNESS="test-harness" INSTALL_ONLY=0