godwokenrises / godwoken-web3

Moved to monorepo https://github.com/godwokenrises/godwoken/tree/develop/web3
14 stars 17 forks source link

Multi-layer cross-contract calls are inconsistent with other blockchain platforms #241

Open gpBlockchain opened 2 years ago

gpBlockchain commented 2 years ago

env: https://github.com/RetricSu/godwoken-kicker.git branch: compatibility-changes commit: 76430974cc0b8f88e2bca659725cb8664d3b78d2 solodity code

pragma solidity ^0.6.10;

contract CrossCallTest{

    CrossCall cc;
    CrossCall cc1;

    constructor () public {
        cc = new CrossCall();
        cc1 = new CrossCall();
        cc.setOtherAddress(address(cc1));
        cc1.setOtherAddress(address(cc));
    }
    event throwTestResult(uint256 idx,bool result,bytes bts);

    function call_throwTest_1() public {
        cc.modType(1);
        bytes memory  bb;
        try cc.throwTest() returns(bool result){
            // emit throwTestResult(0,true,bb);
            require(false,"cc.throwTest() returns(bool result)");
        } catch Error(string memory bt1){
            // emit throwTestResult(1,true,bytes(bt1));
            require(false,"cc.throwTest() returns(bool result)");
        }catch(bytes memory bt2){
            emit throwTestResult(2,false,bt2);
        }
    }

    function call_throwTest_2() public {
        cc.modType(2);
        bytes memory  bb;
        try cc.throwTest() returns(bool result){
            // emit throwTestResult(0,true,bb);
            require(false,"cc.throwTest() returns(bool result)");
        } catch Error(string memory bt1){
            // emit throwTestResult(1,true,bytes(bt1));
            require(keccak256(bytes(bt1)) == keccak256("require throw test"),"require throw test");
        }catch(bytes memory bt2){
            require(false,"cc.throwTest(2) returns(bool result)");
        }
    }

    function call_throwTest_3() public {
        cc.modType(3);
        bytes memory  bb;
        try cc.throwTest() returns(bool result){
            // emit throwTestResult(0,true,bb);
            require(result == true,"cc.throwTest(3) returns(bool result)");
        } catch Error(string memory bt1){
            // emit throwTestResult(1,true,bytes(bt1));
            require(keccak256(bytes(bt1)) == keccak256("require throw test"),"require throw test");

        }catch(bytes memory bt2){
            require(false,"cc.throwTest(2) returns(bool result)");
        }
    }

    function call_throwTest_4() public {
        cc.modType(4);
        bytes memory  bb;
        try cc.throwTest() returns(bool result){
            // emit throwTestResult(0,true,bb);
            require(false,"exec failed");
        } catch Error(string memory bt1){
            // emit throwTestResult(1,true,bytes(bt1));
            require(keccak256(bytes(bt1)) == keccak256("require throw test"),"chatch 1");

        }catch(bytes memory bt2){
            // require(false,"cc.throwTest(4) catch 2");
        }
    }

    function call_throwTest_5() public {
        cc.modType(5);
        bytes memory  bb;
        try cc.throwTest() returns(bool result){
            // emit throwTestResult(0,true,bb);
            require(true,"exec failed");
        } catch Error(string memory bt1){
            // emit throwTestResult(1,true,bytes(bt1));
            require(keccak256(bytes(bt1)) == keccak256("require throw test"),"chatch 1");

        }catch(bytes memory bt2){
            require(false,"cc.throwTest(5) catch 2");
        }
    }

    function call_1() public returns(bool){
        uint256 beginSize = cc1.getType1();
        cc.callTestFunc(address(cc1),500000000,0,"addModType()");
        require(cc1.getType1() == beginSize+1,"addModType failed");
        return true;
    }

    function call_stack(uint stackSize) public returns(uint256){
        return cc.call_stack(stackSize-1)+stackSize;
    }

    function call_out_of_gas() public returns(bool){
        uint256 beginSize = cc1.getType1();
        try cc.callTestFunc(address(cc1),5,0,"addModType()") returns(bool result,bytes memory bts){
            require(!result,"can't do this");
        }catch{}
        require(cc1.getType1() == beginSize,"addModType failed");
        return true;
    }

    function call_delegatecallFunc() public returns(bool){
        uint256 beginSize = cc.getType1();
        cc.delegatecallFunc(address(cc1),500000000,"addModType()");
        require(cc.getType1() == beginSize+1,"addModType failed");
        return true;
    }

    function call_staticcallFunc() public returns(bool){
        uint256 beginSize = cc.getType1();
        uint256 cc1BeginSize = cc1.getType1();
        try cc.staticcallFunc(address(cc1),500000000,"addModType()") returns(bool result){
            require(!result,"exec must failed");

        }catch {

        }
        require(cc1.getType1() == beginSize,"type must not mod ");
        require(cc.getType1() == beginSize,"type must not mod ");
        return true;
    }

    function call_throwTest(uint idx) public{
        cc.modType(idx);
        bytes memory  bb;
        try cc.throwTest() returns(bool result){

            emit throwTestResult(0,true,bb);

        }catch Error(string memory bt1 ) {
            // This is executed in case
            // revert was called inside getData
            // and a reason string was provided.
            require(keccak256(bytes(bt1)) == keccak256("require throw test"),"require throw test");
            emit throwTestResult(1,true,bytes(bt1));

        }  catch (bytes memory bt2) {
            // This is executed in case revert() was used.
            emit throwTestResult(2,false,bt2);
        }

    }

}
contract CrossCall{

    address payable otherAddress;

    function setOtherAddress(address payable addr) public {
        otherAddress = addr;
    }
    event typeModMsg(uint256);
    uint256 public  type1;

    function getType1() public returns(uint256){
        return type1;
    }

    function callTestFunc(address addr,uint256 useGas,uint valuedata,string memory  func) public payable returns (bool,bytes memory){
        return  addr.call{value:valuedata,gas:useGas}(abi.encodeWithSignature(func));
    }

    function call_stack(uint stackSize) public returns(uint256){
        CrossCall cc = CrossCall(otherAddress);
        if(stackSize<=0){
            return 1;
        }
        try cc.call_stack(stackSize-1) returns(uint256 num){
            return num+stackSize;
        }catch {
            return cc.call_stack{gas:100000}(stackSize-1);
        }
    }

    //todo add callcode
    // function callcodeFunc(address addr,uint256 useGas,uint valuedata,string memory func) public payable returns (bool,bytes memory){
    // return addr.callcode.value(valuedata).gas(useGas)(bytes4(keccak256(func)));
    // return  addr.callcode{value:valuedata,gas:useGas}(abi.encodeWithSignature(func));
    // }

    function delegatecallFunc(address addr,uint256 useGas,string memory func) public payable returns (bool,bytes memory){
        // return addr.delegatecall.gas(useGas)(bytes4(keccak256(func)));
        return  addr.delegatecall{gas:useGas}(abi.encodeWithSignature(func));

    }

    function staticcallFunc(address addr,uint256 useGas,string memory func)public payable returns (bool){
        bool success;
        bytes4 sig = bytes4(keccak256(bytes(func)));

        assembly{
            let x := mload(0x40)   //Find empty storage location using "free memory pointer"
            mstore(x,sig) //Place signature at begining of empty storage

            success := staticcall(
            useGas, //5k gas
            addr, //To addr
            x,    // Inputs are at location x
            0x40, //Inputs size two padded, so 68 bytes
            x,    //Store output over input
            0x00) //Output is 32 bytes long        }

        }
        return success;
    }

    uint public test_count=1;

    function callTest(address addr,uint256 useGas,uint valuedata) public payable returns (bool){
        // return addr.call.value(valuedata).gas(useGas)(bytes4(keccak256("test()")));
        (bool successed,) =  addr.call{value:valuedata,gas:useGas}(abi.encodeWithSignature("test()"));
        return successed;
    }

    function newTest(bool isThrow) public returns(bool){
        A a = new A(isThrow);
        return true;
    }

    receive() external payable{

    }

    // function() payable{}

    function getType() public view returns(uint256) {
        return type1;
    }

    function addModType()public returns(bool){
        type1+=1;
        emit typeModMsg(type1);
    }

    function modType(uint256 intdata) public returns(uint256){
        type1 = intdata;
        emit typeModMsg(type1);

        return type1;
    }
    function throwTest() public payable returns(bool){
        RollBackContract contract1 = new RollBackContract();
        if(type1 == 1){
            contract1.assertThrow();
        }
        if(type1 == 2){
            contract1.requiteThrow();
        }
        if(type1 == 3){
            contract1.blanceNotEnough();
        }
        if(type1 == 4){
            contract1.invalOpCode();
        }
        if(type1 == 5){
            contract1.stop();
        }
        return true;
    }

}

contract RollBackContract{
    event time(uint256);

    constructor() public  payable {
        A a = new A(true);
    }
    function new_throw(uint256 idx) public payable{

        A a = new A(false);
        a.test(msg.sender,address(this));

        check_throw(idx);
    }

    function suicide_throw(uint256 idx) public returns(bool){
        A a = new A(true);
        a.test1(msg.sender);
        check_throw(idx);
        return true;
    }

    function check_throw(uint256 idx) internal{
        // time(now);
        if(idx%10 > 7){
            assert(false);
        }
        if(idx%10<2){
            require(false,"time is too smill");
        }
    }

    function assertThrow() public payable{
        emit time(100);
        assert(false);
    }

    function requiteThrow() public payable{
        emit time(100);
        require(false,"require throw test");

    }

    function blanceNotEnough() public payable returns(bool){
        return msg.sender.send(10);
    }

    function invalOpCode() public payable{

        assembly{ invalid() }

    }
    function stop() public payable{
        assembly{ stop() }
    }

}
contract A{
    event logKV(address key,address value);
    mapping(address => address) hashMap;
    bool isThrow;
    constructor(bool isThrow) public payable{
        logKV(msg.sender,msg.sender);
        isThrow = isThrow;
        require(isThrow,"time is too smill");

    }

    function test(address key,address value) public returns(address){
        hashMap[key] = value;
        logKV(key,value);
        return value;
    }

    function test1(address addr) public{
        selfdestruct(payable(addr));
    }

}
  1. deploy CrossCallTest
  2. invoke call_stack(5)
    
    Error: transaction failed [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ] (transactionHash="0xe8f5331987afde47ebbb7cf9fd77d1fd8b758fed21b42c2bdac1524ae021444c", transaction={"hash":"0xe8f5331987afde47ebbb7cf9fd77d1fd8b758fed21b42c2bdac1524ae021444c","type":0,"accessList":null,"blockHash":"0xea252193bf3205ea5b7675a5bea342f359b5952845098e3ec6e4751d077ae1d2","blockNumber":411,"transactionIndex":0,"confirmations":1,"from":"0x0000000000000000000000000000000000000000","gasPrice":{"type":"BigNumber","hex":"0x01"},"gasLimit":{"type":"BigNumber","hex":"0x3c7d"},"to":null,"value":{"type":"BigNumber","hex":"0x00"},"nonce":0,"data":"0x","r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000","v":0,"creates":"0xBd770416a3345F91E4B34576cb804a576fa48EB1","chainId":0}, receipt={"to":null,"from":"0x0000000000000000000000000000000000000000","contractAddress":null,"transactionIndex":0,"gasUsed":{"type":"BigNumber","hex":"0x3c7d"},"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","blockHash":"0xea252193bf3205ea5b7675a5bea342f359b5952845098e3ec6e4751d077ae1d2","transactionHash":"0xe8f5331987afde47ebbb7cf9fd77d1fd8b758fed21b42c2bdac1524ae021444c","logs":[],"blockNumber":411,"confirmations":1,"cumulativeGasUsed":{"type":"BigNumber","hex":"0x3c7d"},"status":0,"type":0,"byzantium":true}, code=CALL_EXCEPTION, version=providers/5.6.1)
      at Logger.makeError (node_modules/@ethersproject/logger/src.ts/index.ts:261:28)
      at Logger.throwError (node_modules/@ethersproject/logger/src.ts/index.ts:273:20)
      at EthersProviderWrapper.<anonymous> (node_modules/@ethersproject/providers/src.ts/base-provider.ts:1523:24)
      at step (node_modules/@ethersproject/providers/lib/base-provider.js:48:23)
      at Object.next (node_modules/@ethersproject/providers/lib/base-provider.js:29:53)
      at fulfilled (node_modules/@ethersproject/providers/lib/base-provider.js:20:58)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
