Repository: iotexproject/iotex-core
Pull request #4227 (2024-04-11 00:53:28)

[action] add EvmTransaction to represent actions that run in EVM
# Description
Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.

Fixes #(issue)

## Type of change
Please delete options that are not relevant.
- [] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [] Code refactor or improvement
- [] Breaking change (fix or feature that would cause a new or changed behavior of existing functionality)
- [] This change requires a documentation update

# How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
- [x] make test
- [] fullsync
- [] Other test (please specify)

**Test Configuration**:
- Firmware version:
- Hardware:
- Toolchain:
- SDK:

# Checklist:
- [] My code follows the style guidelines of this project
- [] I have performed a self-review of my code
- [] I have commented my code, particularly in hard-to-understand areas
- [] I have made corresponding changes to the documentation
- [] My changes generate no new warnings
- [] I have added tests that prove my fix is effective or that my feature works
- [] New and existing unit tests pass locally with my changes
- [] Any dependent changes have been merged and published in downstream modules


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1.2% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1.7% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1.7% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1.6% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=iotexproject_iotex-core&pullRequest=4227&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1.6% Duplication on New Code](https://sonarcloud.io/component_measures?id=iotexproject_iotex-core&pullRequest=4227&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=iotexproject_iotex-core&pullRequest=4227)



Patch:
diff --git a/action/evm_transaction.go b/action/evm_transaction.go
new file mode 100644
index 000000000..8264448e6
--- /dev/null
+++ b/action/evm_transaction.go
@@ -0,0 +1,73 @@
+// 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"
+)
+
+type (
+	// EvmTransaction represents an action to be executed by EVM protocol
+	// as of now 3 types of transactions are supported:
+	// 1. Legacy transaction
+	// 2. EIP-2930 access list transaction
+	// 3. EIP-4844 shard blob transaction
+	EvmTransaction struct {
+		inner TxData
+	}
+
+	TxData interface {
+		Nonce() uint64
+		GasLimit() uint64
+		GasPrice() *big.Int
+		Amount() *big.Int
+		To() *common.Address
+		Data() []byte
+		AccessList() types.AccessList
+	}
+)
+
+func NewEvmTx(a Action) *EvmTransaction {
+	tx := new(EvmTransaction)
+	switch act := a.(type) {
+	case *Execution:
+		tx.inner = act
+	default:
+		panic("unsupported action type")
+	}
+	return tx
+}
+
+func (tx *EvmTransaction) Nonce() uint64 {
+	return tx.inner.Nonce()
+}
+
+func (tx *EvmTransaction) Gas() uint64 {
+	return tx.inner.GasLimit()
+}
+
+func (tx *EvmTransaction) GasPrice() *big.Int {
+	return tx.inner.GasPrice()
+}
+
+func (tx *EvmTransaction) Value() *big.Int {
+	return tx.inner.Amount()
+}
+
+func (tx *EvmTransaction) To() *common.Address {
+	return tx.inner.To()
+}
+
+func (tx *EvmTransaction) Data() []byte {
+	return tx.inner.Data()
+}
+
+func (tx *EvmTransaction) AccessList() types.AccessList {
+	return tx.inner.AccessList()
+}
diff --git a/action/execution.go b/action/execution.go
index 197cc6561..f7b993180 100644
--- a/action/execution.go
+++ b/action/execution.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 IoTeX Foundation
+// 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.
@@ -33,6 +33,7 @@ const (
 var (
 	_ hasDestination      = (*Execution)(nil)
 	_ EthCompatibleAction = (*Execution)(nil)
+	_ TxData              = (*Execution)(nil)
 )
 
 // Execution defines the struct of account-based contract execution
@@ -91,6 +92,20 @@ func NewExecutionWithAccessList(
 	}, nil
 }
 
+// To returns the contract address pointer
+// nil indicates a contract-creation transaction
+func (ex *Execution) To() *common.Address {
+	if ex.contract == EmptyAddress {
+		return nil
+	}
+	addr, err := address.FromString(ex.contract)
+	if err != nil {
+		panic(err)
+	}
+	evmAddr := common.BytesToAddress(addr.Bytes())
+	return &evmAddr
+}
+
 // Contract returns a contract address
 func (ex *Execution) Contract() string { return ex.contract }
 
