Sources
DelegateCall: Calling Another Contract Function in Solidity
Difference between CALL, CALLCODE and DELEGATECALL
Solidity call() and delegateCall()
How to prevent the code of my contract being used in a CALLCODE?
Overview
When delegatecall
used in caller contract to target contract,
- target contract executes its logic
- context(storage) is on caller contract
As EVM saves filed variable to storage in a slot order, if we use delegatecall
, we should set the order of variables in same order.
Preventing unwanted Delegate call
This method is from Uniswap v3’s NoDelegateCall.sol.
The core is to compare between the original address of contract(originalAdress
) and new address that executes delegatecall
(newAdress
).
1. How it works
pragma solidity >0.8.0;
contract Receiver {
string greeting = "Hello";
address private immutable originalAdress;
event Greeting(string greeting, address originalAdress, address newAddress);
constructor() {
// Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
// In other words, this variable won't change when it's checked at runtime.
originalAdress = address(this);
}
function greet() external {
address newAddress = address(this);
emit Greeting(greeting, originalAdress, newAddress);
}
}
contract Sender {
string greeting = "Hi";
function delegatedGreeting(address _contract) external {
(bool success,) = _contract.delegatecall(
abi.encodeWithSignature("greet()")
);
}
function callGreeting(address _contract) external {
(bool success,) = _contract.call(
abi.encodeWithSignature("greet()")
);
}
}
-
Sender
contract address : 0xaE036c65C649172b43ef7156b009c6221B596B8b -
Reciever
contract address : 0xcD6a42782d230D7c13A74ddec5dD140e55499Df9
origianalAddress
This variable is initialized in constructor of Reciever
contract and fixed to Reciever
contract address whether Sender
executes call
or delegatecall
. It cannot be modified because of immutable
keyword.
newAddress
This variable is assigned to address(this)
inside the function greet
. As address(this)
refers the contract which the function is executing, it’s value varies when it’s triggered by call
and delegatecall
.
-
call
Using
call
function, the contract that is executed isReciever
. So,newAddress
is same toorginalAddress
. -
delegatecall
But, using
delegatecall
, the contract that is actually executed isSender
. So,newAddress
stores the value ofSender
contract address, which is different tooriginalAddress
.
2. 50% Success(revert issue)
pragma solidity >0.8.0;
contract Receiver {
string greeting = "Hello";
address private immutable original;
event Greeting(string greeting, address original, address addressThis);
constructor() {
// Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
// In other words, this variable won't change when it's checked at runtime.
original = address(this);
}
/// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
/// and the use of immutable means the address bytes are copied in every place the modifier is used.
function checkNotDelegateCall() private view {
require(address(this) == original);
}
/// @notice Prevents delegatecall into the modified method
modifier noDelegateCall() {
checkNotDelegateCall();
_;
}
function greet() external noDelegateCall {
emit Greeting(greeting, original, address(this));
}
}
contract Sender {
string greeting = "Hi";
function delegatedGreeting(address _contract) external {
(bool success,) = _contract.delegatecall(
abi.encodeWithSignature("greet()")
);
}
function callGreeting(address _contract) external {
(bool success,) = _contract.call(
abi.encodeWithSignature("greet()")
);
}
}
I implemented noDelegateCall
modifier to check the difference between original
variable and address(this)
inside function greet
. The problem was that although the emitted event was empty, delegatecall
wasn’t reverted. So I tried to make it revert...
3. Why doesn’t delegatedcall
revert?
solidity delegatecall prevention doesn't works
When low-level
delegatecall
reverts, it doesn’t automatically revert main transaction. Instead It returnsfalse
as the first return value.
So, to make delegatecall
revert, we should check if the first return value success
is true.
function delegatedGreeting(address _contract) external {
(bool success,) = _contract.delegatecall(
abi.encodeWithSignature("greet()")
);
require(success == true, "delegatecall failed");
}
4. 100% Success
pragma solidity >0.8.0;
contract Receiver {
string greeting = "Hello";
address private immutable original;
event Greeting(string greeting, address original, address addressThis);
constructor() {
// Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
// In other words, this variable won't change when it's checked at runtime.
original = address(this);
}
/// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
/// and the use of immutable means the address bytes are copied in every place the modifier is used.
function checkNotDelegateCall() private view {
require(address(this) == original);
}
/// @notice Prevents delegatecall into the modified method
modifier noDelegateCall() {
checkNotDelegateCall();
_;
}
function greet() external noDelegateCall {
emit Greeting(greeting, original, address(this));
}
}
contract Sender {
string greeting = "Hi";
function delegatedGreeting(address _contract) external {
(bool success,) = _contract.delegatecall(
abi.encodeWithSignature("greet()")
);
require(success == true, "Delegation failed!");
}
function callGreeting(address _contract) external {
(bool success,) = _contract.call(
abi.encodeWithSignature("greet()")
);
require(success == true, "Call failed!");
}
}
Top comments (0)