Injecting Custom Functions into Existing Contracts with eth_simulateV1
December 23rd, 2024

As detailed in The JSON-RPC Method eth_simulateV1 is now available on multiple networks, eth_simulateV1 is a powerful tool for conducting advanced simulations on Ethereum.

Recently, we encountered a challenge: simulating a delegate call on an existing contract. This need arose from our desire to test delegate call transactions on Gnosis Safe before committing to the transaction or signing any messages.

For example, with the Transaction Builder extension for Gnosis Safe, you can perform a multi-asset transfer in a single transaction:

image

Simulating this transaction results in to following view in The Interceptor:

image

Under normal circumstances, Gnosis Safe executes this transaction by performing a delegate call to a multicall contract to send multiple assets. To simulate this behavior, we aim to replicate the same process but bypass the Gnosis Safe's signature validation checks. To achieve this, we add a custom function to the Gnosis Safe that directly performs the delegate call while skipping these checks. Importantly, the existing functionality of Gnosis Safe must otherwise remain intact, as the multicall contract might call back into the Gnosis Safe.

This approach can be implemented using eth_simulateV1:

  1. Relocate the Safe: Move the Safe to a new address, for example, eth:0x0000000000000000000000000000000000920515 (gnosis in leetspeak).

  2. Replace the Existing Code: Replace the code of the existing Safe (e.g., eth:0xA06C2B67e7435cE25a5969e49983ec3304D8e787) with a proxy contract. This proxy delegates all calls to the original Safe (0x...920515) except calls to the newly introduced delegateCallExecute function, which performs the delegate call directly.

  3. Execute the Delegate Call: Invoke the delegateCallExecute function to perform the desired operation.

Here’s the eth_simulateV1 call that achieves this:

{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "eth_simulateV1",
	"params": [
		{
			"blockStateCalls": [{
				"calls": [{
					"type": "0x2",
					"from": "0xa06c2b67e7435ce25a5969e49983ec3304d8e787",
					"nonce": "0x0",
					"maxPriorityFeePerGas": "0x0",
					"gas": "0x1c9510f",
					"to": "0xa06c2b67e7435ce25a5969e49983ec3304d8e787",
					"value": "0x0",
					"input": "0x9fe8397800000000000000000000000040a2accbd92bca938b02010e17a5b8929b49130d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001848d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000013200a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000006d6054f7745a3aaf4d1e4ac5830e4abdc328ab6b00000000000000000000000000000000000000000000000000000000000f424000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000000f4240000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
					"chainId": "0x1"
				}],
				"stateOverrides": {
					"0xa06c2b67e7435ce25a5969e49983ec3304d8e787": {
						"code": "0x6080604052600436106100225760003560e01c80639fe839781461004957610023565b5b629205153660008037600080366000845af43d6000803e80610044573d6000fd5b3d6000f35b610063600480360381019061005e91906102eb565b610079565b60405161007091906103c6565b60405180910390f35b60606000808473ffffffffffffffffffffffffffffffffffffffff16846040516100a39190610424565b600060405180830381855af49150503d80600081146100de576040519150601f19603f3d011682016040523d82523d6000602084013e6100e3565b606091505b509150915081610128576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161011f90610498565b60405180910390fd5b809250505092915050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061017282610147565b9050919050565b61018281610167565b811461018d57600080fd5b50565b60008135905061019f81610179565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6101f8826101af565b810181811067ffffffffffffffff82111715610217576102166101c0565b5b80604052505050565b600061022a610133565b905061023682826101ef565b919050565b600067ffffffffffffffff821115610256576102556101c0565b5b61025f826101af565b9050602081019050919050565b82818337600083830152505050565b600061028e6102898461023b565b610220565b9050828152602081018484840111156102aa576102a96101aa565b5b6102b584828561026c565b509392505050565b600082601f8301126102d2576102d16101a5565b5b81356102e284826020860161027b565b91505092915050565b600080604083850312156103025761030161013d565b5b600061031085828601610190565b925050602083013567ffffffffffffffff81111561033157610330610142565b5b61033d858286016102bd565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610381578082015181840152602081019050610366565b60008484015250505050565b600061039882610347565b6103a28185610352565b93506103b2818560208601610363565b6103bb816101af565b840191505092915050565b600060208201905081810360008301526103e0818461038d565b905092915050565b600081905092915050565b60006103fe82610347565b61040881856103e8565b9350610418818560208601610363565b80840191505092915050565b600061043082846103f3565b915081905092915050565b600082825260208201905092915050565b7f44656c65676174652063616c6c206661696c6564000000000000000000000000600082015250565b600061048260148361043b565b915061048d8261044c565b602082019050919050565b600060208201905081810360008301526104b181610475565b905091905056fea26469706673582212203e96f34ac95ff29da01f27c2e715937c3b3829ae9ffeb1111dd78145a79362dc64736f6c63430008120033"
					},
					"0x0000000000000000000000000000000000920515": {
						"code": "0x608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea2646970667358221220d1429297349653a4918076d650332de1a1068c5f3e07c5c82360c277770b955264736f6c63430007060033"
					}
				}
			}],
			"traceTransfers": true,
			"validation": false
		},
		"latest"
	]
}

