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