0

I want to create a call from a smart contract A to a smart contract B.

Premisses:

  • I have enough ether balance in A
  • I do not know what is the name of B's function to be called
  • B's function is payable and do not accept any arguments
  • hexData is the SHA3 of the name of the B's function
  • I do not want to delegate A's storage
  • I want to fail if B's function caused an EVM exception
  • Two questions:

    a) Is it the best way to codify this need?

    function newTransferToB(address B, uint256 valueInWei, bytes4 hexData) onlyOwner {
    
        if(!B.call.value(valueInWei)(hexData)) revert();  
    
    }
    

    b) What if I do not know the number of arguments of B's function?

    3

    Q1) Require

    will throw and revert the whole state if the requirement is not meant, meaning if your call fails in the transaction the state will be discarded / not saved.

    <!-- language: solc -->
    
    require( _addr.call.value(valueInWei)(hexData), "Transfer failed" );  
    

    Q2) Multiple method arguments

    The way the current solidity abi and abi.v2 work, is as follows:

    First 4 bytes of a call represent the method we're calling, and the rest of the buffer will contain abi encoded parameters.

    Which means, we don't need to know anything about said hexBuffer and all we have to do is relay it.

    Code example with tests:

    <!-- language: solc -->
    
    pragma solidity 0.4.25;
    
    contract B {
    
        event received( uint256 );
    
        uint256 public lastReceivedUint = 0;
    
        function receiver() public payable {
            emit received( msg.value );
        }
    
        function receiverWithArgs( uint256 test ) public payable {
            lastReceivedUint = test;
            emit received( msg.value );
        }
    
        function badCall() public payable {
            revert();
        }
    }
    
    
    contract A {
    
        event transferValue( uint256 );
    
        // fallback method that accepts value transfer
        function() payable public {
    
        }
    
        // hexData can accept any number of bytes, as they are relayed to the next call
        function newTransferToB(address _addr, uint256 valueInWei, bytes hexData) public payable {
            emit transferValue( valueInWei );
            require( _addr.call.value(valueInWei)(hexData), "Transfer failed" );  
        }
    }
    
    contract Test {
    
        A public Contract_A;
        B public Contract_B;
    
        constructor() public payable {
            // require a value
            require(msg.value > 0, "Value required");
    
            // deploy our contracts
            Contract_A = new A();
            Contract_B = new B();
    
            // transfer the value we received to A
            address(Contract_A).transfer(address(this).balance);
        }
    
        function show_balance_A() public view returns(uint256) {
            return address(Contract_A).balance;
        }
    
        function show_balance_B() public view returns(uint256) {
            return address(Contract_B).balance;
        }
    
        // send 10 wei from A to B "receiver method"
        function testTransferTo_B_No_Args() public {
    
            // save initial balance
            uint256 BalanceBInitial = address(Contract_B).balance;
    
            uint256 sendValue = 10; // 10 wei
    
            Contract_A.newTransferToB( 
                address(Contract_B),
                sendValue,
                abi.encode(bytes4(keccak256("receiver()")))
            );
    
            // validate result
            assert( address(Contract_B).balance == BalanceBInitial + sendValue);
        }
    
        // send 100 wei from A to B "receiverWithArgs( uint256 test ) method"
        function testTransferTo_B_With_Args() public {
    
    
            // save initial balance
            uint256 BalanceBInitial = address(Contract_B).balance;
    
            uint256 sendValue = 100; // 100 wei
            uint256 uintParamValue = 5; 
    
            Contract_A.newTransferToB( 
                address(Contract_B),
                sendValue,
                abi.encodeWithSelector(bytes4(keccak256("receiverWithArgs(uint256)")), uintParamValue)
            );
    
            // validate result
            assert( address(Contract_B).balance == BalanceBInitial + sendValue);
            assert( Contract_B.lastReceivedUint() == uintParamValue);
    
        }
    
        // try to send more than available
        function testTransferTo_B_More_than_Available_should_throw() public {
    
            uint256 BalanceAInitial = address(Contract_A).balance;
            uint256 sendValue = BalanceAInitial + 1; 
    
            Contract_A.newTransferToB( 
                address(Contract_B),
                sendValue,
                abi.encode(bytes4(keccak256("receiver()")))
            );
    
            // should never reach here as the whole transaction gets reverted
    
        }
    }
    

    Usage:

    1. Deploy the contract and send at least 1000 wei to it when constructing.
    2. You can use show_balance_A / B to view contract balances.
    3. call test.testTransferTo_B_No_Args() to transfer and validate Q1
    4. call test.testTransferTo_B_With_Args() to transfer and validate Q2
    5. call test.testTransferTo_B_More_than_Available_should_throw() to validate that the method throws / reverts properly.

    Let me know if you have other questions.

    Your Answer

    By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

    Not the answer you're looking for? Browse other questions tagged or ask your own question.