4. invoke call_stack(100)

ProviderError: Cannot read property 'last_log' of undefined at HttpProvider.request (node_modules/hardhat/src/internal/core/providers/http.ts:74:19) at LocalAccountsProvider.request (node_modules/hardhat/src/internal/core/providers/accounts.ts:188:34) at processTicksAndRejections (node:internal/process/task_queues:96:5) at EthersProviderWrapper.send (node_modules/@nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:20)



expected
invoke success

actions
hardhat local net:https://github.com/gpBlockchain/gw-evm-opcode-test/runs/5733029647
godwoken net : https://github.com/gpBlockchain/gw-evm-opcode-test/runs/5733665185?check_suite_focus=true
### Reproduce the problem
1.fork https://github.com/gpBlockchain/gw-evm-opcode-test.git
2. exec actions
<img width="349" alt="image" src="https://user-images.githubusercontent.com/32102187/160567313-332906a9-9c4d-43f3-bdbf-edb4dca47540.png">
magicalne commented 2 years ago

invoke call_stack(5)

We can make it pass by setting more gas in hardhat config.

invoke call_stack(100)

This one consumes way a lot more memory than we can support. For this contract, call_stack(13) should be fine.

Flouse commented 2 years ago

doc about Restriction of memory usage

https://github.com/nervosnetwork/godwoken-polyjuice/blob/main/docs/EVM-compatible.md#restriction-of-memory-usage