Skip to content

Commit 43eecf0

Browse files
author
Eela Nagaraj
authored
Add test cases for native and precompile transfers and tracing (#2077)
* Add callTracer API test cases for native and precompile transfers * Add e2e test for calling GoldToken.transfer * Add comment explaining GoldToken transfer e2e test * Fix nits in transferPrecompile var and comments * Fix bug in precompile length checking * Rename ValueTransferTransaction to BuildSignedTransaction * Keep TransferAddress private for now and address PR comments
1 parent 47596d3 commit 43eecf0

File tree

5 files changed

+286
-10
lines changed

5 files changed

+286
-10
lines changed

core/vm/contracts.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ func (c *transfer) Run(input []byte, caller common.Address, evm *EVM) ([]byte, e
698698
// to: 32 bytes representing the address of the recipient
699699
// value: 32 bytes, a 256 bit integer representing the amount of Celo Gold to transfer
700700
// 3 arguments x 32 bytes each = 96 bytes total input
701-
if (evm.chainRules.IsGFork && len(input) != 96) || len(input) <= 96 {
701+
if (evm.chainRules.IsGFork && len(input) != 96) || len(input) < 96 {
702702
return nil, ErrInputLength
703703
}
704704

e2e_test/e2e_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/celo-org/celo-blockchain/common"
1515
"github.com/celo-org/celo-blockchain/common/hexutil"
1616
"github.com/celo-org/celo-blockchain/core/types"
17+
"github.com/celo-org/celo-blockchain/eth/tracers"
1718
"github.com/celo-org/celo-blockchain/log"
1819
"github.com/celo-org/celo-blockchain/node"
1920
"github.com/celo-org/celo-blockchain/rpc"
@@ -55,6 +56,41 @@ func TestSendCelo(t *testing.T) {
5556
require.NoError(t, err)
5657
}
5758

59+
// This test starts a network, submits a GoldToken.transfer tx, waits for the whole
60+
// network to process the transaction, and traces that tx.
61+
// This tests that CELO transfers made via the transfer precompile work e2e
62+
// and can be useful for debugging these traces.
63+
func TestTraceSendCeloViaGoldToken(t *testing.T) {
64+
ac := test.AccountConfig(3, 2)
65+
gc, ec, err := test.BuildConfig(ac)
66+
require.NoError(t, err)
67+
network, shutdown, err := test.NewNetwork(ac, gc, ec)
68+
require.NoError(t, err)
69+
defer shutdown()
70+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
71+
defer cancel()
72+
73+
accounts := test.Accounts(ac.DeveloperAccounts(), gc.ChainConfig())
74+
// Send 1 wei of CELO from accounts[0] to accounts[1] by calling GoldToken.transfer
75+
tx, err := accounts[0].SendCeloViaGoldToken(ctx, accounts[1].Address, 1, network[0])
76+
require.NoError(t, err)
77+
78+
// Wait for the whole network to process the transaction.
79+
err = network.AwaitTransactions(ctx, tx)
80+
require.NoError(t, err)
81+
c, err := rpc.DialContext(ctx, network[0].WSEndpoint())
82+
require.NoError(t, err)
83+
84+
var result map[string]interface{}
85+
tracerStr := "callTracer"
86+
err = c.CallContext(ctx, &result, "debug_traceTransaction", tx.Hash().String(), tracers.TraceConfig{Tracer: &tracerStr})
87+
88+
require.NoError(t, err)
89+
// Check top level gas values
90+
require.Equal(t, result["gasUsed"], "0x3a46")
91+
require.Equal(t, result["gas"], "0x3ac4")
92+
}
93+
5894
// This test verifies correct behavior in a network of size one, in the case that
5995
// this fails we know that the problem does not lie with our network code.
6096
func TestSingleNodeNetworkManyTxs(t *testing.T) {

eth/tracers/api_test.go

+189
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"testing"
3030
"time"
3131

32+
"github.com/celo-org/celo-blockchain/accounts/abi"
3233
"github.com/celo-org/celo-blockchain/common"
3334
"github.com/celo-org/celo-blockchain/common/hexutil"
3435
"github.com/celo-org/celo-blockchain/consensus"
@@ -544,6 +545,194 @@ func TestTraceTransactionWithRegistryDeployed(t *testing.T) {
544545
}
545546
}
546547

548+
// Use the callTracer to trace a native CELO transfer after the
549+
// registry has been deployed, as above.
550+
func TestCallTraceTransactionNativeTransfer(t *testing.T) {
551+
t.Parallel()
552+
553+
// Initialize test accounts
554+
accounts := newAccounts(2)
555+
genesis := &core.Genesis{Alloc: core.GenesisAlloc{
556+
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
557+
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
558+
common.HexToAddress("0xce10"): { // Registry Proxy
559+
Code: testutil.RegistryProxyOpcodes,
560+
Storage: map[common.Hash]common.Hash{
561+
// Hashes represent the storage slot for Registry.sol's `registry` mapping
562+
// which is stored in the RegistryProxy's storage.
563+
// Hashes are computed by taking: keccack(packed(params.GoldTokenRegistryId, 1)),
564+
// where 1 is the storage offset)
565+
common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"): common.HexToHash("0xce11"), // Registry Implementation
566+
common.HexToHash("0x91646b8507bf2e54d7c3de9155442ba111546b81af1cbdd1f68eeb6926b98d58"): common.HexToHash("0xd023"), // Governance Proxy
567+
},
568+
Balance: big.NewInt(0),
569+
},
570+
common.HexToAddress("0xce11"): { // Registry Implementation
571+
Code: testutil.RegistryOpcodes,
572+
Balance: big.NewInt(0),
573+
},
574+
}}
575+
576+
target := common.Hash{}
577+
signer := types.HomesteadSigner{}
578+
transferVal := big.NewInt(1000)
579+
api := NewAPI(newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
580+
// Transfer from account[0] to account[1]
581+
// value: 1000 wei
582+
// fee: 0 wei
583+
tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, transferVal, params.TxGas, nil, nil, nil, nil, nil), signer, accounts[0].key)
584+
b.AddTx(tx)
585+
target = tx.Hash()
586+
}))
587+
tracerStr := "callTracer"
588+
result, err := api.TraceTransaction(context.Background(), target, &TraceConfig{Tracer: &tracerStr})
589+
if err != nil {
590+
t.Errorf("Failed to trace transaction %v", err)
591+
}
592+
593+
ret := new(callTrace)
594+
if err := json.Unmarshal(result.(json.RawMessage), ret); err != nil {
595+
t.Fatalf("failed to unmarshal trace result: %v", err)
596+
}
597+
expectedTrace := &callTrace{
598+
Type: "CALL",
599+
From: accounts[0].addr,
600+
To: accounts[1].addr,
601+
Input: hexutil.Bytes(common.Hex2Bytes("0x")),
602+
Output: hexutil.Bytes(common.Hex2Bytes("0x")),
603+
Gas: newRPCUint64(0),
604+
GasUsed: newRPCUint64(0),
605+
Value: (*hexutil.Big)(transferVal),
606+
}
607+
if !jsonEqual(ret, expectedTrace) {
608+
t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, expectedTrace)
609+
}
610+
}
611+
612+
// Use the callTracer to trace a CELO transfer made via the transfer
613+
// precompile (by sending this from the registered GoldToken contract).
614+
func TestCallTraceTransactionPrecompileTransfer(t *testing.T) {
615+
// Invoke the transfer precompile by sending a transfer from an account
616+
// registered as GoldToken in the mock registry
617+
t.Parallel()
618+
// Initialize test accounts
619+
accounts := newAccounts(3)
620+
goldToken := accounts[0]
621+
registryProxyAddr := common.HexToAddress("0xce10")
622+
registryImplAddr := common.HexToAddress("0xce11")
623+
genesis := &core.Genesis{Alloc: core.GenesisAlloc{
624+
goldToken.addr: {Balance: big.NewInt(params.Ether)},
625+
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
626+
accounts[2].addr: {Balance: big.NewInt(params.Ether)},
627+
registryProxyAddr: { // Registry Proxy
628+
Code: testutil.RegistryProxyOpcodes,
629+
Storage: map[common.Hash]common.Hash{
630+
common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"): common.HexToHash("0xce11"), // Registry Implementation
631+
common.HexToHash("0x91646b8507bf2e54d7c3de9155442ba111546b81af1cbdd1f68eeb6926b98d58"): common.HexToHash("0xd023"), // Governance Proxy
632+
common.HexToHash("0x773cc8652456781771d48fb3d560d7ba3d6d1882654492b68beec583d2c590aa"): goldToken.addr.Hash(), // GoldToken Implementation
633+
common.HexToHash("0x2131a4338f6fb8d4507e234a7c72af8efefbbf2f1817ed570bce33eb6667feb9"): common.HexToHash("0xd007"), // Reserve Implementation
634+
},
635+
Balance: big.NewInt(0),
636+
},
637+
registryImplAddr: { // Registry Implementation
638+
Code: testutil.RegistryOpcodes,
639+
Balance: big.NewInt(0),
640+
},
641+
}}
642+
643+
target := common.Hash{}
644+
signer := types.HomesteadSigner{}
645+
transferVal := big.NewInt(1000)
646+
647+
// Construct and pack data for transfer precompile representing [from, to, value]
648+
addressTy, err := abi.NewType("address", "", nil)
649+
if err != nil {
650+
t.Fatalf("failed to create address type: %v", err)
651+
}
652+
uint256Ty, err := abi.NewType("uint256", "", nil)
653+
if err != nil {
654+
t.Fatalf("failed to create uint256 type: %v", err)
655+
}
656+
657+
transferArgs := abi.Arguments{
658+
{
659+
Type: addressTy,
660+
},
661+
{
662+
Type: addressTy,
663+
},
664+
{
665+
Type: uint256Ty,
666+
},
667+
}
668+
data, err := transferArgs.Pack(accounts[1].addr, accounts[2].addr, transferVal)
669+
if err != nil {
670+
t.Fatalf("failed to pack args: %v", err)
671+
}
672+
transferPrecompile := common.HexToAddress("0xfd")
673+
gas := params.TxGas * 2
674+
api := NewAPI(newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
675+
// Transfer via transfer precompile by sending tx from GoldToken addr
676+
tx, _ := types.SignTx(types.NewTransaction(uint64(i), transferPrecompile, big.NewInt(0), gas, big.NewInt(3), nil, nil, nil, data), signer, goldToken.key)
677+
b.AddTx(tx)
678+
target = tx.Hash()
679+
}))
680+
tracerStr := "callTracer"
681+
result, err := api.TraceTransaction(context.Background(), target, &TraceConfig{Tracer: &tracerStr})
682+
683+
if err != nil {
684+
t.Errorf("Failed to trace transaction %v", err)
685+
}
686+
687+
ret := new(callTrace)
688+
if err := json.Unmarshal(result.(json.RawMessage), ret); err != nil {
689+
t.Fatalf("failed to unmarshal trace result: %v", err)
690+
}
691+
692+
// Registry ABI packed version of Registry.getAddressFor("GoldToken")
693+
packedGetAddressForGoldToken := common.FromHex("0xdd927233d7e89ade8430819f08bf97a087285824af3351ee12d72a2d132b0c6c0687bfaf")
694+
expectedTrace := &callTrace{
695+
// Outer transaction call
696+
Type: "CALL",
697+
From: goldToken.addr,
698+
To: transferPrecompile,
699+
Input: data,
700+
Output: data,
701+
Gas: newRPCUint64(20112),
702+
// Note that top-level traces do not currently include intrinsic gas
703+
GasUsed: newRPCUint64(params.CallValueTransferGas),
704+
Value: (*hexutil.Big)(big.NewInt(0)),
705+
Calls: []callTrace{
706+
{
707+
// Lookup of GoldToken contract address with capped gas
708+
Type: "STATICCALL",
709+
From: common.ZeroAddress,
710+
To: registryProxyAddr,
711+
Input: packedGetAddressForGoldToken,
712+
Output: common.LeftPadBytes(goldToken.addr.Bytes(), 32),
713+
Gas: newRPCUint64(params.MaxGasForGetAddressFor),
714+
GasUsed: newRPCUint64(0),
715+
Calls: []callTrace{
716+
{
717+
// RegistryProxy -> RegistryImpl delegate call to
718+
// retrieve GoldToken address
719+
Type: "DELEGATECALL",
720+
From: registryProxyAddr,
721+
To: registryImplAddr,
722+
Input: packedGetAddressForGoldToken,
723+
Output: common.LeftPadBytes(goldToken.addr.Bytes(), 32),
724+
Gas: newRPCUint64(0),
725+
GasUsed: newRPCUint64(0),
726+
},
727+
},
728+
},
729+
},
730+
}
731+
if !jsonEqual(ret, expectedTrace) {
732+
t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, expectedTrace)
733+
}
734+
}
735+
547736
func TestTraceBlock(t *testing.T) {
548737
t.Parallel()
549738

test/account.go

+55-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/celo-org/celo-blockchain/common"
99
"github.com/celo-org/celo-blockchain/core/types"
10+
"github.com/celo-org/celo-blockchain/mycelo/contract"
1011
"github.com/celo-org/celo-blockchain/mycelo/env"
1112
"github.com/celo-org/celo-blockchain/params"
1213
)
@@ -44,14 +45,16 @@ func (a *Account) SendCelo(ctx context.Context, recipient common.Address, value
4445
}
4546

