diff --git a/jsonrpc/types/tx.go b/jsonrpc/types/tx.go index ff9b20d..9ae834d 100644 --- a/jsonrpc/types/tx.go +++ b/jsonrpc/types/tx.go @@ -39,14 +39,6 @@ func NewRPCTransaction(tx *coretypes.Transaction, blockHash common.Hash, blockNu from, _ := coretypes.Sender(signer, tx) v, r, s := tx.RawSignatureValues() al := tx.AccessList() - var yparity *hexutil.Uint64 = new(hexutil.Uint64) - switch tx.Type() { - case coretypes.LegacyTxType: - yparity = nil - default: // Dynamic and Access List use yParity - *yparity = hexutil.Uint64(v.Sign()) - } - result := &RPCTransaction{ Type: hexutil.Uint64(tx.Type()), From: from, @@ -64,27 +56,69 @@ func NewRPCTransaction(tx *coretypes.Transaction, blockHash common.Hash, blockNu S: (*hexutil.Big)(s), ChainID: (*hexutil.Big)(chainID), Accesses: &al, - YParity: yparity, } + if blockHash != (common.Hash{}) { result.BlockHash = &blockHash result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } + switch tx.Type() { + case coretypes.LegacyTxType: // Legacy type returns nil on yparity + default: // Dynamic and Access List type returns yParity + yParityValue := (v.Uint64()) + result.YParity = (*hexutil.Uint64)(&yParityValue) + } + return result } func (rpcTx RPCTransaction) ToTransaction() *coretypes.Transaction { - return coretypes.NewTx(&coretypes.LegacyTx{ - Nonce: uint64(rpcTx.Nonce), - GasPrice: rpcTx.GasPrice.ToInt(), - Gas: uint64(rpcTx.Gas), - To: rpcTx.To, - Value: rpcTx.Value.ToInt(), - Data: rpcTx.Input, - V: rpcTx.V.ToInt(), - R: rpcTx.R.ToInt(), - S: rpcTx.S.ToInt(), - }) + + switch rpcTx.Type { + case coretypes.LegacyTxType: + return coretypes.NewTx(&coretypes.LegacyTx{ + Nonce: uint64(rpcTx.Nonce), + GasPrice: rpcTx.GasPrice.ToInt(), + Gas: uint64(rpcTx.Gas), + To: rpcTx.To, + Value: rpcTx.Value.ToInt(), + Data: rpcTx.Input, + V: rpcTx.V.ToInt(), + R: rpcTx.R.ToInt(), + S: rpcTx.S.ToInt(), + }) + case coretypes.AccessListTxType: + return coretypes.NewTx(&coretypes.AccessListTx{ + ChainID: rpcTx.ChainID.ToInt(), + Nonce: uint64(rpcTx.Nonce), + GasPrice: rpcTx.GasPrice.ToInt(), + Gas: uint64(rpcTx.Gas), + To: rpcTx.To, + Value: rpcTx.Value.ToInt(), + Data: rpcTx.Input, + AccessList: *rpcTx.Accesses, + V: rpcTx.V.ToInt(), + R: rpcTx.R.ToInt(), + S: rpcTx.S.ToInt(), + }) + case coretypes.DynamicFeeTxType: + return coretypes.NewTx((&coretypes.DynamicFeeTx{ + ChainID: rpcTx.ChainID.ToInt(), + Nonce: uint64(rpcTx.Nonce), + GasTipCap: rpcTx.GasTipCap.ToInt(), + GasFeeCap: rpcTx.GasFeeCap.ToInt(), + Gas: uint64(rpcTx.Gas), + To: rpcTx.To, + Value: rpcTx.Value.ToInt(), + Data: rpcTx.Input, + AccessList: *rpcTx.Accesses, + V: rpcTx.V.ToInt(), + R: rpcTx.R.ToInt(), + S: rpcTx.S.ToInt(), + })) + default: + return nil + } } diff --git a/jsonrpc/types/tx_test.go b/jsonrpc/types/tx_test.go new file mode 100644 index 0000000..dc10ce7 --- /dev/null +++ b/jsonrpc/types/tx_test.go @@ -0,0 +1,171 @@ +package types_test + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + coretypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + types "github.com/initia-labs/minievm/jsonrpc/types" + "github.com/stretchr/testify/require" +) + +func TestLegacyTxTypeRPCTransaction(t *testing.T) { + privateKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("Failed to generate private key: %v", err) + } + + toAddress := crypto.PubkeyToAddress(privateKey.PublicKey) + chainID := big.NewInt(1) + tx := coretypes.NewTx(&coretypes.LegacyTx{ + Nonce: 0, + GasPrice: big.NewInt(1000), + Gas: 1000, + To: &toAddress, + Value: big.NewInt(100), + Data: []byte{}, + V: nil, + R: nil, + S: nil, + }) + + signedTx, err := coretypes.SignTx(tx, coretypes.NewCancunSigner(chainID), privateKey) + if err != nil { + t.Fatalf("Failed to sign transaction: %v", err) + } + + rpcTx := types.NewRPCTransaction(signedTx, common.Hash{}, 0, 0, chainID) + ethTx := rpcTx.ToTransaction() + + err = matchTx(signedTx, ethTx) + require.NoError(t, err) + +} + +func TestAccessListTypeRPCTransaction(t *testing.T) { + privateKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("Failed to generate private key: %v", err) + } + + toAddress := crypto.PubkeyToAddress(privateKey.PublicKey) + chainID := big.NewInt(1) + tx := coretypes.NewTx(&coretypes.AccessListTx{ + ChainID: chainID, + Nonce: 0, + GasPrice: big.NewInt(1000), + Gas: 1000, + To: &toAddress, + Value: big.NewInt(100), + Data: []byte{}, + AccessList: []coretypes.AccessTuple{}, + V: nil, + R: nil, + S: nil, + }) + + signedTx, err := coretypes.SignTx(tx, coretypes.NewCancunSigner(chainID), privateKey) + if err != nil { + t.Fatalf("Failed to sign transaction: %v", err) + } + rpcTx := types.NewRPCTransaction(signedTx, common.Hash{}, 0, 0, chainID) + ethTx := rpcTx.ToTransaction() + ethTx.Hash() + err = matchTx(signedTx, ethTx) + require.NoError(t, err) +} + +func TestDynamicFeeTxTypeRPCTransaction(t *testing.T) { + privateKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("Failed to generate private key: %v", err) + } + + toAddress := crypto.PubkeyToAddress(privateKey.PublicKey) + chainID := big.NewInt(1) + tx := coretypes.NewTx(&coretypes.DynamicFeeTx{ + ChainID: chainID, + Nonce: 0, + GasTipCap: big.NewInt(20), + GasFeeCap: big.NewInt(100), + Gas: 21000, + To: &toAddress, + Value: big.NewInt(1000), + Data: []byte{}, + AccessList: []coretypes.AccessTuple{}, + V: nil, + R: nil, + S: nil, + }) + + signedTx, err := coretypes.SignTx(tx, coretypes.NewCancunSigner(chainID), privateKey) + if err != nil { + t.Fatalf("Failed to sign transaction: %v", err) + } + rpcTx := types.NewRPCTransaction(signedTx, common.Hash{}, 0, 0, chainID) + ethTx := rpcTx.ToTransaction() + + err = matchTx(signedTx, ethTx) + require.NoError(t, err) +} + +func matchTx(signedTx *coretypes.Transaction, ethTx *coretypes.Transaction) error { + if signedTx.Type() != ethTx.Type() { + return fmt.Errorf("Expected transaction type %v, got %v", signedTx.Type(), ethTx.Type()) + } + + if signedTx.Hash() != ethTx.Hash() { + return fmt.Errorf("Expected hash %v, got %v", signedTx.Hash(), ethTx.Hash()) + } + + if signedTx.Nonce() != ethTx.Nonce() { + return fmt.Errorf("Expected nonce %v, got %v", signedTx.Nonce(), ethTx.Nonce()) + } + + if signedTx.Gas() != ethTx.Gas() { + return fmt.Errorf("Expected gas %v, got %v", signedTx.Gas(), ethTx.Gas()) + } + + if signedTx.GasFeeCapCmp(ethTx) != 0 { + return fmt.Errorf("Expected gas price %v, got %v", signedTx.GasPrice(), ethTx.GasPrice()) + } + + if signedTx.GasFeeCapCmp(ethTx) != 0 { + return fmt.Errorf("Expected gas fee cap %v, got %v", signedTx.GasFeeCap(), ethTx.GasFeeCap()) + } + + if signedTx.GasTipCapCmp(ethTx) != 0 { + return fmt.Errorf("Expected gas tip cap %v, got %v", signedTx.GasTipCap(), ethTx.GasTipCap()) + } + + if signedTx.Value().Cmp(ethTx.Value()) != 0 { + return fmt.Errorf("Expected value %v, got %v", signedTx.Value(), ethTx.Value()) + } + + if signedTx.To() == nil || ethTx.To() == nil || *signedTx.To() != *ethTx.To() { + return fmt.Errorf("Expected to address %v, got %v", signedTx.To(), ethTx.To()) + } + signedTxAccesList := signedTx.AccessList() + ethTxAccessList := ethTx.AccessList() + if len(signedTxAccesList) != len(ethTxAccessList) { + return fmt.Errorf("Expected access list length %v, got %v", len(signedTxAccesList), len(ethTxAccessList)) + } + for i := range signedTxAccesList { + if signedTxAccesList[i].Address != ethTxAccessList[i].Address || len(signedTxAccesList[i].StorageKeys) != len(ethTxAccessList[i].StorageKeys) { + return fmt.Errorf("Expected access list %v, got %v", signedTx.AccessList(), ethTxAccessList) + } + for j := range signedTxAccesList[i].StorageKeys { + if signedTxAccesList[i].StorageKeys[j] != ethTxAccessList[i].StorageKeys[j] { + return fmt.Errorf("Expected access list %v, got %v", signedTx.AccessList(), ethTxAccessList) + } + } + } + if string(signedTx.Data()) != string(ethTx.Data()) { + return fmt.Errorf("Expected data %v, got %v", signedTx.Data(), ethTx.Data()) + } + + return nil +} diff --git a/x/evm/keeper/txutils.go b/x/evm/keeper/txutils.go index a2d149f..2ee9d6a 100644 --- a/x/evm/keeper/txutils.go +++ b/x/evm/keeper/txutils.go @@ -91,8 +91,20 @@ func (u *TxUtils) ConvertEthereumTxToCosmosTx(ctx context.Context, ethTx *corety if err != nil { return nil, err } - var accessList []types.AccessTuple - + var accessList []types.AccessTuple = nil + if len(ethTx.AccessList()) > 0 { + accessList = make([]types.AccessTuple, len(ethTx.AccessList())) + } + for i, al := range ethTx.AccessList() { + storageKeys := make([]string, len(al.StorageKeys)) + for j, s := range al.StorageKeys { + storageKeys[j] = s.String() + } + accessList[i] = types.AccessTuple{ + Address: al.Address.String(), + StorageKeys: storageKeys, + } + } // sig bytes v, r, s := ethTx.RawSignatureValues() sigBytes := make([]byte, 65) @@ -101,30 +113,8 @@ func (u *TxUtils) ConvertEthereumTxToCosmosTx(ctx context.Context, ethTx *corety sigBytes[64] = byte(new(big.Int).Sub(v, new(big.Int).Add(new(big.Int).Add(ethChainID, ethChainID), big.NewInt(35))).Uint64()) case coretypes.AccessListTxType: sigBytes[64] = byte(v.Uint64()) - accessList = make([]types.AccessTuple, len(ethTx.AccessList())) - for i, al := range ethTx.AccessList() { - storageKeys := make([]string, len(al.StorageKeys)) - for j, s := range al.StorageKeys { - storageKeys[j] = s.String() - } - accessList[i] = types.AccessTuple{ - Address: al.Address.String(), - StorageKeys: storageKeys, - } - } case coretypes.DynamicFeeTxType: sigBytes[64] = byte(v.Uint64()) - accessList = make([]types.AccessTuple, len(ethTx.AccessList())) - for i, al := range ethTx.AccessList() { - storageKeys := make([]string, len(al.StorageKeys)) - for j, s := range al.StorageKeys { - storageKeys[j] = s.String() - } - accessList[i] = types.AccessTuple{ - Address: al.Address.String(), - StorageKeys: storageKeys, - } - } default: return nil, sdkerrors.ErrorInvalidSigner.Wrapf("unsupported tx type: %d", ethTx.Type()) } diff --git a/x/evm/keeper/txutils_test.go b/x/evm/keeper/txutils_test.go index 7b6fa4e..15e1cf9 100644 --- a/x/evm/keeper/txutils_test.go +++ b/x/evm/keeper/txutils_test.go @@ -176,7 +176,7 @@ func Test_AccessTxConversion(t *testing.T) { To: ðFactoryAddr, Data: inputBz, Value: value, - AccessList: coretypes.AccessList{}, + AccessList: nil, }) signer := coretypes.LatestSignerForChainID(ethChainID) @@ -209,7 +209,7 @@ func Test_AccessTxConversion(t *testing.T) { ContractAddr: ethFactoryAddr.Hex(), Input: hexutil.Encode(inputBz), Value: math.NewInt(100), - AccessList: []types.AccessTuple{}, + AccessList: nil, }) authTx := sdkTx.(authsigning.Tx)