diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index d8103c926a..8bf32260cb 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1105,6 +1105,7 @@ type CallBatch struct { type callResult struct { ReturnValue hexutil.Bytes `json:"return"` Logs []*types.Log `json:"logs"` + Transfers []transfer `json:"transfers,omitempty"` GasUsed hexutil.Uint64 `json:"gasUsed"` Error string `json:"error"` } @@ -1116,12 +1117,11 @@ type callResult struct { // // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, blockNrOrHash rpc.BlockNumberOrHash) ([][]callResult, error) { +func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, blockNrOrHash rpc.BlockNumberOrHash, includeTransfers *bool) ([][]callResult, error) { state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } - // Setup context so it may be cancelled before the calls completed // or, in case of unmetered gas, setup a context with a timeout. var ( @@ -1166,7 +1166,14 @@ func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, block // Hack to get logs from statedb which stores logs by txhash. txhash := common.BigToHash(big.NewInt(int64(i))) state.SetTxContext(txhash, i) - result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, &vm.Config{NoBaseFee: true, DisableECRecover: true}) + vmConfig := &vm.Config{NoBaseFee: true} + if block.ECRecoverOverride != nil { + vmConfig.DisableECRecover = true + } + if includeTransfers != nil && *includeTransfers { + vmConfig.Tracer = newTracer() + } + result, err := doCall(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig) if err != nil { results[bi][i] = callResult{Error: err.Error()} continue @@ -1180,7 +1187,11 @@ func (s *BlockChainAPI) Multicall(ctx context.Context, blocks []CallBatch, block for _, l := range logs { l.TxHash = common.Hash{} } - callRes := callResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)} + var transfers []transfer + if includeTransfers != nil && *includeTransfers { + transfers = vmConfig.Tracer.(*tracer).Transfers() + } + callRes := callResult{ReturnValue: result.Return(), Logs: logs, Transfers: transfers, GasUsed: hexutil.Uint64(result.UsedGas)} if result.Err != nil { callRes.Error = result.Err.Error() } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 12682afb33..4da72ce6c9 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -629,20 +629,23 @@ func TestMulticall(t *testing.T) { b.AddTx(tx) })) var ( - randomAccounts = newAccounts(4) - latest = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + randomAccounts = newAccounts(4) + latest = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + includeTransfers = true ) type res struct { ReturnValue string `json:"return"` Error string Logs []types.Log GasUsed string + Transfers []transfer } var testSuite = []struct { - blocks []CallBatch - tag rpc.BlockNumberOrHash - expectErr error - want [][]res + blocks []CallBatch + tag rpc.BlockNumberOrHash + includeTransfers *bool + expectErr error + want [][]res }{ // State build-up over calls: // First value transfer OK after state override. @@ -919,10 +922,55 @@ func TestMulticall(t *testing.T) { GasUsed: "0x52f6", }}}, }, + // Test ether transfers. + { + tag: latest, + blocks: []CallBatch{{ + StateOverrides: &StateOverride{ + randomAccounts[0].addr: OverrideAccount{ + Balance: newRPCBalance(big.NewInt(100)), + // Yul code that transfers 100 wei to address passed in calldata: + // object "Test" { + // code { + // let recipient := shr(96, calldataload(0)) + // let value := 100 + // let success := call(gas(), recipient, value, 0, 0, 0, 0) + // if eq(success, 0) { + // revert(0, 0) + // } + // } + // } + Code: hex2Bytes("60003560601c606460008060008084865af160008103601d57600080fd5b505050"), + }, + }, + Calls: []TransactionArgs{{ + From: &accounts[0].addr, + To: &randomAccounts[0].addr, + Value: (*hexutil.Big)(big.NewInt(50)), + Input: hex2Bytes(strings.TrimPrefix(randomAccounts[1].addr.String(), "0x")), + }}, + }}, + includeTransfers: &includeTransfers, + want: [][]res{{{ + ReturnValue: "0x", + GasUsed: "0xd984", + Transfers: []transfer{ + { + From: accounts[0].addr, + To: randomAccounts[0].addr, + Value: big.NewInt(50), + }, { + From: randomAccounts[0].addr, + To: randomAccounts[1].addr, + Value: big.NewInt(100), + }, + }, + }}}, + }, } for i, tc := range testSuite { - result, err := api.Multicall(context.Background(), tc.blocks, tc.tag) + result, err := api.Multicall(context.Background(), tc.blocks, tc.tag, tc.includeTransfers) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 19dc2d2e14..943854a7da 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -614,8 +614,8 @@ web3._extend({ new web3._extend.Method({ name: 'multicall', call: 'eth_multicall', - params: 2, - inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter], + params: 3, + inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter, null], }), ], properties: [