Skip to content
This repository has been archived by the owner on Mar 24, 2023. It is now read-only.

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

Open
gpBlockchain opened this issue Mar 29, 2022 · 2 comments
Assignees

Comments

@gpBlockchain
Copy link

gpBlockchain commented Mar 29, 2022

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)

  1. 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
image

@Flouse Flouse assigned classicalliu and magicalne and unassigned classicalliu Apr 6, 2022
@magicalne
Copy link
Contributor

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
Copy link
Collaborator

Flouse commented May 25, 2022

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants