diff --git a/action/actctx.go b/action/actctx.go index 30ab79f812..cc881748e8 100644 --- a/action/actctx.go +++ b/action/actctx.go @@ -6,6 +6,7 @@ package action import ( + "fmt" "math/big" "github.com/ethereum/go-ethereum/core/types" @@ -158,3 +159,33 @@ func (act *AbstractAction) fromProto(pb *iotextypes.ActionCore) error { } return nil } + +func (act *AbstractAction) convertToTx() TxCommonWithProto { + switch act.version { + case LegacyTxType: + tx := LegacyTx{ + chainID: act.chainID, + nonce: act.nonce, + gasLimit: act.gasLimit, + gasPrice: &big.Int{}, + } + if act.gasPrice != nil { + tx.gasPrice.Set(act.gasPrice) + } + return &tx + case AccessListTxType: + tx := AccessListTx{ + chainID: act.chainID, + nonce: act.nonce, + gasLimit: act.gasLimit, + gasPrice: &big.Int{}, + accessList: act.accessList, + } + if act.gasPrice != nil { + tx.gasPrice.Set(act.gasPrice) + } + return &tx + default: + panic(fmt.Sprintf("unsupported action version = %d", act.version)) + } +} diff --git a/action/action.go b/action/action.go index b335cddb04..3f9401c62b 100644 --- a/action/action.go +++ b/action/action.go @@ -15,6 +15,13 @@ import ( "github.com/pkg/errors" ) +const ( + LegacyTxType = 1 + AccessListTxType = 2 + DynamicFeeTxType = 3 + BlobTxType = 4 +) + type ( // Action is the action can be Executed in protocols. The method is added to avoid mistakenly used empty interface as action. Action interface { diff --git a/action/builder.go b/action/builder.go index ddc7bab699..596f5b0d31 100644 --- a/action/builder.go +++ b/action/builder.go @@ -79,23 +79,24 @@ func (b *Builder) Build() AbstractAction { // TODO: change envelope to *envelope type EnvelopeBuilder struct { elp envelope + ab AbstractAction } // SetVersion sets action's version. func (b *EnvelopeBuilder) SetVersion(v uint32) *EnvelopeBuilder { - b.elp.version = v + b.ab.version = v return b } // SetNonce sets action's nonce. func (b *EnvelopeBuilder) SetNonce(n uint64) *EnvelopeBuilder { - b.elp.nonce = n + b.ab.nonce = n return b } // SetGasLimit sets action's gas limit. func (b *EnvelopeBuilder) SetGasLimit(l uint64) *EnvelopeBuilder { - b.elp.gasLimit = l + b.ab.gasLimit = l return b } @@ -104,8 +105,8 @@ func (b *EnvelopeBuilder) SetGasPrice(p *big.Int) *EnvelopeBuilder { if p == nil { return b } - b.elp.gasPrice = &big.Int{} - b.elp.gasPrice.Set(p) + b.ab.gasPrice = &big.Int{} + b.ab.gasPrice.Set(p) return b } @@ -114,8 +115,8 @@ func (b *EnvelopeBuilder) SetGasPriceByBytes(buf []byte) *EnvelopeBuilder { if len(buf) == 0 { return b } - b.elp.gasPrice = &big.Int{} - b.elp.gasPrice.SetBytes(buf) + b.ab.gasPrice = &big.Int{} + b.ab.gasPrice.SetBytes(buf) return b } @@ -127,12 +128,12 @@ func (b *EnvelopeBuilder) SetAction(action actionPayload) *EnvelopeBuilder { // SetChainID sets action's chainID. func (b *EnvelopeBuilder) SetChainID(chainID uint32) *EnvelopeBuilder { - b.elp.chainID = chainID + b.ab.chainID = chainID return b } func (b *EnvelopeBuilder) SetAccessList(acl types.AccessList) *EnvelopeBuilder { - b.elp.accessList = acl + b.ab.accessList = acl return b } @@ -142,15 +143,17 @@ func (b *EnvelopeBuilder) Build() Envelope { } func (b *EnvelopeBuilder) build() Envelope { - if b.elp.gasPrice == nil { - b.elp.gasPrice = big.NewInt(0) + if b.ab.gasPrice == nil { + b.ab.gasPrice = big.NewInt(0) } - if b.elp.version == 0 { - b.elp.version = version.ProtocolVersion + if b.ab.version == 0 { + // default to version = 1 (legacy tx) + b.ab.version = LegacyTxType } if b.elp.payload == nil { panic("cannot build Envelope w/o a valid payload") } + b.elp.common = b.ab.convertToTx() return &b.elp } @@ -165,10 +168,10 @@ func (b *EnvelopeBuilder) BuildTransfer(tx *types.Transaction) (Envelope, error) } func (b *EnvelopeBuilder) setEnvelopeCommonFields(tx *types.Transaction) { - b.elp.nonce = tx.Nonce() - b.elp.gasPrice = new(big.Int).Set(tx.GasPrice()) - b.elp.gasLimit = tx.Gas() - b.elp.accessList = tx.AccessList() + b.ab.nonce = tx.Nonce() + b.ab.gasPrice = tx.GasPrice() + b.ab.gasLimit = tx.Gas() + b.ab.accessList = tx.AccessList() } func getRecipientAddr(addr *common.Address) string { diff --git a/action/envelope.go b/action/envelope.go index 8baf8f0dc4..837b280333 100644 --- a/action/envelope.go +++ b/action/envelope.go @@ -6,6 +6,7 @@ package action import ( + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -54,6 +55,17 @@ type ( TxBlob } + TxCommonWithProto interface { + TxCommon + Version() uint32 + ChainID() uint32 + SanityCheck() error + toProto() *iotextypes.ActionCore + setNonce(uint64) + setGas(uint64) + setChainID(uint32) + } + TxDynamicGas interface { GasTipCap() *big.Int GasFeeCap() *big.Int @@ -67,37 +79,57 @@ type ( } envelope struct { - AbstractAction + common TxCommonWithProto payload actionPayload } ) +func (elp *envelope) Version() uint32 { + return elp.common.Version() +} + +func (elp *envelope) ChainID() uint32 { + return elp.common.ChainID() +} + +func (elp *envelope) Nonce() uint64 { + return elp.common.Nonce() +} + func (elp *envelope) Gas() uint64 { - return elp.gasLimit + return elp.common.Gas() +} + +func (elp *envelope) GasPrice() *big.Int { + return elp.common.GasPrice() } func (elp *envelope) AccessList() types.AccessList { - return elp.accessList + return elp.common.AccessList() +} + +func (elp *envelope) GasTipCap() *big.Int { + return elp.common.GasTipCap() +} + +func (elp *envelope) GasFeeCap() *big.Int { + return elp.common.GasFeeCap() } func (elp *envelope) BlobGas() uint64 { - // TODO - return 0 + return elp.common.BlobGas() } func (elp *envelope) BlobGasFeeCap() *big.Int { - // TODO - return nil + return elp.common.BlobGasFeeCap() } func (elp *envelope) BlobHashes() []common.Hash { - // TODO - return nil + return elp.common.BlobHashes() } func (elp *envelope) BlobTxSidecar() *types.BlobTxSidecar { - // TODO - return nil + return elp.common.BlobTxSidecar() } func (elp *envelope) Value() *big.Int { @@ -138,7 +170,7 @@ func (elp *envelope) Cost() (*big.Int, error) { return nil, errors.Wrap(err, "failed to get payload's intrinsic gas") } if _, ok := elp.payload.(gasLimitForCost); ok { - gas = elp.Gas() + gas = elp.common.Gas() } if acl := elp.AccessList(); len(acl) > 0 { gas += uint64(len(acl)) * TxAccessListAddressGas @@ -169,7 +201,11 @@ func (elp *envelope) IntrinsicGas() (uint64, error) { // Size returns the size of envelope func (elp *envelope) Size() uint32 { - size := elp.BasicActionSize() + // VersionSizeInBytes + NonceSizeInBytes + GasSizeInBytes + var size uint32 = 4 + 8 + 8 + if gasPrice := elp.common.GasPrice(); gasPrice != nil { + size += uint32(len(gasPrice.Bytes())) + } if s, ok := elp.payload.(hasSize); ok { size += s.Size() } @@ -187,7 +223,7 @@ func (elp *envelope) ToEthTx(evmNetworkID uint32, encoding iotextypes.Encoding) // treat native tx as EVM LegacyTx fallthrough case encoding == iotextypes.Encoding_ETHEREUM_EIP155 || encoding == iotextypes.Encoding_ETHEREUM_UNPROTECTED: - return toLegacyTx(&elp.AbstractAction, elp.Action()) + return toLegacyEthTx(elp.common, elp.Action()) default: return nil, errors.Wrapf(ErrInvalidAct, "unsupported encoding type %v", encoding) } @@ -195,7 +231,7 @@ func (elp *envelope) ToEthTx(evmNetworkID uint32, encoding iotextypes.Encoding) // Proto convert Envelope to protobuf format. func (elp *envelope) Proto() *iotextypes.ActionCore { - actCore := elp.AbstractAction.toProto() + actCore := elp.common.toProto() // TODO assert each action switch act := elp.Action().(type) { @@ -253,10 +289,26 @@ func (elp *envelope) LoadProto(pbAct *iotextypes.ActionCore) error { if elp == nil { return ErrNilAction } - if err := elp.AbstractAction.fromProto(pbAct); err != nil { + if err := elp.loadProtoTxCommon(pbAct); err != nil { return err } + return elp.loadProtoActionPayload(pbAct) +} +func (elp *envelope) loadProtoTxCommon(pbAct *iotextypes.ActionCore) error { + var err error + switch pbAct.Version { + case LegacyTxType: + elp.common, err = fromProtoLegacyTx(pbAct) + case AccessListTxType: + elp.common, err = fromProtoAccessListTx(pbAct) + default: + panic(fmt.Sprintf("unsupported action version = %d", pbAct.Version)) + } + return err +} + +func (elp *envelope) loadProtoActionPayload(pbAct *iotextypes.ActionCore) error { switch { case pbAct.GetTransfer() != nil: act := &Transfer{} @@ -385,17 +437,23 @@ func (elp *envelope) LoadProto(pbAct *iotextypes.ActionCore) error { return nil } +func (elp *envelope) SetNonce(n uint64) { + elp.common.setNonce(n) +} + func (elp *envelope) SetGas(gas uint64) { - elp.SetGasLimit(gas) + elp.common.setGas(gas) } // SetChainID sets the chainID value -func (elp *envelope) SetChainID(chainID uint32) { elp.chainID = chainID } +func (elp *envelope) SetChainID(chainID uint32) { + elp.common.setChainID(chainID) +} // SanityCheck does the sanity check func (elp *envelope) SanityCheck() error { if err := elp.payload.SanityCheck(); err != nil { return err } - return elp.AbstractAction.SanityCheck() + return elp.common.SanityCheck() } diff --git a/action/envelope_test.go b/action/envelope_test.go index c3eec5b672..82fe089579 100644 --- a/action/envelope_test.go +++ b/action/envelope_test.go @@ -41,27 +41,21 @@ func TestEnvelope_Proto(t *testing.T) { proto := evlp.Proto() actCore := &iotextypes.ActionCore{ - Version: evlp.version, - Nonce: evlp.nonce, - GasLimit: evlp.gasLimit, - ChainID: evlp.chainID, + Version: evlp.Version(), + Nonce: evlp.Nonce(), + GasLimit: evlp.Gas(), + ChainID: evlp.ChainID(), } - actCore.GasPrice = evlp.gasPrice.String() + actCore.GasPrice = evlp.GasPrice().String() actCore.Action = &iotextypes.ActionCore_Transfer{Transfer: tsf.Proto()} req.Equal(actCore, proto) evlp2 := &envelope{} req.NoError(evlp2.LoadProto(proto)) - req.Equal(evlp.version, evlp2.version) - req.Equal(evlp.chainID, evlp2.chainID) - req.Equal(evlp.nonce, evlp2.nonce) - req.Equal(evlp.gasLimit, evlp2.gasLimit) - req.Equal(evlp.gasPrice, evlp2.gasPrice) + req.Equal(evlp, evlp2) tsf2, ok := evlp2.Action().(*Transfer) req.True(ok) - req.Equal(tsf.amount, tsf2.amount) - req.Equal(tsf.recipient, tsf2.recipient) - req.Equal(tsf.payload, tsf2.payload) + req.Equal(tsf, tsf2) } func TestEnvelope_Actions(t *testing.T) { diff --git a/action/evm_transaction.go b/action/evm_transaction.go index a6e8570719..4e7c286d49 100644 --- a/action/evm_transaction.go +++ b/action/evm_transaction.go @@ -6,11 +6,8 @@ package action import ( - "encoding/hex" "math/big" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/iotexproject/iotex-proto/golang/iotextypes" ) @@ -23,41 +20,6 @@ type ( } ) -func toAccessListProto(list types.AccessList) []*iotextypes.AccessTuple { - if len(list) == 0 { - return nil - } - proto := make([]*iotextypes.AccessTuple, len(list)) - for i, v := range list { - proto[i] = &iotextypes.AccessTuple{} - proto[i].Address = hex.EncodeToString(v.Address.Bytes()) - if numKey := len(v.StorageKeys); numKey > 0 { - proto[i].StorageKeys = make([]string, numKey) - for j, key := range v.StorageKeys { - proto[i].StorageKeys[j] = hex.EncodeToString(key.Bytes()) - } - } - } - return proto -} - -func fromAccessListProto(list []*iotextypes.AccessTuple) types.AccessList { - if len(list) == 0 { - return nil - } - accessList := make(types.AccessList, len(list)) - for i, v := range list { - accessList[i].Address = common.HexToAddress(v.Address) - if numKey := len(v.StorageKeys); numKey > 0 { - accessList[i].StorageKeys = make([]common.Hash, numKey) - for j, key := range v.StorageKeys { - accessList[i].StorageKeys[j] = common.HexToHash(key) - } - } - } - return accessList -} - // EffectiveGas returns the effective gas func EffectiveGasTip(tx TxDynamicGas, baseFee *big.Int) (*big.Int, error) { tip := tx.GasTipCap() diff --git a/action/execution_test.go b/action/execution_test.go index c6fd838c34..5a057c0f7e 100644 --- a/action/execution_test.go +++ b/action/execution_test.go @@ -33,7 +33,6 @@ func TestExecutionSignVerify(t *testing.T) { SetAction(ex).Build() elp, ok := eb.(*envelope) require.True(ok) - require.EqualValues(21, elp.BasicActionSize()) require.EqualValues(87, eb.Size()) w := AssembleSealedEnvelope(elp, executorKey.PublicKey(), []byte("lol")) @@ -126,8 +125,8 @@ func TestExecutionAccessList(t *testing.T) { identityset.Address(29).String(), big.NewInt(20), []byte("test")) - elp := (&EnvelopeBuilder{}).SetNonce(1).SetGasPrice(big.NewInt(1000000)). - SetAccessList(v.list).SetGasLimit(100).SetAction(ex).Build() + elp := (&EnvelopeBuilder{}).SetVersion(AccessListTxType).SetNonce(1).SetAccessList(v.list). + SetGasPrice(big.NewInt(1000000)).SetGasLimit(100).SetAction(ex).Build() require.NoError(ex1.LoadProto(ex.Proto())) require.Equal(ex, ex1) gas, err := elp.IntrinsicGas() diff --git a/action/protocol/execution/protocol_test.go b/action/protocol/execution/protocol_test.go index 480cf1e6de..50007b33ca 100644 --- a/action/protocol/execution/protocol_test.go +++ b/action/protocol/execution/protocol_test.go @@ -274,8 +274,13 @@ func readExecution( return nil, nil, err } exec := action.NewExecution(contractAddr, ecfg.Amount(), ecfg.ByteCode()) - elp := (&action.EnvelopeBuilder{}).SetNonce(state.PendingNonce()).SetGasPrice(ecfg.GasPrice()). - SetGasLimit(ecfg.GasLimit()).SetAccessList(ecfg.AccessList()).SetAction(exec).Build() + builder := (&action.EnvelopeBuilder{}).SetGasPrice(ecfg.GasPrice()).SetGasLimit(ecfg.GasLimit()). + SetNonce(state.PendingNonce()).SetAction(exec) + if len(ecfg.AccessList()) > 0 { + println("read acl") + builder.SetVersion(action.AccessListTxType).SetAccessList(ecfg.AccessList()) + } + elp := builder.Build() addr := ecfg.PrivateKey().PublicKey().Address() if addr == nil { return nil, nil, errors.New("failed to get address") @@ -320,15 +325,15 @@ func (sct *SmartContractTest) runExecutions( ecfg.Amount(), ecfg.ByteCode(), ) - builder := &action.EnvelopeBuilder{} - builder.SetAction(exec). - SetNonce(nonce). - SetGasLimit(ecfg.GasLimit()). - SetGasPrice(ecfg.GasPrice()). - SetAccessList(ecfg.AccessList()) + builder := (&action.EnvelopeBuilder{}).SetGasLimit(ecfg.GasLimit()).SetGasPrice(ecfg.GasPrice()). + SetNonce(nonce).SetAction(exec) if sct.InitGenesis.IsShanghai { builder.SetChainID(bc.ChainID()) } + if len(ecfg.AccessList()) > 0 { + println("write acl") + builder.SetVersion(action.AccessListTxType).SetAccessList(ecfg.AccessList()) + } elp := builder.Build() selp, err := action.Sign(elp, ecfg.PrivateKey()) if err != nil { @@ -1196,6 +1201,9 @@ func TestLondonEVM(t *testing.T) { t.Run("datacopy", func(t *testing.T) { NewSmartContractTest(t, "testdata-london/datacopy.json") }) + t.Run("datacopy-with-accesslist", func(t *testing.T) { + NewSmartContractTest(t, "testdata-london/datacopy-accesslist.json") + }) t.Run("CVE-2021-39137-attack-replay", func(t *testing.T) { NewSmartContractTest(t, "testdata-london/CVE-2021-39137-attack-replay.json") }) @@ -1229,6 +1237,9 @@ func TestShanghaiEVM(t *testing.T) { t.Run("datacopy", func(t *testing.T) { NewSmartContractTest(t, "testdata-shanghai/datacopy.json") }) + t.Run("datacopy-with-accesslist", func(t *testing.T) { + NewSmartContractTest(t, "testdata-shanghai/datacopy-accesslist.json") + }) t.Run("f.value", func(t *testing.T) { NewSmartContractTest(t, "testdata-shanghai/f.value.json") }) diff --git a/action/protocol/execution/testdata-london/datacopy-accesslist.json b/action/protocol/execution/testdata-london/datacopy-accesslist.json new file mode 100644 index 0000000000..75e3ae7b59 --- /dev/null +++ b/action/protocol/execution/testdata-london/datacopy-accesslist.json @@ -0,0 +1,52 @@ +{ + "initGenesis": { + "isBering" : true, + "isIceland" : true, + "isLondon" : true + }, + "initBalances": [{ + "account": "io1mflp9m6hcgm2qcghchsdqj3z3eccrnekx9p0ms", + "rawBalance": "1000000000000000000000000000" + }], + "deployments": [{ + "rawByteCode": "608060405234801561001057600080fd5b5061026d806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063574c807814610030575b600080fd5b61003861003a565b005b604080516003808252818301909252600091602082018180368337019050509050601160f81b81600081518110610073576100736101e7565b60200101906001600160f81b031916908160001a905350602260f81b816001815181106100a2576100a26101e7565b60200101906001600160f81b031916908160001a905350603360f81b816002815181106100d1576100d16101e7565b60200101906001600160f81b031916908160001a9053508051604080516003808252818301909252600091602082018180368337019050509050600082602185018460208701600462010000fa9050826000602084013e61013182610137565b50505050565b805161014a90600090602084019061014e565b5050565b82805461015a906101fd565b90600052602060002090601f01602090048101928261017c57600085556101c2565b82601f1061019557805160ff19168380011785556101c2565b828001600101855582156101c2579182015b828111156101c25782518255916020019190600101906101a7565b506101ce9291506101d2565b5090565b5b808211156101ce57600081556001016101d3565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168061021157607f821691505b60208210810361023157634e487b7160e01b600052602260045260246000fd5b5091905056fea2646970667358221220cb76f887319bd2bf08ab8098cd53e02dcf9d2c8b311ffdae7d73f2c4b7e2442264736f6c634300080e0033", + "rawPrivateKey": "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1", + "rawAmount": "0", + "rawGasLimit": 5000000, + "rawGasPrice": "0", + "rawExpectedGasConsumed": 199671, + "expectedStatus": 1, + "expectedBalances": [], + "comment": "deploy datacopy contract" + }], + "executions": [{ + "rawPrivateKey": "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1", + "rawByteCode": "574c8078", + "rawAmount": "0", + "rawGasLimit": 1000000, + "rawGasPrice": "0", + "rawAccessList": [ + { + "address": "0x1234567890abcdef1234567890abcdef12345678", + "storageKeys": [ + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef", + "0x1234567890123456789012345678901234567890123456789012345678901234" + ] + }, + { + "address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", + "storageKeys": [ + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef" + ] + } + ], + "rawExpectedGasConsumed": 44220, + "expectedStatus": 1, + "comment": "the data of return is [0x11, 0x22, 0x33]", + "expectedBlockInfos" : { + "txRootHash" : "31da3e807a653dd42dc74d6d5a3dde2239b4c41859de6c65e044376a1020f9ce", + "stateRootHash" : "3595fffd9f80f99887e3108d84f3558e2691ab9dc1c73f6279e3da5735d02afa", + "receiptRootHash" : "6f9c949c391a25e324555d1592924d49d91d8883d8511f8db45fbae8b0bcde99" + } + }] +} diff --git a/action/protocol/execution/testdata-shanghai/datacopy-accesslist.json b/action/protocol/execution/testdata-shanghai/datacopy-accesslist.json new file mode 100644 index 0000000000..2759b93cf4 --- /dev/null +++ b/action/protocol/execution/testdata-shanghai/datacopy-accesslist.json @@ -0,0 +1,53 @@ +{ + "initGenesis": { + "isBering" : true, + "isIceland" : true, + "isLondon" : true, + "isShanghai" : true + }, + "initBalances": [{ + "account": "io1mflp9m6hcgm2qcghchsdqj3z3eccrnekx9p0ms", + "rawBalance": "1000000000000000000000000000" + }], + "deployments": [{ + "rawByteCode": "608060405234801561001057600080fd5b5061026d806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063574c807814610030575b600080fd5b61003861003a565b005b604080516003808252818301909252600091602082018180368337019050509050601160f81b81600081518110610073576100736101e7565b60200101906001600160f81b031916908160001a905350602260f81b816001815181106100a2576100a26101e7565b60200101906001600160f81b031916908160001a905350603360f81b816002815181106100d1576100d16101e7565b60200101906001600160f81b031916908160001a9053508051604080516003808252818301909252600091602082018180368337019050509050600082602185018460208701600462010000fa9050826000602084013e61013182610137565b50505050565b805161014a90600090602084019061014e565b5050565b82805461015a906101fd565b90600052602060002090601f01602090048101928261017c57600085556101c2565b82601f1061019557805160ff19168380011785556101c2565b828001600101855582156101c2579182015b828111156101c25782518255916020019190600101906101a7565b506101ce9291506101d2565b5090565b5b808211156101ce57600081556001016101d3565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168061021157607f821691505b60208210810361023157634e487b7160e01b600052602260045260246000fd5b5091905056fea2646970667358221220cb76f887319bd2bf08ab8098cd53e02dcf9d2c8b311ffdae7d73f2c4b7e2442264736f6c634300080e0033", + "rawPrivateKey": "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1", + "rawAmount": "0", + "rawGasLimit": 5000000, + "rawGasPrice": "0", + "rawExpectedGasConsumed": 199671, + "expectedStatus": 1, + "expectedBalances": [], + "comment": "deploy datacopy contract" + }], + "executions": [{ + "rawPrivateKey": "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1", + "rawByteCode": "574c8078", + "rawAmount": "0", + "rawGasLimit": 1000000, + "rawGasPrice": "0", + "rawAccessList": [ + { + "address": "0x1234567890abcdef1234567890abcdef12345678", + "storageKeys": [ + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef", + "0x1234567890123456789012345678901234567890123456789012345678901234" + ] + }, + { + "address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", + "storageKeys": [ + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef" + ] + } + ], + "rawExpectedGasConsumed": 44220, + "expectedStatus": 1, + "comment": "the data of return is [0x11, 0x22, 0x33]", + "expectedBlockInfos" : { + "txRootHash" : "6a61ad22579c0548af74a0c8ae6239b3950df05a6e69f758f566cb85111275fd", + "stateRootHash" : "3595fffd9f80f99887e3108d84f3558e2691ab9dc1c73f6279e3da5735d02afa", + "receiptRootHash" : "03b68bde2535bd4a933bea0a861d1756e31e7cda47be9bb1d126277d4c8c4c3f" + } + }] +} diff --git a/action/rlp_tx.go b/action/rlp_tx.go index 2d4c876c4b..fb3a0280d1 100644 --- a/action/rlp_tx.go +++ b/action/rlp_tx.go @@ -129,7 +129,7 @@ func ExtractTypeSigPubkey(tx *types.Transaction) (iotextypes.Encoding, []byte, c // ====================================== // utility funcs to convert native action to eth tx // ====================================== -func toLegacyTx(ab *AbstractAction, act Action) (*types.Transaction, error) { +func toLegacyEthTx(ab TxCommon, act Action) (*types.Transaction, error) { tx, ok := act.(EthCompatibleAction) if !ok { // action type not supported diff --git a/action/transfer_test.go b/action/transfer_test.go index bbb0e58794..b0ac69ed97 100644 --- a/action/transfer_test.go +++ b/action/transfer_test.go @@ -28,7 +28,6 @@ func TestTransferSignVerify(t *testing.T) { SetAction(tsf).Build() elp, ok := eb.(*envelope) require.True(ok) - require.EqualValues(21, elp.BasicActionSize()) require.EqualValues(87, eb.Size()) w := AssembleSealedEnvelope(elp, senderKey.PublicKey(), []byte("lol")) diff --git a/action/tx_access_list.go b/action/tx_access_list.go new file mode 100644 index 0000000000..5c171bebd6 --- /dev/null +++ b/action/tx_access_list.go @@ -0,0 +1,163 @@ +// Copyright (c) 2024 IoTeX Foundation +// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability +// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. +// This source code is governed by Apache License 2.0 that can be found in the LICENSE file. + +package action + +import ( + "encoding/hex" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/pkg/errors" +) + +// AccessListTx represents EIP-2930 access list transaction +type AccessListTx struct { + chainID uint32 + nonce uint64 + gasLimit uint64 + gasPrice *big.Int + accessList types.AccessList +} + +func (tx *AccessListTx) Version() uint32 { + return AccessListTxType +} + +func (tx *AccessListTx) ChainID() uint32 { + return tx.chainID +} + +func (tx *AccessListTx) Nonce() uint64 { + return tx.nonce +} + +func (tx *AccessListTx) Gas() uint64 { + return tx.gasLimit +} + +func (tx *AccessListTx) GasPrice() *big.Int { + return tx.price() +} + +func (tx *AccessListTx) price() *big.Int { + p := &big.Int{} + if tx.gasPrice == nil { + return p + } + return p.Set(tx.gasPrice) +} + +func (tx *AccessListTx) AccessList() types.AccessList { + return tx.accessList +} + +func (tx *AccessListTx) GasTipCap() *big.Int { + return tx.price() +} + +func (tx *AccessListTx) GasFeeCap() *big.Int { + return tx.price() +} + +func (tx *AccessListTx) BlobGas() uint64 { return 0 } + +func (tx *AccessListTx) BlobGasFeeCap() *big.Int { return nil } + +func (tx *AccessListTx) BlobHashes() []common.Hash { return nil } + +func (tx *AccessListTx) BlobTxSidecar() *types.BlobTxSidecar { return nil } + +func (tx *AccessListTx) SanityCheck() error { + // Reject execution of negative gas price + if tx.gasPrice != nil && tx.gasPrice.Sign() < 0 { + return ErrNegativeValue + } + return nil +} + +func (tx *AccessListTx) toProto() *iotextypes.ActionCore { + actCore := iotextypes.ActionCore{ + Version: AccessListTxType, + Nonce: tx.nonce, + GasLimit: tx.gasLimit, + ChainID: tx.chainID, + } + if tx.gasPrice != nil { + actCore.GasPrice = tx.gasPrice.String() + } + if len(tx.accessList) > 0 { + actCore.AccessList = toAccessListProto(tx.accessList) + } + return &actCore +} + +func toAccessListProto(list types.AccessList) []*iotextypes.AccessTuple { + if len(list) == 0 { + return nil + } + proto := make([]*iotextypes.AccessTuple, len(list)) + for i, v := range list { + proto[i] = &iotextypes.AccessTuple{} + proto[i].Address = hex.EncodeToString(v.Address.Bytes()) + if numKey := len(v.StorageKeys); numKey > 0 { + proto[i].StorageKeys = make([]string, numKey) + for j, key := range v.StorageKeys { + proto[i].StorageKeys[j] = hex.EncodeToString(key.Bytes()) + } + } + } + return proto +} + +func fromAccessListProto(list []*iotextypes.AccessTuple) types.AccessList { + if len(list) == 0 { + return nil + } + accessList := make(types.AccessList, len(list)) + for i, v := range list { + accessList[i].Address = common.HexToAddress(v.Address) + if numKey := len(v.StorageKeys); numKey > 0 { + accessList[i].StorageKeys = make([]common.Hash, numKey) + for j, key := range v.StorageKeys { + accessList[i].StorageKeys[j] = common.HexToHash(key) + } + } + } + return accessList +} + +func fromProtoAccessListTx(pb *iotextypes.ActionCore) (*AccessListTx, error) { + var tx AccessListTx + tx.nonce = pb.GetNonce() + tx.gasLimit = pb.GetGasLimit() + tx.chainID = pb.GetChainID() + tx.gasPrice = &big.Int{} + + if price := pb.GetGasPrice(); len(price) > 0 { + var ok bool + if tx.gasPrice, ok = tx.gasPrice.SetString(price, 10); !ok { + return nil, errors.Errorf("invalid gasPrice %s", price) + } + } + if acl := pb.GetAccessList(); len(acl) > 0 { + tx.accessList = fromAccessListProto(acl) + } + return &tx, nil +} + +func (tx *AccessListTx) setNonce(n uint64) { + tx.nonce = n +} + +func (tx *AccessListTx) setGas(gas uint64) { + tx.gasLimit = gas +} + +func (tx *AccessListTx) setChainID(n uint32) { + tx.chainID = n +} diff --git a/action/tx_access_list_test.go b/action/tx_access_list_test.go new file mode 100644 index 0000000000..3a1ffe92a5 --- /dev/null +++ b/action/tx_access_list_test.go @@ -0,0 +1,109 @@ +// Copyright (c) 2024 IoTeX Foundation +// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability +// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. +// This source code is governed by Apache License 2.0 that can be found in the LICENSE file. + +package action + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + . "github.com/iotexproject/iotex-core/pkg/util/assertions" +) + +func TestAccessListTx(t *testing.T) { + r := require.New(t) + t.Run("proto", func(t *testing.T) { + tx := &AccessListTx{ + chainID: 3, + nonce: 8, + gasLimit: 1001, + gasPrice: big.NewInt(13), + accessList: types.AccessList{ + {Address: common.Address{}, StorageKeys: nil}, + {Address: _c1, StorageKeys: []common.Hash{_k1, {}, _k3}}, + {Address: _c2, StorageKeys: []common.Hash{_k2, _k3, _k4, _k1}}, + }, + } + r.EqualValues(AccessListTxType, tx.Version()) + r.EqualValues(8, tx.Nonce()) + r.EqualValues(1001, tx.Gas()) + r.Equal(big.NewInt(13), tx.GasPrice()) + r.Equal(big.NewInt(13), tx.GasTipCap()) + r.Equal(big.NewInt(13), tx.GasFeeCap()) + r.Equal(3, len(tx.AccessList())) + r.Zero(tx.BlobGas()) + r.Nil(tx.BlobGasFeeCap()) + r.Nil(tx.BlobHashes()) + r.Nil(tx.BlobTxSidecar()) + b := MustNoErrorV(proto.Marshal(tx.toProto())) + r.Equal("0802100818e9072202313328034a2a0a28303030303030303030303030303030303030303030303030303030303030303030303030303030304af0010a28303166633234363633333437306366363261653261393536643231653864343831633361363965311240303265393430646430666435623564663463666238643662636439633734656334333365396135633231616362373263626362356265396537313162363738661240303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030301240613631386561356234383965636134326633333161626362303833393466353831663265396461383963386565376537326337343732303438343261626538624ab2020a2833343730636636326165326139353664333864343831633361363965313231653031666332343636124065373730396161376161313631323436363734393139623266303239396539356362623663353438326535633334386431326466653232366637316636336436124061363138656135623438396563613432663333316162636230383339346635383166326539646138396338656537653732633734373230343834326162653862124038383164336264663265313362366538623664363835643232373761343866663337313431343935646464346533643732383966636661353537306632396631124030326539343064643066643562356466346366623864366263643963373465633433336539613563323161636237326362636235626539653731316236373866", hex.EncodeToString(b)) + pb := iotextypes.ActionCore{} + r.NoError(proto.Unmarshal(b, &pb)) + r.Equal(tx, MustNoErrorV(fromProtoAccessListTx(&pb))) + }) + ab := AbstractAction{ + version: AccessListTxType, + chainID: 3, + nonce: 8, + gasLimit: 1001, + gasTipCap: big.NewInt(10), + gasFeeCap: big.NewInt(30), + accessList: types.AccessList{ + {Address: common.Address{}, StorageKeys: nil}, + {Address: _c1, StorageKeys: []common.Hash{_k1, {}, _k3}}, + {Address: _c2, StorageKeys: []common.Hash{_k2, _k3, _k4, _k1}}, + }, + } + expect := &AccessListTx{ + chainID: 3, + nonce: 8, + gasLimit: 1001, + accessList: types.AccessList{ + {Address: common.Address{}, StorageKeys: nil}, + {Address: _c1, StorageKeys: []common.Hash{_k1, {}, _k3}}, + {Address: _c2, StorageKeys: []common.Hash{_k2, _k3, _k4, _k1}}, + }, + } + t.Run("convert", func(t *testing.T) { + for _, price := range []*big.Int{ + nil, big.NewInt(13), + } { + ab.gasPrice = price + tx := ab.convertToTx() + acl, ok := tx.(*AccessListTx) + r.True(ok) + if price == nil { + expect.gasPrice = new(big.Int) + } else { + expect.gasPrice = new(big.Int).Set(price) + } + r.EqualValues(AccessListTxType, acl.Version()) + r.Equal(expect, acl) + } + }) + t.Run("loadProtoTxCommon", func(t *testing.T) { + for _, price := range []*big.Int{ + nil, big.NewInt(13), + } { + expect.gasPrice = price + elp := envelope{} + r.NoError(elp.loadProtoTxCommon(expect.toProto())) + acl, ok := elp.common.(*AccessListTx) + r.True(ok) + r.EqualValues(AccessListTxType, acl.Version()) + if price == nil { + expect.gasPrice = new(big.Int) + } + r.Equal(expect, acl) + } + }) +} diff --git a/action/tx_legacy.go b/action/tx_legacy.go new file mode 100644 index 0000000000..e2a22b1959 --- /dev/null +++ b/action/tx_legacy.go @@ -0,0 +1,119 @@ +// Copyright (c) 2024 IoTeX Foundation +// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability +// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. +// This source code is governed by Apache License 2.0 that can be found in the LICENSE file. + +package action + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/pkg/errors" +) + +// LegacyTx is the legacy transaction +type LegacyTx struct { + chainID uint32 + nonce uint64 + gasLimit uint64 + gasPrice *big.Int +} + +func (tx *LegacyTx) Version() uint32 { + return LegacyTxType +} + +func (tx *LegacyTx) ChainID() uint32 { + return tx.chainID +} + +func (tx *LegacyTx) Nonce() uint64 { + return tx.nonce +} + +func (tx *LegacyTx) Gas() uint64 { + return tx.gasLimit +} + +func (tx *LegacyTx) GasPrice() *big.Int { + return tx.price() +} + +func (tx *LegacyTx) price() *big.Int { + p := &big.Int{} + if tx.gasPrice == nil { + return p + } + return p.Set(tx.gasPrice) +} + +func (tx *LegacyTx) AccessList() types.AccessList { + return nil +} + +func (tx *LegacyTx) GasTipCap() *big.Int { + return tx.price() +} + +func (tx *LegacyTx) GasFeeCap() *big.Int { + return tx.price() +} + +func (tx *LegacyTx) BlobGas() uint64 { return 0 } + +func (tx *LegacyTx) BlobGasFeeCap() *big.Int { return nil } + +func (tx *LegacyTx) BlobHashes() []common.Hash { return nil } + +func (tx *LegacyTx) BlobTxSidecar() *types.BlobTxSidecar { return nil } + +func (tx *LegacyTx) SanityCheck() error { + // Reject execution of negative gas price + if tx.gasPrice != nil && tx.gasPrice.Sign() < 0 { + return ErrNegativeValue + } + return nil +} + +func (tx *LegacyTx) toProto() *iotextypes.ActionCore { + actCore := iotextypes.ActionCore{ + Version: LegacyTxType, + Nonce: tx.nonce, + GasLimit: tx.gasLimit, + ChainID: tx.chainID, + } + if tx.gasPrice != nil { + actCore.GasPrice = tx.gasPrice.String() + } + return &actCore +} + +func fromProtoLegacyTx(pb *iotextypes.ActionCore) (*LegacyTx, error) { + var tx LegacyTx + tx.nonce = pb.GetNonce() + tx.gasLimit = pb.GetGasLimit() + tx.chainID = pb.GetChainID() + tx.gasPrice = &big.Int{} + if price := pb.GetGasPrice(); len(price) > 0 { + var ok bool + if tx.gasPrice, ok = tx.gasPrice.SetString(price, 10); !ok { + return nil, errors.Errorf("invalid gasPrice %s", price) + } + } + return &tx, nil +} + +func (tx *LegacyTx) setNonce(n uint64) { + tx.nonce = n +} + +func (tx *LegacyTx) setGas(gas uint64) { + tx.gasLimit = gas +} + +func (tx *LegacyTx) setChainID(n uint32) { + tx.chainID = n +} diff --git a/action/tx_legacy_test.go b/action/tx_legacy_test.go new file mode 100644 index 0000000000..02e70f81d4 --- /dev/null +++ b/action/tx_legacy_test.go @@ -0,0 +1,99 @@ +// Copyright (c) 2024 IoTeX Foundation +// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability +// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. +// This source code is governed by Apache License 2.0 that can be found in the LICENSE file. + +package action + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + . "github.com/iotexproject/iotex-core/pkg/util/assertions" +) + +func TestLegacyTx(t *testing.T) { + r := require.New(t) + t.Run("proto", func(t *testing.T) { + tx := &LegacyTx{ + chainID: 3, + nonce: 8, + gasLimit: 1001, + gasPrice: big.NewInt(13), + } + r.EqualValues(LegacyTxType, tx.Version()) + r.EqualValues(8, tx.Nonce()) + r.EqualValues(1001, tx.Gas()) + r.Equal(big.NewInt(13), tx.GasPrice()) + r.Equal(big.NewInt(13), tx.GasTipCap()) + r.Equal(big.NewInt(13), tx.GasFeeCap()) + r.Nil(tx.AccessList()) + r.Zero(tx.BlobGas()) + r.Nil(tx.BlobGasFeeCap()) + r.Nil(tx.BlobHashes()) + r.Nil(tx.BlobTxSidecar()) + b := MustNoErrorV(proto.Marshal(tx.toProto())) + r.Equal("0801100818e907220231332803", hex.EncodeToString(b)) + pb := iotextypes.ActionCore{} + r.NoError(proto.Unmarshal(b, &pb)) + r.Equal(tx, MustNoErrorV(fromProtoLegacyTx(&pb))) + }) + ab := AbstractAction{ + version: LegacyTxType, + chainID: 3, + nonce: 8, + gasLimit: 1001, + gasTipCap: big.NewInt(10), + gasFeeCap: big.NewInt(30), + accessList: types.AccessList{ + {Address: common.Address{}, StorageKeys: nil}, + {Address: _c1, StorageKeys: []common.Hash{_k1, {}, _k3}}, + {Address: _c2, StorageKeys: []common.Hash{_k2, _k3, _k4, _k1}}, + }, + } + expect := &LegacyTx{ + chainID: 3, + nonce: 8, + gasLimit: 1001, + } + t.Run("convert", func(t *testing.T) { + for _, price := range []*big.Int{ + nil, big.NewInt(13), + } { + ab.gasPrice = price + tx := ab.convertToTx() + legacy, ok := tx.(*LegacyTx) + r.True(ok) + if price == nil { + expect.gasPrice = new(big.Int) + } else { + expect.gasPrice = new(big.Int).Set(price) + } + r.EqualValues(LegacyTxType, legacy.Version()) + r.Equal(expect, legacy) + } + }) + t.Run("loadProtoTxCommon", func(t *testing.T) { + for _, price := range []*big.Int{ + nil, big.NewInt(13), + } { + expect.gasPrice = price + elp := envelope{} + r.NoError(elp.loadProtoTxCommon(expect.toProto())) + legacy, ok := elp.common.(*LegacyTx) + r.True(ok) + r.EqualValues(LegacyTxType, legacy.Version()) + if price == nil { + expect.gasPrice = new(big.Int) + } + r.Equal(expect, legacy) + } + }) +}