15

I would like to call a contract and deal with the return values manually using EVM assembly in Solidity. For example, this should simply add two numbers together.

contract Test1 {
    function add(int a, int b) returns(int){  //Simply add the two arguments and return
        return a+b;
    }
    function() returns (int){  //If the function signature doesn't check out, return -1
        return -1;
    }
}

contract Test2 {
    Test1 test1;

    function Test2(){  //Constructor function
        test1 = new Test1();  //Create new "Test1" function
    }

    function test(int a, int b) constant returns (int c){
        address addr = address(test1);  //Place the test1 address on the stack
         bytes4 sig = bytes4(sha3("add(int256,int256)")); //Function signature

        assembly {
            let x := mload(0x40)   //Find empty storage location using "free memory pointer"
            mstore(x,sig) //Place signature at begining of empty storage 
            mstore(add(x,0x04),a) //Place first argument directly next to signature
            mstore(add(x,0x24),b) //Place second argument next to first, padded to 32 bytes

            call(5000, addr, 0, //Issue call, providing 5k gas and 0 value to "addr"
            x, 0x44, add(x,0x80), 0x20) //Inputs start at location "x" and are 68 bytes long, outputs start 128 bytes after x, and are 32 bytes long
            c := mload(add(x,0x80)) //Assign output value to c
            mstore(0x40,add(x,0x100)) // Set storage pointer to empty space
        }
    }

    function test2(int a, int b) constant returns(int c){ //Make sure the Test1 function works properly
        return test1.add(a,b); // (It does)
    }
}

The issue is that this is returning an Out-of-Gas error, originating from the call... line.

How can I fix this issue?

5
  • Not a comment on your problem but just curious what putting assembly in Solidity is for and why not coding in Solidity except for mind challenge? Commented Jun 20, 2016 at 21:43
  • 1
    There's some reasons here, and in this case it's because I need to retrieve a dynamically sized byte array from another contract, which can't be done automatically since the EVM needs to allocate memory for return data beforehand Commented Jun 20, 2016 at 21:56
  • Thanks, very interesting even if it's out of reach for me to write such low level code 😥 Commented Jun 20, 2016 at 22:06
  • 1
    You really should try, it's really useful and even just the simple things like copying memory into new variables makes dealing with arrays and strings way easier Commented Jun 20, 2016 at 22:38
  • 1
    Perhaps, someday, who knows, when I will be very confident... Commented Jun 20, 2016 at 22:40

1 Answer 1

17

The error was due to an unhandled item on the stack that was left by the call opcode. The working and optimized relevant code is here:

    assembly {
        let x := mload(0x40)   //Find empty storage location using "free memory pointer"
        mstore(x,sig) //Place signature at begining of empty storage 
        mstore(add(x,0x04),a) //Place first argument directly next to signature
        mstore(add(x,0x24),b) //Place second argument next to first, padded to 32 bytes

        let success := call(      //This is the critical change (Pop the top stack value)
                            5000, //5k gas
                            addr, //To addr
                            0,    //No value
                            x,    /Inputs are stored at location x
                            0x44, //Inputs are 68 bytes long
                            x,    //Store output over input (saves space)
                            0x20) //Outputs are 32 bytes long

        c := mload(x) //Assign output value to c
        mstore(0x40,add(x,0x44)) // Set storage pointer to empty space
    }

Thanks to @chriseth for pointing out my error

EDIT:

As @Ilan pointed out, the final mstore is not strictly necessary, since we don't care about keeping that memory allocated. If the returned data is a heap object like an array, then you need to make sure that the memory stays allocated.

8
  • The 5th parameter to call is measured in bytes, not bits. SE won't let me edit to correct because it isn't "a big enough change" despite it having massive impact on the accuracy of the answer. :/ Commented Dec 8, 2017 at 6:16
  • 1
    Good catch, edited Commented Dec 8, 2017 at 16:59
  • @TjadenHess question: when setting storage pointer to empty space. since input not needed anymore. can u use x + 0x20 like so: mstore(0x40,add(x,0x20)); Commented Mar 5, 2019 at 16:30
  • Actually, you don't need to set it at all, since you've already loaded the value onto the stack Commented Mar 6, 2019 at 1:05
  • @TjadenHess after playing more with the code there are 3 issues here. 1. setting output over input will not work in some functions, where some output value might be set before using input for the last time. 2. free memory pointer should be set before calling other function which might use it as well. 3. after function call was completed. the free memory pointer should be set back to x, which is the starting position it was before the function call happened. Commented Mar 6, 2019 at 18:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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