From f5d6f1cd1e5cba969136651398b88438d67f6057 Mon Sep 17 00:00:00 2001 From: cmgCr Date: Fri, 16 May 2025 20:29:39 +0800 Subject: [PATCH] test(examples): support forge test for example foundry_erc20 --- .github/workflows/dtvm_sol_test.yml | 2 + examples/foundry_erc20/build.sh | 41 ++----- examples/foundry_erc20/build_forge_test.sh | 27 +++++ examples/foundry_erc20/test/Contract.t.sol | 23 ---- examples/foundry_erc20/test/TestContract.sol | 109 +++++++++++++++++++ examples/foundry_erc20/test_forge_test.sh | 38 +++++++ examples/scripts/WasmTestVM.sol | 9 ++ tools/build_utils.sh | 56 ++++++++++ tools/forge_test_utils.sh | 46 ++++++++ 9 files changed, 298 insertions(+), 53 deletions(-) create mode 100755 examples/foundry_erc20/build_forge_test.sh delete mode 100644 examples/foundry_erc20/test/Contract.t.sol create mode 100644 examples/foundry_erc20/test/TestContract.sol create mode 100755 examples/foundry_erc20/test_forge_test.sh create mode 100644 examples/scripts/WasmTestVM.sol create mode 100755 tools/build_utils.sh create mode 100755 tools/forge_test_utils.sh diff --git a/.github/workflows/dtvm_sol_test.yml b/.github/workflows/dtvm_sol_test.yml index 6406c77..df96eb9 100644 --- a/.github/workflows/dtvm_sol_test.yml +++ b/.github/workflows/dtvm_sol_test.yml @@ -80,6 +80,8 @@ jobs: ./build.sh debug ./test_my_token.sh ./test_token_factory.sh + ./build_forge_test.sh debug + ./test_forge_test.sh echo "testing examples/perf_example" cd $CUR_PATH/examples/perf_example diff --git a/examples/foundry_erc20/build.sh b/examples/foundry_erc20/build.sh index 7bc7920..088529b 100755 --- a/examples/foundry_erc20/build.sh +++ b/examples/foundry_erc20/build.sh @@ -1,45 +1,26 @@ #!/bin/bash set -e +source ../../tools/build_utils.sh + # install solidity # https://docs.soliditylang.org/en/latest/installing-solidity.html # install foundry # curl -L https://foundry.paradigm.xyz | bash -# Determine the build mode -BUILD_MODE=${1:-release} - -echo "Building in $BUILD_MODE mode" - -# --enable-little-endian-storage-load-store -YUL2WASM_EXTRA_ARGS="--verbose" - -# if env ENABLE_LITTLE_ENDIAN_STORAGE == "ON", then add --enable-little-endian-storage-load-store -if [ "$ENABLE_LITTLE_ENDIAN_STORAGE" == "ON" ]; then - YUL2WASM_EXTRA_ARGS="$YUL2WASM_EXTRA_ARGS --enable-little-endian-storage-load-store" -fi - -# Set the yul2wasm path based on the build mode -if [ "$BUILD_MODE" == "release" ]; then - YUL2WASM_PATH="../../target/release/yul2wasm" -else - YUL2WASM_PATH="../../target/debug/yul2wasm" - YUL2WASM_EXTRA_ARGS="$YUL2WASM_EXTRA_ARGS --debug" -fi - -# npm install @openzeppelin/contracts -# solc --ir --optimize-yul -o . --overwrite TokenFactory.sol MyToken.sol +setup_build_mode ${1:-release} +forge clean forge build --extra-output-files ir-optimized # for debug: forge build --extra-output-files ir # ir generated in out/TokenFactory.sol/TokenFactory.ir -$YUL2WASM_PATH --input out/MyToken.sol/MyToken.iropt --output out/MyToken.wasm $YUL2WASM_EXTRA_ARGS -wasm2wat -o out/MyToken.wat out/MyToken.wasm -echo 'MyToken compiled to wasm in out/MyToken.wasm' +YUL_IR_PATH="out" +# contracts to compile +CONTRACTS=( + "MyToken" + "TokenFactory" +) -# Compile TokenFactory, which will be used to deploy MyToken -$YUL2WASM_PATH --input out/TokenFactory.sol/TokenFactory.iropt --output out/TokenFactory.wasm $YUL2WASM_EXTRA_ARGS -wasm2wat -o out/TokenFactory.wat out/TokenFactory.wasm -echo 'TokenFactory compiled to wasm in out/TokenFactory.wasm' +compile_all_contracts CONTRACTS[@] "$YUL_IR_PATH" diff --git a/examples/foundry_erc20/build_forge_test.sh b/examples/foundry_erc20/build_forge_test.sh new file mode 100755 index 0000000..490cedc --- /dev/null +++ b/examples/foundry_erc20/build_forge_test.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +source ../../tools/build_utils.sh + +# install solidity +# https://docs.soliditylang.org/en/latest/installing-solidity.html + +# install foundry +# curl -L https://foundry.paradigm.xyz | bash + +setup_build_mode ${1:-release} + +forge clean +cp ../scripts/WasmTestVM.sol src/WasmTestVM.sol +forge test --extra-output-files ir-optimized +rm src/WasmTestVM.sol + +YUL_IR_PATH="out" + +# contracts to compile +CONTRACTS=( + "WasmTestVM" + "TestContract" +) + +compile_all_contracts CONTRACTS[@] "$YUL_IR_PATH" diff --git a/examples/foundry_erc20/test/Contract.t.sol b/examples/foundry_erc20/test/Contract.t.sol deleted file mode 100644 index 2852dcd..0000000 --- a/examples/foundry_erc20/test/Contract.t.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; - -import "src/MyToken.sol"; - -contract TestContract is Test { - MyToken c; - - function setUp() public { - c = new MyToken(100000); - } - - function testBar() public { - assertEq(uint256(1), uint256(1), "ok"); - } - - function testFoo(uint256 x) public { - vm.assume(x < type(uint128).max); - assertEq(x + x, x * 2); - } -} diff --git a/examples/foundry_erc20/test/TestContract.sol b/examples/foundry_erc20/test/TestContract.sol new file mode 100644 index 0000000..d78be82 --- /dev/null +++ b/examples/foundry_erc20/test/TestContract.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +import "src/MyToken.sol"; + +contract TestContract is Test { + MyToken c; + + address owner = address(this); + address user2 = address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4); + address user3 = address(0x5B38Da6a701C568545DcFCb03fcb875F56BEDDc5); + + uint256 constant INITIAL_SUPPLY = 1000; + uint256 constant MINT_AMOUNT = 7; + uint256 constant TRANSFER_AMOUNT = 5; + uint256 constant APPROVE_AMOUNT = 10; + + function setUp() public { + c = new MyToken(INITIAL_SUPPLY); + } + + function testDeployAndTotalSupply() public { + assertEq(c.totalSupply(), INITIAL_SUPPLY, "Initial total supply should match"); + } + + function testMint() public { + uint256 user2_balance = c.balanceOf(user2); + c.mint(user2, MINT_AMOUNT); + assertEq(c.balanceOf(user2), user2_balance + MINT_AMOUNT, "User2 balance should be correct after mint"); + } + + function testApproveAndAllowance() public { + uint256 user2_allowance = c.allowance(owner, user2); + c.approve(user2, APPROVE_AMOUNT); + assertEq(c.allowance(owner, user2), user2_allowance + APPROVE_AMOUNT, "Allowance should be set correctly"); + } + + function testTransfer() public { + assertEq(c.balanceOf(address(this)), INITIAL_SUPPLY, "Owner balance should be 0 before testTransfer"); + c.mint(address(this), MINT_AMOUNT); + assertEq(c.balanceOf(address(this)), INITIAL_SUPPLY + MINT_AMOUNT, "Owner balance should be 5 before transfer"); + + uint256 user2_balance = c.balanceOf(user2); + c.transfer(user2, TRANSFER_AMOUNT); + + assertEq(c.balanceOf(user2), user2_balance + TRANSFER_AMOUNT, "User2 should receive correct amount"); + assertEq(c.balanceOf(address(this)), INITIAL_SUPPLY + MINT_AMOUNT - TRANSFER_AMOUNT, "Owner balance should be decreased"); + } + + event ValueLogged(uint256 value); + + event AddressLogged(address value); + + function testTransferFrom() public { + emit AddressLogged(address(this)); + uint256 user2_allowance = c.allowance(address(this), user2); + vm.startPrank(owner); + uint256 this_balance = c.balanceOf(address(this)); + emit ValueLogged(this_balance); + c.mint(address(this), MINT_AMOUNT); + emit ValueLogged(c.balanceOf(address(this))); + c.approve(user2, APPROVE_AMOUNT); + vm.stopPrank(); + + vm.startPrank(user2); + uint256 user3_balance = c.balanceOf(user3); + emit ValueLogged(user3_balance); + c.transferFrom(address(this), user3, APPROVE_AMOUNT); + vm.stopPrank(); + emit ValueLogged(c.balanceOf(address(this))); + + assertEq(c.balanceOf(user3), user3_balance + APPROVE_AMOUNT, "User3 should receive correct amount"); + assertEq(c.balanceOf(address(this)), this_balance + MINT_AMOUNT - APPROVE_AMOUNT, "Owner balance should be decreased"); + assertEq(c.allowance(address(this), user2), user2_allowance + APPROVE_AMOUNT - APPROVE_AMOUNT, "Allowance should be decreased"); + } + + // CompleteFlow: deploy -> mint -> transfer -> approve -> transferFrom + function testCompleteFlow() public { + uint256 owner_balance = c.balanceOf(owner); + uint256 user2_balance = c.balanceOf(user2); + uint256 user2_allowance_before_approve = c.allowance(owner, user2); + uint256 user3_balance = c.balanceOf(user3); + + vm.startPrank(owner); + + assertEq(c.totalSupply(), INITIAL_SUPPLY, "Initial total supply should match"); + + c.mint(owner, MINT_AMOUNT); + assertEq(c.balanceOf(owner), owner_balance + MINT_AMOUNT, "Owner should receive minted tokens"); + + c.transfer(user2, TRANSFER_AMOUNT); + assertEq(c.balanceOf(user2), user2_balance + TRANSFER_AMOUNT, "User2 should receive transferred tokens"); + + c.approve(user2, APPROVE_AMOUNT); + uint256 user2_allowance_after_approve = c.allowance(owner, user2); + assertEq(c.allowance(owner, user2), user2_allowance_before_approve + APPROVE_AMOUNT, "Allowance should be set correctly"); + + vm.stopPrank(); + + vm.startPrank(user2); + c.transferFrom(owner, user3, APPROVE_AMOUNT); + vm.stopPrank(); + + assertEq(c.balanceOf(user3), user3_balance + APPROVE_AMOUNT, "User3 should receive correct amount"); + assertEq(c.allowance(owner, user2), user2_allowance_after_approve - APPROVE_AMOUNT, "Allowance should be decreased"); + } +} diff --git a/examples/foundry_erc20/test_forge_test.sh b/examples/foundry_erc20/test_forge_test.sh new file mode 100755 index 0000000..6b7423e --- /dev/null +++ b/examples/foundry_erc20/test_forge_test.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +cd .. +source ../tools/forge_test_utils.sh +cd foundry_erc20 + +YUL_IR_PATH="out" +wasm_vm_file="$YUL_IR_PATH/WasmTestVM.wasm" +contract_file="$YUL_IR_PATH/TestContract.wasm" +# deploy WasmTestVM, Cheat code address from: abstract contract CommonBase, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D +WASM_TEST_VM_DEPLOY_ADDR=0x7109709ecfa91a80626ff3989d68f67f5b1dd12d +DEPLOYER_INITIALIZER_ADDR=0x11bbccddeeffaabbccddeeffaabbccddeeffaa11 + +run_single_test() { + init_test "$wasm_vm_file" "$WASM_TEST_VM_DEPLOY_ADDR" "$contract_file" "$DEPLOYER_INITIALIZER_ADDR" + + local wasm_file=$1 + local function_name=$2 + local expected_result=$3 + call_contract_function "$wasm_file" "$DEPLOYER_INITIALIZER_ADDR" "$function_name" "$expected_output" + echo "Test success: $function_name" +} + +# testDeployAndTotalSupply() - 0x39eb0c5c +run_single_test $contract_file "testDeployAndTotalSupply()" 'evm finish with result hex: 00000000000000000000000000000000000000000000000000000000000003e8' +# testMint() - 0x9642ddaf +run_single_test $contract_file "testMint()" 'evm finish with result hex: 0000000000000000000000000000000000000000000000000000000000000007' +# testApproveAndAllowance() - 0xba5af22d +run_single_test $contract_file "testApproveAndAllowance()" 'evm finish with result hex: 0000000000000000000000000000000000000000000000000000000000000001' +# testTransfer() - 0xd591221f +run_single_test $contract_file "testTransfer()" 'evm finish with result hex: 00000000000000000000000000000000000000000000000000000000000003ea' +# testTransferFrom() - 0x70557298 +run_single_test $contract_file "testTransferFrom()" 'evm finish with result hex:' +# testCompleteFlow() - 0xe44962e7 +run_single_test $contract_file "testCompleteFlow()" 'evm finish with result hex:' + +echo "All tests success!" diff --git a/examples/scripts/WasmTestVM.sol b/examples/scripts/WasmTestVM.sol new file mode 100644 index 0000000..28cc5db --- /dev/null +++ b/examples/scripts/WasmTestVM.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +contract WasmTestVM { + /// Asserts that two `int256` values are equal and includes error message into revert string on failure. + function assertEq(uint256 left, uint256 right, string memory err) public pure { + require(left == right, err); + } +} diff --git a/tools/build_utils.sh b/tools/build_utils.sh new file mode 100755 index 0000000..9b216ba --- /dev/null +++ b/tools/build_utils.sh @@ -0,0 +1,56 @@ +#!/bin/bash +set -e + +setup_build_mode() { + local BUILD_MODE=${1:-release} + echo "Building in $BUILD_MODE mode" + + # --enable-little-endian-storage-load-store + YUL2WASM_EXTRA_ARGS="--verbose" + + # if env ENABLE_LITTLE_ENDIAN_STORAGE == "ON", then add --enable-little-endian-storage-load-store + if [ "$ENABLE_LITTLE_ENDIAN_STORAGE" == "ON" ]; then + YUL2WASM_EXTRA_ARGS="$YUL2WASM_EXTRA_ARGS --enable-little-endian-storage-load-store" + fi + + # Set the yul2wasm path based on the build mode + if [ "$BUILD_MODE" == "release" ]; then + YUL2WASM_PATH="../../target/release/yul2wasm" + else + YUL2WASM_PATH="../../target/debug/yul2wasm" + YUL2WASM_EXTRA_ARGS="$YUL2WASM_EXTRA_ARGS --debug" + fi + + export YUL2WASM_PATH + export YUL2WASM_EXTRA_ARGS +} + +compile_contract() { + local contract=$1 + local YUL_IR_PATH=$2 + echo "Compiling $contract..." + + $YUL2WASM_PATH --input $YUL_IR_PATH/$contract.sol/$contract.iropt \ + --output $YUL_IR_PATH/$contract.wasm \ + $YUL2WASM_EXTRA_ARGS + + wasm2wat -o $YUL_IR_PATH/$contract.wat $YUL_IR_PATH/$contract.wasm + + if [ -f "$YUL_IR_PATH/$contract.wasm" ]; then + echo "Successfully compiled $contract to $YUL_IR_PATH/$contract.wasm" + else + echo "Error: Failed to compile $contract" >&2 + exit 1 + fi +} + +compile_all_contracts() { + local contracts=("${!1}") + local YUL_IR_PATH=$2 + + for contract in "${contracts[@]}"; do + compile_contract "$contract" "$YUL_IR_PATH" + done + + echo "All contracts compiled successfully" +} diff --git a/tools/forge_test_utils.sh b/tools/forge_test_utils.sh new file mode 100755 index 0000000..3bba090 --- /dev/null +++ b/tools/forge_test_utils.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -e + +source ../examples/scripts/common.sh +ABI_ENCODE="../scripts/abi_encode.py" +MOCKCLI_PATH="/opt/chain_mockcli" + +DEPLOYER_SENDER=0x9988776655443322119900112233445566778899 + +cleanup() { + if [ -f test.db ]; then + rm -f test.db + fi +} + +deploy_contract() { + local wasm_file=$1 + local deploy_addr=$2 + echo "deploy contract: $wasm_file" + $MOCKCLI_PATH -f $wasm_file --action deploy -s $DEPLOYER_SENDER -t $deploy_addr -i 0x +} + +call_contract_function() { + local wasm_file=$1 + local deploy_addr=$2 + local function_name=$3 + local expected_output=$4 + echo "call contract $wasm_file function $function_name" + ABI_DATA=$($ABI_ENCODE "$function_name") + output=$($MOCKCLI_PATH -f $wasm_file -t $deploy_addr --action call --print-time --enable-gas-meter -s $DEPLOYER_SENDER -i $ABI_DATA) + run_cmd_and_grep "$output" "$expected_output" +} + +init_test() { + cleanup + + local wasm_vm_file=$1 + local WASM_TEST_VM_DEPLOY_ADDR=$2 + local contract_file=$3 + local DEPLOYER_INITIALIZER_ADDR=$4 + + deploy_contract "$wasm_vm_file" "$WASM_TEST_VM_DEPLOY_ADDR" + deploy_contract "$contract_file" "$DEPLOYER_INITIALIZER_ADDR" + + call_contract_function "$contract_file" "$DEPLOYER_INITIALIZER_ADDR" "setUp()" 'evm finish with result hex:' +}