diff --git a/action/protocol/execution/evm/evm.go b/action/protocol/execution/evm/evm.go
index 929ac0909..4fd7105fa 100644
--- a/action/protocol/execution/evm/evm.go
+++ b/action/protocol/execution/evm/evm.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 IoTeX Foundation
+// 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.
@@ -108,7 +108,7 @@ type (
 // newParams creates a new context for use in the EVM.
 func newParams(
 	ctx context.Context,
-	execution *action.Execution,
+	execution *action.EvmTransaction,
 	stateDB *StateDBAdapter,
 ) (*Params, error) {
 	var (
@@ -121,21 +121,11 @@ func newParams(
 		executorAddr = common.BytesToAddress(actionCtx.Caller.Bytes())
 		getBlockHash = helperCtx.GetBlockHash
 
-		vmConfig            vm.Config
-		contractAddrPointer *common.Address
-		getHashFn           vm.GetHashFunc
+		vmConfig  vm.Config
+		getHashFn vm.GetHashFunc
 	)
 
-	if dest := execution.Contract(); dest != action.EmptyAddress {
-		contract, err := address.FromString(execution.Contract())
-		if err != nil {
-			return nil, errors.Wrapf(err, "failed to decode contract address %s", dest)
-		}
-		contractAddr := common.BytesToAddress(contract.Bytes())
-		contractAddrPointer = &contractAddr
-	}
-
-	gasLimit := execution.GasLimit()
+	gasLimit := execution.Gas()
 	// Reset gas limit to the system wide action gas limit cap if it's greater than it
 	if blkCtx.BlockHeight > 0 && featureCtx.SystemWideActionGasLimit && gasLimit > _preAleutianActionGasLimit {
 		gasLimit = _preAleutianActionGasLimit
@@ -200,8 +190,8 @@ func newParams(
 			GasPrice: execution.GasPrice(),
 		},
 		execution.Nonce(),
-		execution.Amount(),
-		contractAddrPointer,
+		execution.Value(),
+		execution.To(),
 		gasLimit,
 		execution.Data(),
 		execution.AccessList(),
@@ -237,7 +227,7 @@ func securityDeposit(ps *Params, stateDB vm.StateDB, gasLimit uint64) error {
 func ExecuteContract(
 	ctx context.Context,
 	sm protocol.StateManager,
-	execution *action.Execution,
+	execution *action.EvmTransaction,
 ) ([]byte, *action.Receipt, error) {
 	ctx, span := tracer.NewSpan(ctx, "evm.ExecuteContract")
 	defer span.End()
@@ -332,16 +322,17 @@ func ExecuteContract(
 	return retval, receipt, nil
 }
 
-func processSGD(ctx context.Context, sm protocol.StateManager, execution *action.Execution, consumedGas uint64, sgd SGDRegistry,
+func processSGD(ctx context.Context, sm protocol.StateManager, execution *action.EvmTransaction, consumedGas uint64, sgd SGDRegistry,
 ) (address.Address, uint64, error) {
-	if execution.Contract() == action.EmptyAddress {
+	if execution.To() == nil {
 		return nil, 0, nil
 	}
 	height, err := sm.Height()
 	if err != nil {
 		return nil, 0, err
 	}
-	receiver, percentage, ok, err := sgd.CheckContract(ctx, execution.Contract(), height-1)
+	contract, _ := address.FromBytes((*execution.To())[:])
+	receiver, percentage, ok, err := sgd.CheckContract(ctx, contract.String(), height-1)
 	if err != nil || !ok {
 		return nil, 0, err
 	}
@@ -680,6 +671,6 @@ func SimulateExecution(
 	return ExecuteContract(
 		ctx,
 		sm,
-		ex,
+		action.NewEvmTx(ex),
 	)
 }
diff --git a/action/protocol/execution/protocol.go b/action/protocol/execution/protocol.go
index ba5264ea2..f6d77295e 100644
--- a/action/protocol/execution/protocol.go
+++ b/action/protocol/execution/protocol.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 IoTeX Foundation
+// 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.
@@ -74,7 +74,7 @@ func (p *Protocol) Handle(ctx context.Context, act action.Action, sm protocol.St
 		DepositGasFunc: p.depositGas,
 		Sgd:            p.sgdRegistry,
 	})
-	_, receipt, err := evm.ExecuteContract(ctx, sm, exec)
+	_, receipt, err := evm.ExecuteContract(ctx, sm, action.NewEvmTx(exec))
 
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to execute contract")
diff --git a/action/protocol/poll/consortium.go b/action/protocol/poll/consortium.go
index 7fe54d61b..7f85cb73a 100644
--- a/action/protocol/poll/consortium.go
+++ b/action/protocol/poll/consortium.go
@@ -145,7 +145,7 @@ func (cc *consortiumCommittee) CreateGenesisStates(ctx context.Context, sm proto
 	_, receipt, err := evm.ExecuteContract(
 		ctx,
 		sm,
-		execution,
+		action.NewEvmTx(execution),
 	)
 	if err != nil {
 		return err
diff --git a/action/protocol/poll/staking_committee.go b/action/protocol/poll/staking_committee.go
index fb97d01c8..c93e2ed3f 100644
--- a/action/protocol/poll/staking_committee.go
+++ b/action/protocol/poll/staking_committee.go
@@ -152,7 +152,7 @@ func (sc *stakingCommittee) CreateGenesisStates(ctx context.Context, sm protocol
 	_, receipt, err := evm.ExecuteContract(
 		ctx,
 		sm,
-		execution,
+		action.NewEvmTx(execution),
 	)
 	if err != nil {
 		return err


Test patch:
diff --git a/action/execution_test.go b/action/execution_test.go
index c381bb297..4620e0c14 100644
--- a/action/execution_test.go
+++ b/action/execution_test.go
@@ -15,6 +15,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/require"
 
+	"github.com/iotexproject/iotex-address/address"
 	"github.com/iotexproject/iotex-core/test/identityset"
 )
 
@@ -75,6 +76,22 @@ func TestExecutionSanityCheck(t *testing.T) {
 		require.Contains(ex.SanityCheck().Error(), "error when validating contract's address")
 	})
 
+	t.Run("Empty contract address", func(t *testing.T) {
+		ex, err := NewExecution(
+			EmptyAddress,
+			uint64(1),
+			big.NewInt(0),
+			uint64(0),
+			big.NewInt(0),
+			[]byte{},
+		)
+		require.NoError(err)
+		require.Nil(ex.To())
+		addr, _ := address.FromBytes(_c1.Bytes())
+		ex.contract = addr.String()
+		require.Equal(_c1, *ex.To())
+	})
+
 	t.Run("Negative gas price", func(t *testing.T) {
 		ex, err := NewExecution(identityset.Address(29).String(), uint64(1), big.NewInt(100), uint64(0), big.NewInt(-1), []byte{})
 		require.NoError(err)
diff --git a/action/protocol/execution/evm/evm_test.go b/action/protocol/execution/evm/evm_test.go
index f995bfbab..8865bd244 100644
--- a/action/protocol/execution/evm/evm_test.go
+++ b/action/protocol/execution/evm/evm_test.go
@@ -72,7 +72,7 @@ func TestExecuteContractFailure(t *testing.T) {
 		},
 		Sgd: nil,
 	})
-	retval, receipt, err := ExecuteContract(ctx, sm, e)
+	retval, receipt, err := ExecuteContract(ctx, sm, action.NewEvmTx(e))
 	require.Nil(t, retval)
 	require.Nil(t, receipt)
 	require.Error(t, err)
@@ -302,7 +302,7 @@ func TestConstantinople(t *testing.T) {
 		})
 		stateDB, err := prepareStateDB(fCtx, sm)
 		require.NoError(err)
-		ps, err := newParams(fCtx, ex, stateDB)
+		ps, err := newParams(fCtx, action.NewEvmTx(ex), stateDB)
 		require.NoError(err)
 
 		evm := vm.NewEVM(ps.context, ps.txCtx, stateDB, ps.chainConfig, ps.evmConfig)