diff --git a/action/protocol/context.go b/action/protocol/context.go index 26c1bb0eba..55d88f6d0d 100644 --- a/action/protocol/context.go +++ b/action/protocol/context.go @@ -120,6 +120,7 @@ type ( DisableDelegateEndorsement bool RefactorFreshAccountConversion bool SuicideTxLogMismatchPanic bool + EnableCancunEVM bool } // FeatureWithHeightCtx provides feature check functions. @@ -265,6 +266,7 @@ func WithFeatureCtx(ctx context.Context) context.Context { DisableDelegateEndorsement: !g.IsTsunami(height), RefactorFreshAccountConversion: g.IsTsunami(height), SuicideTxLogMismatchPanic: g.IsToBeEnabled(height), + EnableCancunEVM: g.IsToBeEnabled(height), }, ) } diff --git a/action/protocol/execution/evm/evm.go b/action/protocol/execution/evm/evm.go index fca42d0254..4a088b575f 100644 --- a/action/protocol/execution/evm/evm.go +++ b/action/protocol/execution/evm/evm.go @@ -411,6 +411,9 @@ func prepareStateDB(ctx context.Context, sm protocol.StateManager) (*StateDBAdap if featureCtx.SuicideTxLogMismatchPanic { opts = append(opts, SuicideTxLogMismatchPanicOption()) } + if featureCtx.EnableCancunEVM { + opts = append(opts, EnableCancunEVMOption()) + } return NewStateDBAdapter( sm, diff --git a/action/protocol/execution/evm/evmstatedbadapter.go b/action/protocol/execution/evm/evmstatedbadapter.go index 048d97f202..6c83b831f6 100644 --- a/action/protocol/execution/evm/evmstatedbadapter.go +++ b/action/protocol/execution/evm/evmstatedbadapter.go @@ -35,6 +35,9 @@ type ( // deleteAccount records the account/contract to be deleted deleteAccount map[hash.Hash160]struct{} + // createdAccount contains new accounts created in this tx + createdAccount map[common.Address]bool + // contractMap records the contracts being changed contractMap map[hash.Hash160]Contract @@ -61,6 +64,8 @@ type ( preimageSnapshot map[int]preimageMap accessList *accessList // per-transaction access list accessListSnapshot map[int]*accessList + createdAccount createdAccount + createdAccountSnapshot map[int]createdAccount logsSnapshot map[int]int // logs is an array, save len(logs) at time of snapshot suffices txLogsSnapshot map[int]int notFixTopicCopyBug bool @@ -73,6 +78,7 @@ type ( manualCorrectGasRefund bool suicideTxLogMismatchPanic bool zeroNonceForFreshAccount bool + enableCancun bool } ) @@ -162,6 +168,14 @@ func ZeroNonceForFreshAccountOption() StateDBAdapterOption { } } +// EnableCancunEVMOption indicates that Cancun EVM is activated +func EnableCancunEVMOption() StateDBAdapterOption { + return func(adapter *StateDBAdapter) error { + adapter.enableCancun = true + return nil + } +} + // NewStateDBAdapter creates a new state db with iotex blockchain func NewStateDBAdapter( sm protocol.StateManager, @@ -197,6 +211,10 @@ func NewStateDBAdapter( if !s.legacyNonceAccount && s.useConfirmedNonce { return nil, errors.New("invalid parameter combination") } + if s.enableCancun { + s.createdAccount = make(createdAccount) + s.createdAccountSnapshot = make(map[int]createdAccount) + } return s, nil } @@ -225,12 +243,15 @@ func (stateDB *StateDBAdapter) CreateAccount(evmAddr common.Address) { log.L().Error("Failed to convert evm address.", zap.Error(err)) return } - _, _, err = accountutil.LoadOrCreateAccount(stateDB.sm, addr, stateDB.accountCreationOpts()...) + _, created, err := accountutil.LoadOrCreateAccount(stateDB.sm, addr, stateDB.accountCreationOpts()...) if err != nil { log.L().Error("Failed to create account.", zap.Error(err)) stateDB.logError(err) return } + if stateDB.enableCancun && created { + stateDB.createdAccount[evmAddr] = true + } log.L().Debug("Called CreateAccount.", log.Hex("addrHash", evmAddr[:])) } @@ -433,6 +454,7 @@ func (stateDB *StateDBAdapter) SelfDestruct(evmAddr common.Address) { } // clears the account balance actBalance := new(big.Int).Set(s.Balance) + log.L().Info("SelfDestruct contract", zap.String("Balance", actBalance.String())) if err := s.SubBalance(s.Balance); err != nil { log.L().Debug("failed to clear balance", zap.Error(err), zap.String("address", evmAddr.Hex())) return @@ -443,28 +465,10 @@ func (stateDB *StateDBAdapter) SelfDestruct(evmAddr common.Address) { stateDB.logError(err) return } - // To ensure data consistency, generate this log after the hard-fork - // a separate patch file will be created later to provide missing logs before the hard-fork - // TODO: remove this gating once the hard-fork has passed - if stateDB.suicideTxLogMismatchPanic { - // before calling SelfDestruct, EVM will transfer the contract's balance to beneficiary - // need to create a transaction log on successful SelfDestruct - if stateDB.lastAddBalanceAmount.Cmp(actBalance) == 0 { - if stateDB.lastAddBalanceAmount.Cmp(big.NewInt(0)) > 0 { - from, _ := address.FromBytes(evmAddr[:]) - stateDB.addTransactionLogs(&action.TransactionLog{ - Type: iotextypes.TransactionLogType_IN_CONTRACT_TRANSFER, - Sender: from.String(), - Recipient: stateDB.lastAddBalanceAddr, - Amount: stateDB.lastAddBalanceAmount, - }) - } - } else { - log.L().Panic("SelfDestruct contract's balance does not match", - zap.String("SelfDestruct", actBalance.String()), - zap.String("beneficiary", stateDB.lastAddBalanceAmount.String())) - } - } + // before calling SelfDestruct, EVM will transfer the contract's balance to beneficiary + // need to create a transaction log on successful SelfDestruct + from, _ := address.FromBytes(evmAddr[:]) + stateDB.generateSelfDestructTransferLog(from.String(), stateDB.lastAddBalanceAmount.Cmp(actBalance) == 0) // mark it as deleted stateDB.selfDestructed[addrHash] = struct{}{} } @@ -476,6 +480,22 @@ func (stateDB *StateDBAdapter) HasSelfDestructed(evmAddr common.Address) bool { return ok } +// Selfdestruct6780 implements EIP-6780 +func (stateDB *StateDBAdapter) Selfdestruct6780(evmAddr common.Address) { + if !stateDB.Exist(evmAddr) { + log.L().Debug("Account does not exist.", zap.String("address", evmAddr.Hex())) + return + } + // opSelfdestruct6780 has already subtracted the contract's balance + // so create a transaction log + from, _ := address.FromBytes(evmAddr[:]) + stateDB.generateSelfDestructTransferLog(from.String(), true) + // per EIP-6780, delete the account only if it is created in the same transaction + if stateDB.createdAccount[evmAddr] { + stateDB.selfDestructed[hash.BytesToHash160(evmAddr.Bytes())] = struct{}{} + } +} + // SetTransientState sets transient storage for a given account func (stateDB *StateDBAdapter) SetTransientState(addr common.Address, key, value common.Hash) { log.S().Panic("SetTransientState not implemented") @@ -487,12 +507,6 @@ func (stateDB *StateDBAdapter) GetTransientState(addr common.Address, key common return common.Hash{} } -// Selfdestruct6780 implements EIP-6780 -func (stateDB *StateDBAdapter) Selfdestruct6780(evmAddr common.Address) { - //Todo: implement EIP-6780 - log.S().Panic("Selfdestruct6780 not implemented") -} - // Exist checks the existence of an address func (stateDB *StateDBAdapter) Exist(evmAddr common.Address) bool { addr, err := address.FromBytes(evmAddr.Bytes()) @@ -623,6 +637,19 @@ func (stateDB *StateDBAdapter) RevertToSnapshot(snapshot int) { } } } + // restore created accounts + if stateDB.enableCancun { + stateDB.createdAccount = stateDB.createdAccountSnapshot[snapshot] + { + for i := snapshot; ; i++ { + if _, ok := stateDB.createdAccountSnapshot[i]; ok { + delete(stateDB.createdAccountSnapshot, i) + } else { + break + } + } + } + } // restore logs and txLogs if stateDB.revertLog { stateDB.logs = stateDB.logs[:stateDB.logsSnapshot[snapshot]] @@ -750,6 +777,14 @@ func (stateDB *StateDBAdapter) Snapshot() int { stateDB.preimageSnapshot[sn] = p // save a copy of access list stateDB.accessListSnapshot[sn] = stateDB.accessList.Copy() + if stateDB.enableCancun { + // save a copy of created account map + ca := make(createdAccount) + for k, v := range stateDB.createdAccount { + ca[k] = v + } + stateDB.createdAccountSnapshot[sn] = ca + } return sn } @@ -854,6 +889,27 @@ func (stateDB *StateDBAdapter) accountState(evmAddr common.Address) (*state.Acco return accountutil.LoadAccountByHash160(stateDB.sm, addrHash, stateDB.accountCreationOpts()...) } +func (stateDB *StateDBAdapter) generateSelfDestructTransferLog(sender string, amountMatch bool) { + // To ensure data consistency, generate this log after the hard-fork + // a separate patch file will be created later to provide missing logs before the hard-fork + // TODO: remove this gating once the hard-fork has passed + if stateDB.suicideTxLogMismatchPanic { + if amountMatch { + if stateDB.lastAddBalanceAmount.Cmp(big.NewInt(0)) > 0 { + stateDB.addTransactionLogs(&action.TransactionLog{ + Type: iotextypes.TransactionLogType_IN_CONTRACT_TRANSFER, + Sender: sender, + Recipient: stateDB.lastAddBalanceAddr, + Amount: stateDB.lastAddBalanceAmount, + }) + } + } else { + log.L().Panic("SelfDestruct contract's balance does not match", + zap.String("beneficiary", stateDB.lastAddBalanceAmount.String())) + } + } +} + func (stateDB *StateDBAdapter) addTransactionLogs(tlog *action.TransactionLog) { stateDB.transactionLogs = append(stateDB.transactionLogs, tlog) } @@ -1093,4 +1149,8 @@ func (stateDB *StateDBAdapter) clear() { stateDB.txLogsSnapshot = make(map[int]int) stateDB.logs = []*action.Log{} stateDB.transactionLogs = []*action.TransactionLog{} + if stateDB.enableCancun { + stateDB.createdAccount = make(createdAccount) + stateDB.createdAccountSnapshot = make(map[int]createdAccount) + } }