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()")
);
}
}
-
Sendercontract address : 0xaE036c65C649172b43ef7156b009c6221B596B8b -
Recievercontract 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 .
-
callUsing
callfunction, the contract that is executed isReciever. So,newAddressis same toorginalAddress. -
delegatecallBut, using
delegatecall, the contract that is actually executed isSender. So,newAddressstores the value ofSendercontract 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
delegatecallreverts, it doesn’t automatically revert main transaction. Instead It returnsfalseas 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)