0

I am trying to perform a delegate call from inline-assembly and I am unable to wrap my head around how to access the memory variables assigned outside the assembly block.

function() external payable {
  bytes memory data = abi.encodeWithSignature("modValue(uint256)", 25);
  address _delegate = delegate;
  assembly {
    let result := delegatecall(gas, _delegate, [#1], [#2], 0, 0)
  }
}

If I want to send the data variable as the calldata, then what should be the values in place for [#1] and [#2]? The size of data is 36 bytes here, so [#2] = 36? How can we access the memory location for externally defined variables?

1

The first 32 bytes of a bytes array are the length and the rest is the data.

  • To calculate a pointer to an array's data in assembly it will be add(array, 32).

  • To read the array's length it will be mload(array).

Combining them together it should be

assembly {
    let result := delegatecall(gas, _delegate, add(data, 32), mload(data), 0, 0)
}
2

First, to access variables check the official docs:

For local storage variables or state variables, a single Yul identifier is not sufficient, since they do not necessarily occupy a single full storage slot. Therefore, their “address” is composed of a slot and a byte-offset inside that slot. To retrieve the slot pointed to by the variable x, you use x.slot, and to retrieve the byte-offset you use x.offset. Using x itself will result in an error.

To illustrate the example I will use the latest Solidity version (v0.7.4).

In order to facilitate the explanation I will change your function. Suppose you have the following contract:

pragma solidity ^0.7.4;

contract D {
    function modValue(uint256 a, uint256 b) public pure returns (uint256) {
        return a % b;
    }
}

Suppose we create another contract in order to perform a delegatecall. We have the _delegate variable which is initialized in the constructor. As you can see, this variable is outside the assembly block. Thus, we need to add .slot to get the variable value inside the assembly block using the sload opcode.

The first 32 bytes of data indicate its length, that's why we do add(data,32) to get the data value. In addition, to get the data length you should use the mload opcode. More info about opcodes here.

pragma solidity ^0.7.4;

contract StackExchange {
    
    address _delegate;
    
    constructor (address delegate) {
        _delegate = delegate;
    }
    
    function getValue() public returns (uint256) {
        bytes memory data = abi.encodeWithSignature("modValue(uint256,uint256)", 52, 3);

        assembly {
            let pointer := mload(0x40)
            
            if iszero(delegatecall(not(0), sload(_delegate.slot), add(data,32), mload(data), pointer, 0x20)) {
                revert(0, 0)
            }

            let size := returndatasize()
            returndatacopy(pointer, 0, size)
            return(pointer,size)
        }
    }
}

Feel free to test the contracts in Remix.

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.