Skip to main content

State Access Wrapper

Artela allows Aspect access to the state's change of smart contract execution. It provides a host API to access it. But it is too low level and many too difficult to use it.

Here is an example to access the stateโ€™s change.

// access a state 'balances' in a contract 
// which contract name is HoneyPot
// and address is 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
const contract = hexToUint8Array("0x5B38Da6a701c568545dCfcB03FcB875f56beddC4");
const stateVar = 'HoneyPot.balances';

// call host API to access it.
const query= new StateChangeQuery(contract,stateVar,[]);
const response = sys.hostApi.trace.queryStateChange(query);
const indicesResult = Protobuf.decode<EthStateChangeIndices>(response, EthStateChangeIndices.decode);

This state access Wrapper can generate a wrapper class, and you can access the stateโ€™s changes more easily like this.

//import wrapper class
import {HoneyPotState} from "./honeypot_storage";
...
// access it by a sweet api
let contract = '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4'
let sysBalance = new HoneyPotState._balance_(contract);

This guide instructs users on generating a wrapper class using aspect-tool generate and provides guidance on its utilization.

Commandโ€‹

USAGE
$ aspect-tool generate [-i <value>] [-o <value>]
FLAGS
-i, --in=<value>
-o, --out=<value>

options๏ผš

  • --i :Input your storage layout json file path or directory, like -i storage_layout.json -i ./build/contract
  • --o: Target generated ts file output path or directory, like -o xx.ts -o ./aspect/contract

Exampleโ€‹

1. create a smart contractโ€‹

Add a smart contract to the 'contract' directory in the my-first-aspect project (see Init Project), such as HoneyPot.sol๏ผš

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;

contract HoneyPot {
mapping(address => uint256) public balances;
address private deployer;
constructor() {
deployer = msg.sender;
}
function isOwner(address user) external view returns (bool result) {
if (user == deployer) {
return true;
} else {
return false;
}
}

function deposit() public payable {
balances[msg.sender] += msg.value;
}

function withdraw() public {
uint bal = balances[msg.sender];
require(bal > 0);

(bool sent,) = msg.sender.call{value: bal}("");
require(sent, "Failed to send Ether");
address sender = msg.sender;
balances[sender] = 0;
}
}

2. set up the asolcโ€‹

The compilation of the contract requires the utilization of asolc. For more information on asolc, please refer to What is ASOLC

Download the latest ASOLC releases in here.

## set up ASOLC to environment variables
$ export PATH= {your asolc path}:$PATH

## confirm that ASOLC is installed successfully
$ asolc --version
solc, the solidity compiler commandline interface
Version: 0.8.21-develop.2023.10.27+commit.d545edb7.Darwin.appleclang

3. compile contractโ€‹

Update package.json 'contract:build' using asolc:

{
"contract:build": "asolc -o ./build/contract/ --via-ir --abi --storage-layout --bin ./contracts/*.sol --overwrite"
}

Execute the contract compilation command

npm run contract:build

If the command is executed successfully, the following file will be generated in the './build/contract' directory.

.
โ”œโ”€โ”€ build
โ”‚ย ย  โ”œโ”€โ”€ contract
โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ HoneyPot.abi
โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ HoneyPot.bin
โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ HoneyPot_storage.json

4. generate classโ€‹

npm run aspect:gen

Specifically, the command will be executed following command, and to generates ./assembly/aspect/honeypot_storage.ts class.

aspect-tool generate -i ./build/contract -o ./assembly/aspect

5. how to workโ€‹

The following code shows how to judge the balance change of the current contract and compare the state change of balances map that key==currentCall.from in the contract, if it is not the same, revert transaction

import {
allocate,
BigInt,
entryPoint,
execute,
IPostContractCallJP,
PostContractCallInput,
sys,
uint8ArrayToHex,
} from '@artela/aspect-libs';
import { HoneyPotState } from './contract/honeypot-storage';

class GuardBTraceAspect implements IPostContractCallJP {
isOwner(sender: Uint8Array): bool {
const value = sys.aspect.property.get<Uint8Array>('owner');
return uint8ArrayToHex(value).includes(uint8ArrayToHex(sender));
}

postContractCall(input: PostContractCallInput): void {
const mytest = sys.aspect.property.get<string>('mytest-key');
sys.require(mytest === 'test abc ', 'failed to get property key.');

// 1.Calculate the eth balance change of DeFi SmartContract(HoneyPot) before and after tx.
const to = uint8ArrayToHex(input.call!.to);
const from = uint8ArrayToHex(input.call!.from);
const sysBalance = new HoneyPotState._balance_(to);
const deltaSys = sysBalance.current()!.sub(sysBalance.original());

// 2.Calculate the financial change of withdrawer in DeFi SmartContract(HoneyPot) before and after tx.
const contractState = new HoneyPotState.balances(to);

let deltaUser = BigInt.ZERO;

const fromState = contractState.get(from);

const current = fromState.current();
const original = fromState.original();
if (current && original) {
deltaUser = current.sub(original);
}
// 3.Verify if the above two values are equal.
if (deltaSys.compareTo(deltaUser) != 0) {
sys.revert('risky transaction');
}
}
}

// 2.register aspect Instance
const aspect = new GuardBTraceAspect();
entryPoint.setAspect(aspect);

// 3.must export it
export { execute, allocate };

6. compile Aspectโ€‹

Execute the Aspect compilation command

npm run aspect:build

If the command is executed successfully, WebAssembly is generated in the build directory