0
pragma solidity >=0.4.22 <0.7.0;
contract D {
  uint public n;
  address public sender;

  function delegatecallSetN(address _e, uint _n) public {
    _e.delegatecall(abi.encode(bytes4(keccak256("setN(uint256)")), _n));
  }
}

contract E {
  uint public n;
  address public sender;
  uint test = 10;

  function setN(uint256 _n) public {
    n = _n;
    sender = msg.sender;
  }
}

Question 1:

I have this contract. Now, when I call a delegatecallSetN from remix with the address of E, it successfully calls E contract's setN function. as we know how delegatecall works, it sets my passed _n value on D contract. Now, what about sender value on D contract ? It still is 0x000000...000 . Why doesn't it set msg.sender on it ?


Question 2:

Let's say in contract E's setN function, I add test = 50. As we know, the code of setN will be executed in the context of Contract D which doesn't have test variable at all. Why doesn't it result in exception or error at all and why does the transaction still succeed ?

UPDATED CODE:


pragma solidity >=0.4.22 <0.7.0;

contract D {
  uint public n;
  address public sender;


  function delegatecallSetN(address _e, uint _n) public {
    _e.delegatecall(abi.encodeWithSignature("setN(uint256)", _n));
  }
  
  
}

contract E {
  uint public n;
  uint public test;
  address public sender;

  function setN(uint256 _n) public {
    n = _n;
    sender = msg.sender;
    test = 20;
  }
}
2
  • After modifying the code to use abi.encodeWithSignature it works as expected both n and sender are updated correctly for D. Regarding question 2) the variables at contract level are referenced by a storage slot at compile time, since slots always exists you cannot check during runtime if a variable was defined or not.
    – Ismael
    Commented Dec 8, 2020 at 23:53
  • Question 1) It doesn't work even after I change it to abi.encodeWithSignature, I am going to include the remix code in my updated question and you can test it too. Basically n variable of D contract gets set, but not the sender. I just tested and sender became 0x0000000000000000000000000000000000000014 this address. which doesn't seem right. Question 2) I still don't understand your answer for the Question 2 unfortunately. Commented Dec 9, 2020 at 9:47

2 Answers 2

2

For delegatecall to work correctly both contracts should have compatible storage layout. Having the same variables in the same order. If variables are reordered the storage layout will be incompatible and it will not work as expected.


The compiler assigns storage slots in the same order the variables are defined.

contract D {
  uint public n;              // <-- slot 0
  address public sender       // <-- slot 1

and

contract E {
  uint public n;              // <-- slot 0
  uint public test;           // <-- slot 1
  address public sender;      // <-- slot 2

When contract E modifies sender it modifies slot 2, similarly for n it modifies slot 0 and for test slot 1.

  • n has the same slot in D and E so its modification by E will be reflected in D.
  • sender for E has slot 2, so any modification is not visible from D because there's no variable assigned slot 2.
  • test has slot 1 for E, so it will store 0x0000000000000000000000000000000000000000000000000000000000000014 in slot 1. For D slot 1 is sender an address so it will interpret as 0x0000000000000000000000000000000000000014.
2
  • Ismael, you rock big time ! Thank you, finally it makes sense. Question 1) I am not sure when it's really a good idea to use delegetecall since if you use it, then you should also have compatible storage layouts figured out in advance. for selfdestruct , that would be a good idea, but can't think of anything else. Question 2) so, in contract D, if I use assembly and read from slot 2, i will still have the variable msg.sender stored in it, right ? Commented Dec 9, 2020 at 14:37
  • Yes, with assembly you can read any slot you want. Proxy contracts use delegatecall blog.openzeppelin.com/proxy-patterns.
    – Ismael
    Commented Dec 9, 2020 at 15:21
0

About Question 1:

The state of contract E is not getting updated, because your deletecall in contract D is not working.

Replace:

_e.delegatecall(abi.encode(bytes4(keccak256("setN(uint256)")), _n));

with

(bool success,) = address(_e).call(abi.encodeWithSignature("setN(uint256)", _n));

About question 2:

I got a bit confused, when you're making delegatecall from contract D to contract E, you're referring to method setN and now inside this method you can access the state of contract where this method is defined ( contract E ). I don't see where is the issue here, that's how it should be working.

4
  • question 1) Sorry, I placed the wrong code there, even if I change it with encodeWithSignature like you did, the problem is still there. imagine I have your code and read the first question again. Question 2) when I call delegatecall from D, setN function gets called, but it's like that whatever is written in setN function gets called in contract D's context. That's how it should be working, but if I add test = 20 in setN function, when this function gets called in context of D, it should throw an exception, because contract D doesn't have test storage variable Commented Dec 8, 2020 at 18:35
  • You're doing something wrong. pastebin.com/g0ZB5Ku4 This is my code and the call is working great. Commented Dec 8, 2020 at 18:39
  • we are talking about delegatecall and not call. Commented Dec 8, 2020 at 18:44
  • My bad, I should've explained a bit more. When using delegatecall the storage still refer to the calling contract, but when using call like in my example the storage of the called contract is getting updated. If you want your logic to work as described in the question just use (bool success,) = address(_e).call(abi.encodeWithSignature("setN(uint256)", _n));. In general both call and delegatecall are low level functions that returns bool. You can validate this bool to check if the call was successful. Please select this answer as the answer if it helped you. Commented Dec 9, 2020 at 5:49

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.