4647
signer := types.MakeSigner(a.ChainConfig, new(big.Int).SetUint64(num))
47-
tx, err := ValueTransferTransaction(
48+
tx, err := BuildSignedTransaction(
4849
node.WsClient,
4950
a.Key,
5051
a.Address,
5152
recipient,
5253
*a.Nonce,
5354
big.NewInt(value),
54-
signer)
55+
signer,
56+
nil,
57+
)
5558

5659
if err != nil {
5760
return nil, err
@@ -118,6 +121,56 @@ func (a *Account) SendCeloTracked(ctx context.Context, recipient common.Address,
118121
return node.Tracker.GetProcessedTx(tx.Hash()), nil
119122
}
120123

124+
// SendCelo submits a transaction to `node` that invokes the equivalent of
125+
// GoldToken.transfer(recipient, value), sent from the calling account.
126+
// The submitted transaction is returned.
127+
func (a *Account) SendCeloViaGoldToken(ctx context.Context, recipient common.Address, value int64, node *Node) (*types.Transaction, error) {
128+
var err error
129+
// Lazy set nonce
130+
if a.Nonce == nil {
131+
a.Nonce = new(uint64)
132+
*a.Nonce, err = node.WsClient.PendingNonceAt(ctx, a.Address)
133+
if err != nil {
134+
return nil, err
135+
}
136+
}
137+
num, err := node.WsClient.BlockNumber(ctx)
138+
if err != nil {
139+
return nil, err
140+
}
141+
142+
signer := types.MakeSigner(a.ChainConfig, new(big.Int).SetUint64(num))
143+
goldTokenName := "GoldToken"
144+
goldTokenProxyAddr, err := env.ProxyAddressFor(goldTokenName)
145+
if err != nil {
146+
return nil, err
147+
}
148+
goldTokenABI := contract.AbiFor(goldTokenName)
149+
data, err := goldTokenABI.Pack("transfer", recipient, big.NewInt(value))
150+
if err != nil {
151+
return nil, err
152+
}
153+
tx, err := BuildSignedTransaction(
154+
node.WsClient,
155+
a.Key,
156+
a.Address,
157+
goldTokenProxyAddr,
158+
*a.Nonce,
159+
big.NewInt(0),
160+
signer,
161+
data,
162+
)
163+
if err != nil {
164+
return nil, err
165+
}
166+
err = node.WsClient.SendTransaction(ctx, tx)
167+
if err != nil {
168+
return nil, err
169+
}
170+
*a.Nonce++
171+
return tx, nil
172+
}
173+
121174
// Accounts converts a slice of env.Account objects to Account objects.
122175
func Accounts(accts []env.Account, chainConfig *params.ChainConfig) []*Account {
123176
accounts := make([]*Account, 0, len(accts))

test/node.go

+5-7
Original file line numberDiff line numberDiff line change
@@ -477,17 +477,16 @@ func (n Network) Shutdown() []error {
477477
return errors
478478
}
479479

480-
// ValueTransferTransaction builds a signed value transfer transaction from the
481-
// sender to the recipient with the given value and nonce, it uses the client
482-
// to suggest a gas price and to estimate the gas.
483-
func ValueTransferTransaction(
480+
// Uses the client to suggest a gas price and to estimate the gas.
481+
func BuildSignedTransaction(
484482
client *ethclient.Client,
485483
senderKey *ecdsa.PrivateKey,
486484
sender,
487485
recipient common.Address,
488486
nonce uint64,
489487
value *big.Int,
490488
signer types.Signer,
489+
data []byte,
491490
) (*types.Transaction, error) {
492491
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
493492
defer cancel()
@@ -497,14 +496,13 @@ func ValueTransferTransaction(
497496
return nil, fmt.Errorf("failed to suggest gas price: %v", err)
498497
}
499498

500-
msg := ethereum.CallMsg{From: sender, To: &recipient, GasPrice: gasPrice, Value: value}
499+
msg := ethereum.CallMsg{From: sender, To: &recipient, GasPrice: gasPrice, Value: value, Data: data}
501500
gasLimit, err := client.EstimateGas(ctx, msg)
502501
if err != nil {
503502
return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
504503
}
505-
506504
// Create the transaction and sign it
507-
rawTx := types.NewTransactionEthCompatible(nonce, recipient, value, gasLimit, gasPrice, nil)
505+
rawTx := types.NewTransactionEthCompatible(nonce, recipient, value, gasLimit, gasPrice, data)
508506
signed, err := types.SignTx(rawTx, signer, senderKey)
509507
if err != nil {
510508
return nil, err

0 commit comments

Comments
 (0)