multicall icon indicating copy to clipboard operation
multicall copied to clipboard

delegatecall

Open geekberryonekey opened this issue 9 months ago • 4 comments

  1. Is it possible to provide a version of aggregate that uses delegatecall,
  2. 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?

geekberryonekey avatar May 08 '24 09:05 geekberryonekey

What is the use case for 1?

For 2, that is tracked in https://github.com/mds1/multicall/issues/146

mds1 avatar May 08 '24 22:05 mds1

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

geekberryonekey avatar May 09 '24 09:05 geekberryonekey

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

mds1 avatar May 09 '24 14:05 mds1

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)
    }) 
})

geekberryonekey avatar May 10 '24 01:05 geekberryonekey

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 :)

mds1 avatar May 20 '24 22:05 mds1

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.

marcelomorgado avatar May 24 '24 20:05 marcelomorgado

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.

mds1 avatar Jun 08 '24 16:06 mds1

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.

marcelomorgado avatar Jun 10 '24 15:06 marcelomorgado

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.

network address
eth 0xc43e013be5b5dbebca5c65ec9aaf71786b98c95e
optimism 0xc43e013be5b5dbebca5c65ec9aaf71786b98c95e
bsc 0x2b7c3d71e052f047690125b72f64f3d8aac5cf10
etc 0x8f05f6ca1c6d72a2909e6e261e56db300819967a
polygon 0x76f66961135bb69a535a12d779d3ce6bd15bb7ea
base 0x279d943ad7c9acc437ba0493b71c4f356d6760e3
arbitrum 0x4fe31475333de3ae8dcd69668738a606c2e9bb13
avalanche 0x946a8b191370e4ede16a8b20cb7e2e3ae50992a6
eth sepolia 0x7d437ccd1ff4544501003480d24c06c44684b503
eth holesky 0x18720235fdcefff9d5152e54475a081e12ea081f
tron TKff5fznoKrufV5ZTm3zX1Ag7ah7Jrx2Fx

And found some deployed contract:

eth: 0xe9bbcd277e2c029c8b5f3c744dad93c290ad01a9 File 9 of 27 : AddressUpgradeable.sol L40

bsc: 0xcb8b1a33b5569d3728b4f834d01c7df0608fb184 L319

geekberryonekey avatar Sep 24 '24 04:09 geekberryonekey