11

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?

  • 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? – Nicolas Massart Jun 20 '16 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 – Tjaden Hess Jun 20 '16 at 21:56
  • Thanks, very interesting even if it's out of reach for me to write such low level code 😥 – Nicolas Massart Jun 20 '16 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 – Tjaden Hess Jun 20 '16 at 22:38
  • 1
    Perhaps, someday, who knows, when I will be very confident... – Nicolas Massart Jun 20 '16 at 22:40
12

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.

  • 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. :/ – Micah Zoltu Dec 8 '17 at 6:16
  • 1
    Good catch, edited – Tjaden Hess Dec 8 '17 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)); – Ilan Dshare Mar 5 at 16:30
  • Actually, you don't need to set it at all, since you've already loaded the value onto the stack – Tjaden Hess Mar 6 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. – Ilan Dshare Mar 6 at 18:18

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.