Here's the Solidity contract that we are using in the place of the original Gnosis Safe. its called GnosisSafeProxyProxy, as the original Gnosis safe is already a proxy contract.

// modified from here: https://github.com/safe-global/safe-smart-account/blob/main/contracts/proxies/SafeProxy.sol
pragma solidity 0.8.18;
contract GnosisSafeProxyProxy {
	function delegateCallExecute(address target, bytes memory callData) payable external returns (bytes memory) {
		(bool success, bytes memory returnData) = payable(target).delegatecall(callData);
		require(success, "Delegate call failed");
		return returnData;
	}
	/// forwards all transactions to 0x0000000000000000000000000000000000920515
	fallback() external payable {
		assembly {
			let _originalGnosisSafeProxy := 0x0000000000000000000000000000000000920515
			calldatacopy(0, 0, calldatasize())
			let success := delegatecall(gas(), _originalGnosisSafeProxy, 0, calldatasize(), 0, 0)
			returndatacopy(0, 0, returndatasize())
			if iszero(success) {
				revert(0, returndatasize())
			}
			return(0, returndatasize())
		}
	}
}

The eth_simulateV1 result will appear as follows. You can observe that two logs are produced, corresponding to two successful USDC transfers.

{
	"jsonrpc": "2.0",
	"id": 1,
	"result": [{
		"baseFeePerGas": "0x0",
		"blobGasUsed": "0x0",
		"calls": [{
			"returnData": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
			"logs": [
				{
					"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
					"topics": [
						"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
						"0x000000000000000000000000a06c2b67e7435ce25a5969e49983ec3304d8e787",
						"0x0000000000000000000000006d6054f7745a3aaf4d1e4ac5830e4abdc328ab6b"
					],
					"data": "0x00000000000000000000000000000000000000000000000000000000000f4240",
					"blockNumber": "0x147195a",
					"transactionHash": "0xeb25010739131379c17648e70bc5fc498824cfae99ebd575fe06ec43a7dcae0e",
					"transactionIndex": "0x0",
					"blockHash": "0x6df5cc16d73e93e390af4505d848fd7dda02c3809d73435f56bc41808f3c0e7d",
					"logIndex": "0x0",
					"removed": false
				},
				{
					"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
					"topics": [
						"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
						"0x000000000000000000000000a06c2b67e7435ce25a5969e49983ec3304d8e787",
						"0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
					],
					"data": "0x00000000000000000000000000000000000000000000000000000000000f4240",
					"blockNumber": "0x147195a",
					"transactionHash": "0xeb25010739131379c17648e70bc5fc498824cfae99ebd575fe06ec43a7dcae0e",
					"transactionIndex": "0x0",
					"blockHash": "0x6df5cc16d73e93e390af4505d848fd7dda02c3809d73435f56bc41808f3c0e7d",
					"logIndex": "0x1",
					"removed": false
				}
			],
			"gasUsed": "0x14c37",
			"status": "0x1"
		}],
		"difficulty": "0x0",
		"excessBlobGas": "0x3ca0000",
		"extraData": "0x",
		"gasLimit": "0x1c9c380",
		"gasUsed": "0x14c37",
		"hash": "0x6df5cc16d73e93e390af4505d848fd7dda02c3809d73435f56bc41808f3c0e7d",
		"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
		"miner": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
		"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
		"nonce": "0x0000000000000000",
		"number": "0x147195a",
		"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
		"parentHash": "0x78f4dfbbc4f917365318388d84c8a4e99de1b644e1c1921dbafe22b6801a12ea",
		"receiptsRoot": "0xc879078b52dba26a2cd82194b119cb930dbd74b0266002ddd05af1e34c49fb81",
		"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
		"size": "0x481",
		"stateRoot": "0xe972f8b699a98a04fc1d5a42fc9270533195c97a31ac8f3302d9d85f57a6ebde",
		"timestamp": "0x67641aaf",
		"totalDifficulty": "0xc70d815d562d3cfa955",
		"transactions": ["0xeb25010739131379c17648e70bc5fc498824cfae99ebd575fe06ec43a7dcae0e"],
		"transactionsRoot": "0x617801340b237e21318adcc25ce295079ea29cb4a600572df694cdea0220e39f",
		"uncles": [],
		"withdrawals": [],
		"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
	}]
}

In this example, we added a new delegate call function to the Gnosis Safe. However, this method is versatile and can be used to add any function to any contract for simulation purposes. This approach is particularly useful for bypassing checks (as demonstrated here), introducing additional get methods to the contract, or performing any other computation directly within the contract. Moreover, the custom functions you write have full access to the existing state of the contract, making this approach even more powerful!

Subscribe to Killari - Dark.florist
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from Killari - Dark.florist

Skeleton

Skeleton

Skeleton