When delegatecall is used in a contract, it executes code in the context of another contract. This means the code runs in the context of the calling contract (using its storage, balance, and address), but the actual code to execute is located in a different contract.
This feature is particularly useful for creating upgradable contracts, where the logic can be updated without changing the contracts address.
However, delegatecall can also be exploited by bad actors to perform malicious activities.
A `nodelegatecall` modifier ensures that the function it modifies cannot be executed via delegatecall.
The purpose of the noDelegateCall modifier is to ensure that certain functions can only be called directly on the contract that contains them, not via delegatecall from another contract. The modifier checks if address(this) (the current executing contract's address) matches originalAddress (the deployed address of the contract). If a function with the noDelegateCall modifier is called via delegatecall from another contract, address(this) will not match `originalAddress`, causing the `require` statement to fail and the transaction to revert.
This feature is particularly useful for creating upgradable contracts, where the logic can be updated without changing the contracts address.
However, delegatecall can also be exploited by bad actors to perform malicious activities.
A `nodelegatecall` modifier ensures that the function it modifies cannot be executed via delegatecall.
The purpose of the noDelegateCall modifier is to ensure that certain functions can only be called directly on the contract that contains them, not via delegatecall from another contract. The modifier checks if address(this) (the current executing contract's address) matches originalAddress (the deployed address of the contract). If a function with the noDelegateCall modifier is called via delegatecall from another contract, address(this) will not match `originalAddress`, causing the `require` statement to fail and the transaction to revert.
Note the Immutable Variable:
originalAddress is declared as an immutable variable, ensuring it is set once during deployment and cannot be changed. If it wouldn't be immutable, it would be void because the storage slot from the caller contract would have been accessed.
The constructor sets `originalAddress` to `address(this)`, which is the address of the contract at the time of deployment.
In the example below, when ContractB attempts to call performTask in ContractA via delegatecall, the noDelegateCall modifier in ContractA checks the contract address. Since the address during the delegatecall is that of ContractB and not ContractA, the modifier's require statement fails. This causes the transaction to revert with the message "no delegate call ", effectively blocking the delegate call attempt.