From 1000461a5587e5346f81e51300e3816656fb17ba Mon Sep 17 00:00:00 2001 From: yihuang Date: Thu, 21 Oct 2021 03:00:17 +0800 Subject: [PATCH] rpc: transaction receipt test (#678) * Problem: No test on the transaction receipt api Closes: #582 - add receipt rpc test for erc20 transfer logs * lower gas fee * build with go 1.17 in CI * use go 1.17 in test-solidity * fix merge --- .github/workflows/build.yml | 3 + .github/workflows/test.yml | 3 + scripts/gen-tests-artifacts.sh | 2 +- tests/rpc/rpc_test.go | 85 +++++++++++++++++++++ x/evm/keeper/benchmark_test.go | 8 +- x/evm/keeper/grpc_query_test.go | 12 +-- x/evm/keeper/keeper_test.go | 30 +------- x/evm/{keeper => types}/ERC20Contract.json | 0 x/evm/types/compiled_contract.go | 88 ++++++++++++++++++++++ 9 files changed, 193 insertions(+), 38 deletions(-) rename x/evm/{keeper => types}/ERC20Contract.json (100%) create mode 100644 x/evm/types/compiled_contract.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92a0a651..9b2557a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.3.5 + - uses: actions/setup-go@v2 + with: + go-version: 1.17 - uses: technote-space/get-diff-action@v5 id: git_diff with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 78e12eb8..96e08cf5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,6 +81,9 @@ jobs: timeout-minutes: 60 steps: - uses: actions/checkout@v2.3.5 + - uses: actions/setup-go@v2 + with: + go-version: 1.17 - uses: technote-space/get-diff-action@v5 id: git_diff with: diff --git a/scripts/gen-tests-artifacts.sh b/scripts/gen-tests-artifacts.sh index b8c3f9ab..86a90749 100755 --- a/scripts/gen-tests-artifacts.sh +++ b/scripts/gen-tests-artifacts.sh @@ -3,4 +3,4 @@ # prepare sloc v0.5.17 in PATH solc --combined-json bin,abi --allow-paths . ./tests/solidity/suites/staking/contracts/test/mocks/StandardTokenMock.sol \ | jq ".contracts.\"./tests/solidity/suites/staking/contracts/test/mocks/StandardTokenMock.sol:StandardTokenMock\"" \ - > x/evm/keeper/ERC20Contract.json + > x/evm/types/ERC20Contract.json diff --git a/tests/rpc/rpc_test.go b/tests/rpc/rpc_test.go index a8a0a7c1..ee95a0cb 100644 --- a/tests/rpc/rpc_test.go +++ b/tests/rpc/rpc_test.go @@ -527,6 +527,84 @@ func TestEth_GetTransactionReceipt(t *testing.T) { require.Equal(t, []interface{}{}, receipt["logs"].([]interface{})) } +// deployTestERC20Contract deploys a contract that emits an event in the constructor +func deployTestERC20Contract(t *testing.T) common.Address { + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", from) + + ctorArgs, err := evmtypes.ERC20Contract.ABI.Pack("", common.BytesToAddress(from), big.NewInt(100000000)) + require.NoError(t, err) + data := append(evmtypes.ERC20Contract.Bin, ctorArgs...) + param[0]["data"] = hexutil.Encode(data) + + param[0]["gas"] = "0x200000" + param[0]["gasPrice"] = "0x1" + + rpcRes := call(t, "eth_sendTransaction", param) + + var hash hexutil.Bytes + err = json.Unmarshal(rpcRes.Result, &hash) + require.NoError(t, err) + + receipt := expectSuccessReceipt(t, hash) + contractAddress := common.HexToAddress(receipt["contractAddress"].(string)) + require.NotEqual(t, common.Address{}, contractAddress) + + require.NotNil(t, receipt["logs"]) + + return contractAddress +} + +// sendTestERC20Transaction sends a typical erc20 transfer transaction +func sendTestERC20Transaction(t *testing.T, contract common.Address, amount *big.Int) hexutil.Bytes { + // transfer + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", from) + param[0]["to"] = contract.Hex() + data, err := evmtypes.ERC20Contract.ABI.Pack("transfer", common.BigToAddress(big.NewInt(1)), amount) + require.NoError(t, err) + param[0]["data"] = hexutil.Encode(data) + param[0]["gas"] = "0x50000" + param[0]["gasPrice"] = "0x1" + + rpcRes := call(t, "eth_sendTransaction", param) + + var hash hexutil.Bytes + err = json.Unmarshal(rpcRes.Result, &hash) + require.NoError(t, err) + return hash +} + +func TestEth_GetTransactionReceipt_ERC20Transfer(t *testing.T) { + // deploy erc20 contract + contract := deployTestERC20Contract(t) + amount := big.NewInt(10) + hash := sendTestERC20Transaction(t, contract, amount) + receipt := expectSuccessReceipt(t, hash) + + require.Equal(t, 1, len(receipt["logs"].([]interface{}))) + log := receipt["logs"].([]interface{})[0].(map[string]interface{}) + + require.Equal(t, contract, common.HexToAddress(log["address"].(string))) + + valueBz, err := hexutil.Decode(log["data"].(string)) + require.NoError(t, err) + require.Equal(t, amount, big.NewInt(0).SetBytes(valueBz)) + + require.Equal(t, false, log["removed"].(bool)) + require.Equal(t, "0x0", log["logIndex"].(string)) + require.Equal(t, "0x0", log["transactionIndex"].(string)) + + expectedTopics := []interface{}{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000" + fmt.Sprintf("%x", from), + "0x0000000000000000000000000000000000000000000000000000000000000001", + } + require.Equal(t, expectedTopics, log["topics"].([]interface{})) +} + // deployTestContract deploys a contract that emits an event in the constructor func deployTestContract(t *testing.T) (hexutil.Bytes, map[string]interface{}) { param := make([]map[string]string, 1) @@ -592,6 +670,13 @@ func waitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { } } +func expectSuccessReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { + receipt := waitForReceipt(t, hash) + require.NotNil(t, receipt, "transaction failed") + require.Equal(t, "0x1", receipt["status"].(string)) + return receipt +} + func TestEth_GetFilterChanges_NoTopics(t *testing.T) { rpcRes := call(t, "eth_blockNumber", []string{}) diff --git a/x/evm/keeper/benchmark_test.go b/x/evm/keeper/benchmark_test.go index 382cbfe8..7d1c80db 100644 --- a/x/evm/keeper/benchmark_test.go +++ b/x/evm/keeper/benchmark_test.go @@ -63,7 +63,7 @@ func DoBenchmark(b *testing.B, txBuilder TxBuilder) { func BenchmarkTokenTransfer(b *testing.B) { DoBenchmark(b, func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx { - input, err := ContractABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000)) + input, err := types.ERC20Contract.ABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000)) require.NoError(b, err) nonce := suite.app.EvmKeeper.GetNonce(suite.address) return types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 410000, big.NewInt(1), nil, nil, input, nil) @@ -72,7 +72,7 @@ func BenchmarkTokenTransfer(b *testing.B) { func BenchmarkEmitLogs(b *testing.B) { DoBenchmark(b, func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx { - input, err := ContractABI.Pack("benchmarkLogs", big.NewInt(1000)) + input, err := types.ERC20Contract.ABI.Pack("benchmarkLogs", big.NewInt(1000)) require.NoError(b, err) nonce := suite.app.EvmKeeper.GetNonce(suite.address) return types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 4100000, big.NewInt(1), nil, nil, input, nil) @@ -81,7 +81,7 @@ func BenchmarkEmitLogs(b *testing.B) { func BenchmarkTokenTransferFrom(b *testing.B) { DoBenchmark(b, func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx { - input, err := ContractABI.Pack("transferFrom", suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(0)) + input, err := types.ERC20Contract.ABI.Pack("transferFrom", suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(0)) require.NoError(b, err) nonce := suite.app.EvmKeeper.GetNonce(suite.address) return types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 410000, big.NewInt(1), nil, nil, input, nil) @@ -90,7 +90,7 @@ func BenchmarkTokenTransferFrom(b *testing.B) { func BenchmarkTokenMint(b *testing.B) { DoBenchmark(b, func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx { - input, err := ContractABI.Pack("mint", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000)) + input, err := types.ERC20Contract.ABI.Pack("mint", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000)) require.NoError(b, err) nonce := suite.app.EvmKeeper.GetNonce(suite.address) return types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 410000, big.NewInt(1), nil, nil, input, nil) diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index 716e2938..eb94046a 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -514,9 +514,9 @@ func (suite *KeeperTestSuite) TestEstimateGas() { }, false, 0, false}, // estimate gas of an erc20 contract deployment, the exact gas number is checked with geth {"contract deployment", func() { - ctorArgs, err := ContractABI.Pack("", &suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) + ctorArgs, err := types.ERC20Contract.ABI.Pack("", &suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) suite.Require().NoError(err) - data := append(ContractBin, ctorArgs...) + data := append(types.ERC20Contract.Bin, ctorArgs...) args = types.TransactionArgs{ From: &suite.address, Data: (*hexutil.Bytes)(&data), @@ -526,7 +526,7 @@ func (suite *KeeperTestSuite) TestEstimateGas() { {"erc20 transfer", func() { contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) suite.Commit() - transferData, err := ContractABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000)) + transferData, err := types.ERC20Contract.ABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000)) suite.Require().NoError(err) args = types.TransactionArgs{To: &contractAddr, From: &suite.address, Data: (*hexutil.Bytes)(&transferData)} }, true, 51880, false}, @@ -549,9 +549,9 @@ func (suite *KeeperTestSuite) TestEstimateGas() { gasCap = 20000 }, false, 0, true}, {"contract deployment w/ dynamicTxFee", func() { - ctorArgs, err := ContractABI.Pack("", &suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) + ctorArgs, err := types.ERC20Contract.ABI.Pack("", &suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) suite.Require().NoError(err) - data := append(ContractBin, ctorArgs...) + data := append(types.ERC20Contract.Bin, ctorArgs...) args = types.TransactionArgs{ From: &suite.address, Data: (*hexutil.Bytes)(&data), @@ -560,7 +560,7 @@ func (suite *KeeperTestSuite) TestEstimateGas() { {"erc20 transfer w/ dynamicTxFee", func() { contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) suite.Commit() - transferData, err := ContractABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000)) + transferData, err := types.ERC20Contract.ABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000)) suite.Require().NoError(err) args = types.TransactionArgs{To: &contractAddr, From: &suite.address, Data: (*hexutil.Bytes)(&transferData)} }, true, 51880, true}, diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 0f164db1..980daa02 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -27,7 +27,6 @@ import ( ethermint "github.com/tharsis/ethermint/types" "github.com/tharsis/ethermint/x/evm/types" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -40,29 +39,6 @@ import ( "github.com/tendermint/tendermint/version" ) -var ( - //go:embed ERC20Contract.json - compiledContractJSON []byte - ContractBin []byte - ContractABI abi.ABI -) - -func init() { - var tmp struct { - Abi string - Bin string - } - err := json.Unmarshal(compiledContractJSON, &tmp) - if err != nil { - panic(err) - } - ContractBin = common.FromHex(tmp.Bin) - err = json.Unmarshal([]byte(tmp.Abi), &ContractABI) - if err != nil { - panic(err) - } -} - var testTokens = sdk.NewIntWithDecimal(1000, 18) type KeeperTestSuite struct { @@ -193,10 +169,10 @@ func (suite *KeeperTestSuite) DeployTestContract(t require.TestingT, owner commo ctx := sdk.WrapSDKContext(suite.ctx) chainID := suite.app.EvmKeeper.ChainID() - ctorArgs, err := ContractABI.Pack("", owner, supply) + ctorArgs, err := types.ERC20Contract.ABI.Pack("", owner, supply) require.NoError(t, err) - data := append(ContractBin, ctorArgs...) + data := append(types.ERC20Contract.Bin, ctorArgs...) args, err := json.Marshal(&types.TransactionArgs{ From: &suite.address, Data: (*hexutil.Bytes)(&data), @@ -250,7 +226,7 @@ func (suite *KeeperTestSuite) TransferERC20Token(t require.TestingT, contractAdd ctx := sdk.WrapSDKContext(suite.ctx) chainID := suite.app.EvmKeeper.ChainID() - transferData, err := ContractABI.Pack("transfer", to, amount) + transferData, err := types.ERC20Contract.ABI.Pack("transfer", to, amount) require.NoError(t, err) args, err := json.Marshal(&types.TransactionArgs{To: &contractAddr, From: &from, Data: (*hexutil.Bytes)(&transferData)}) require.NoError(t, err) diff --git a/x/evm/keeper/ERC20Contract.json b/x/evm/types/ERC20Contract.json similarity index 100% rename from x/evm/keeper/ERC20Contract.json rename to x/evm/types/ERC20Contract.json diff --git a/x/evm/types/compiled_contract.go b/x/evm/types/compiled_contract.go new file mode 100644 index 00000000..e83ee459 --- /dev/null +++ b/x/evm/types/compiled_contract.go @@ -0,0 +1,88 @@ +package types + +import ( + // embed compiled smart contract + _ "embed" + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +// HexString is a byte array that serializes to hex +type HexString []byte + +// MarshalJSON serializes ByteArray to hex +func (s HexString) MarshalJSON() ([]byte, error) { + return json.Marshal(fmt.Sprintf("%x", string(s))) +} + +// UnmarshalJSON deserializes ByteArray to hex +func (s *HexString) UnmarshalJSON(data []byte) error { + var x string + if err := json.Unmarshal(data, &x); err != nil { + return err + } + str, err := hex.DecodeString(x) + if err != nil { + return err + } + *s = str + return nil +} + +// CompiledContract contains compiled bytecode and abi +type CompiledContract struct { + ABI abi.ABI + Bin HexString +} + +type jsonCompiledContract struct { + ABI string + Bin HexString +} + +// MarshalJSON serializes ByteArray to hex +func (s CompiledContract) MarshalJSON() ([]byte, error) { + abi1, err := json.Marshal(s.ABI) + if err != nil { + return nil, err + } + return json.Marshal(jsonCompiledContract{ABI: string(abi1), Bin: s.Bin}) +} + +// UnmarshalJSON deserializes ByteArray to hex +func (s *CompiledContract) UnmarshalJSON(data []byte) error { + var x jsonCompiledContract + if err := json.Unmarshal(data, &x); err != nil { + return err + } + + s.Bin = x.Bin + if err := json.Unmarshal([]byte(x.ABI), &s.ABI); err != nil { + fmt.Println("unmarshal abi fail", x.ABI, string(data)) + return err + } + + return nil +} + +var ( + //go:embed ERC20Contract.json + erc20JSON []byte + + // ERC20Contract is the compiled test erc20 contract + ERC20Contract CompiledContract +) + +func init() { + err := json.Unmarshal(erc20JSON, &ERC20Contract) + if err != nil { + panic(err) + } + + if len(ERC20Contract.Bin) == 0 { + panic("load contract failed") + } +}