multicall
multicall copied to clipboard
delegatecall
- Is it possible to provide a version of aggregate that uses
delegatecall
, - Is it possible to provide a method
isContract
to determine if an address is a contract by checking if the code size is greater than 0?
What is the use case for 1?
For 2, that is tracked in https://github.com/mds1/multicall/issues/146
1.1 For some contracts, they perform checks on the caller (msg.sender), such as supporting only whitelisted callers. In such cases, using call may result in incorrect readings, while using delegatecall can return the correct result.
1.2 For certain write methods, such as withdraw, they directly transfer funds to msg.sender. Using multicall will correctly transfer the amount to msg.sender, but using call will withdraw the amount to the multicall contract itself.
e.g: https://bscscan.com/tx/0x924970e981b924806c65b4e176d71c2f8f65e44cc62dec5ff1357b340eff70d7#eventlog
For those use cases, you want to delegatecall to the Multicall contract. This way your contract (say, a Safe wallet) executes some arbitrary sequence of calls and is the msg.sender during e.g. the whitelist check.
If the Multicall contract did the delegatecall, that means the whitelist check now enforces who can call the Multicall contract, because the whitelist code is executing in the context of Multicall
For those use cases, you want to delegatecall to the Multicall contract. This way your contract (say, a Safe wallet) executes some arbitrary sequence of calls and is the msg.sender during e.g. the whitelist check.
If the Multicall contract did the delegatecall, that means the whitelist check now enforces who can call the Multicall contract, because the whitelist code is executing in the context of Multicall
for example:
// here is fake Multicall code
contract Multicall {
delegateCall({target,callData}) {
target.delegatecall(callData)
}
}
// here is fake Wallet code
contract Wallet {
withdraw(value) {
transfer(msg.sender, value)
}
}
If called Wallet directly like this, transfer msg.sender should be userAddress
ether.call({
from: userAddress,
to: walletAddress,
data: withdraw(value)
})
If called Wallet by delegate call like this, according to the DELEGATE_CALL by EIP-7 design, the transfer msg.sender should still be a userAddress, right?
ether.call({
from: userAddress,
to: multicallAddress,
data: delegateCall({
target: walletAddress,
callData: withdraw(value)
})
})
In your second example, where your EOA calls the multicall contract, and the multicall contract delegatecalls the wallet, then when withdraw
executes the msg.sender
is the EOA, and the multicall contract will transfer value
ETH from itself to the EOA. In other words, the multicall contract is executing the wallet's logic in its own context—usually you don't want to execute within the multicall contract's context
I would suggest reading the solidity docs or some tutorials like the ones by @PatrickAlphaC to learn more about delegatecall and how it's typically used :)
Hi, I just saw this. I think there are use cases where a delegate call would help. For instance, Let's say I want to aggregate interaction with multiple DeFi protocols from a ERC-4337 smart account, and some of these calls are made through adapter/helper contracts.
call[0] = compound.deposit(1000 USDC) (call)
call[1] = compound.borrow(500 DAI) (call)
call[2] = anotherProtocolHelper.deposit(500 DAI) (delegate call)
It isn't possible right now. A solution may be having an isDelagatecall
flag in the Call
struct.
Sorry @marcelomorgado I don't understand. If you want to aggregate interaction from a 4337 smart account, you can already do that with batch calls. For example: https://github.com/eth-infinitism/account-abstraction/blob/f1c5c11b273b7ddae26bb20809419b33ccb8f043/contracts/samples/SimpleAccount.sol#L63-L82
Or if the wallet doesn't have native batching but does have delegatecall (like gnosis safe), you delegatecall from your wallet to the Multicall3 contract to execute batch calls.
Hi @mds1 let's say I want to have such call:
┌───────────────────┐
│ │
│ Contract A │
│ │
└───────────────────┘
▲
│
┌───────────────┐ ┌──────────────────┐ │ ┌───────────────────┐
│ │ │ │ batch│ │ │
│ Smart Wallet ├─────────────►│ Multicall3 ├──────┴───────► Contract B │
│ │ delegate call│ │ delegate calls │
└───────────────┘ └──────────────────┘ │ ────────────────────┘
│
│
▼
┌───────────────────┐
│ │
│ Contract C │
│ │
└───────────────────┘
Since Multcall3 doesn't support batching delegate calls it isn't currently possible. Note: I'm saying it on the context of KernelV3 wallet that doesn't support batch delegate calls.
Revise the point in requirement 2. I think the method name should not be called isContract
, it should be named according to the underlying operation itself.
Here my example code:
contract CodeReader {
function exeCodeSize(address addr) public view returns (uint256 size) {
assembly {
size := extcodesize(addr)
}
}
function extCodeHash(address addr) public view returns (bytes32 codehash) {
assembly {
codehash := extcodehash(addr)
}
}
function extCode(address addr) public view returns (bytes memory code) {
uint256 size;
assembly {
size := extcodesize(addr)
}
if (size > 0) {
code = new bytes(size);
assembly {
extcodecopy(addr, add(code, 0x20), 0, size)
}
}
}
}
We also deployed some contract for us before Multicall
update to v4.
And found some deployed contract:
eth: 0xe9bbcd277e2c029c8b5f3c744dad93c290ad01a9 File 9 of 27 : AddressUpgradeable.sol L40