diff --git a/core/genesis.go b/core/genesis.go index 2803902814..4ff4b1f8a0 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -310,6 +310,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen mantleUpgradeChainConfig := GetUpgradeConfigForMantle(config.ChainID) if mantleUpgradeChainConfig != nil { config.BaseFeeTime = mantleUpgradeChainConfig.BaseFeeTime + config.BVMETHMintUpgradeTime = mantleUpgradeChainConfig.BVMETHMintUpgradeTime } if overrides != nil && overrides.OverrideShanghai != nil { diff --git a/core/mantle_upgrade.go b/core/mantle_upgrade.go index f23fcd816d..c2a49fc783 100644 --- a/core/mantle_upgrade.go +++ b/core/mantle_upgrade.go @@ -8,26 +8,32 @@ import ( var ( MantleMainnetUpgradeConfig = MantleUpgradeChainConfig{ - ChainID: params.MantleMainnetChainId, - BaseFeeTime: u64Ptr(0), + ChainID: params.MantleMainnetChainId, + BaseFeeTime: u64Ptr(0), + BVMETHMintUpgradeTime: u64Ptr(0), } MantleSepoliaUpgradeConfig = MantleUpgradeChainConfig{ - ChainID: params.MantleSepoliaChainId, - BaseFeeTime: u64Ptr(1_704_891_600), + ChainID: params.MantleSepoliaChainId, + BaseFeeTime: u64Ptr(1_704_891_600), + BVMETHMintUpgradeTime: nil, //TODO set upgrade timestamp } MantleLocalUpgradeConfig = MantleUpgradeChainConfig{ - ChainID: params.MantleLocalChainId, - BaseFeeTime: u64Ptr(0), + ChainID: params.MantleLocalChainId, + BaseFeeTime: u64Ptr(0), + BVMETHMintUpgradeTime: u64Ptr(0), } MantleDefaultUpgradeConfig = MantleUpgradeChainConfig{ - BaseFeeTime: u64Ptr(0), + BaseFeeTime: u64Ptr(0), + BVMETHMintUpgradeTime: u64Ptr(0), } ) type MantleUpgradeChainConfig struct { - ChainID *big.Int `json:"chainId"` // chainId identifies the current chain and is used for replay protection - BaseFeeTime *uint64 `json:"BaseFeeTime"` // Mantle BaseFee switch time (nil = no fork, 0 = already on mantle baseFee) + ChainID *big.Int `json:"chainId"` // chainId identifies the current chain and is used for replay protection + + BaseFeeTime *uint64 `json:"BaseFeeTime"` // Mantle BaseFee switch time (nil = no fork, 0 = already on mantle baseFee) + BVMETHMintUpgradeTime *uint64 `json:"BVMETHMintUpgradeTime"` // BVM_ETH mint upgrade switch time (nil = no fork, 0 = already on) } func GetUpgradeConfigForMantle(chainID *big.Int) *MantleUpgradeChainConfig { diff --git a/core/state_transition.go b/core/state_transition.go index 0566e9a4ab..352e79e386 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -161,6 +161,7 @@ type Message struct { IsDepositTx bool // IsDepositTx indicates the message is force-included and can persist a mint. Mint *big.Int // Mint is the amount to mint before EVM processing, or nil if there is no minting. ETHValue *big.Int // ETHValue is the amount to mint BVM_ETH before EVM processing, or nil if there is no minting. + ETHTxValue *big.Int // ETHTxValue is the amount to be transferred to msg.To before EVM processing, and the transfer will be reverted if EVM failed MetaTxParams *types.MetaTxParams // MetaTxParams contains necessary parameter to sponsor gas fee for msg.From. RollupDataGas types.RollupGasData // RollupDataGas indicates the rollup cost of the message, 0 if not a rollup or no cost. @@ -189,6 +190,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In Mint: tx.Mint(), RollupDataGas: tx.RollupDataGas(), ETHValue: tx.ETHValue(), + ETHTxValue: tx.ETHTxValue(), MetaTxParams: metaTxParams, SkipAccountChecks: false, RunMode: CommitMode, @@ -438,19 +440,18 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if mint := st.msg.Mint; mint != nil { st.state.AddBalance(st.msg.From, mint) } + + //Mint BVM_ETH + rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time) //add eth value if ethValue := st.msg.ETHValue; ethValue != nil && ethValue.Cmp(big.NewInt(0)) != 0 { - var ethRecipient common.Address - if st.msg.To != nil { - ethRecipient = *st.msg.To - } else { - ethRecipient = crypto.CreateAddress(st.msg.From, st.evm.StateDB.GetNonce(st.msg.From)) - } - st.addBVMETHBalance(ethRecipient, ethValue) - st.addBVMETHTotalSupply(ethValue) - st.generateBVMETHMintEvent(ethRecipient, ethValue) + st.mintBVMETH(ethValue, rules) } snap := st.state.Snapshot() + // Will be reverted if failed + if ethTxValue := st.msg.ETHTxValue; ethTxValue != nil && ethTxValue.Cmp(big.NewInt(0)) != 0 { + st.transferBVMETH(ethTxValue, rules) + } result, err := st.innerTransitionDb() // Failed deposits must still be included. Unless we cannot produce the block at all due to the gas limit. @@ -671,12 +672,33 @@ func (st *StateTransition) gasUsed() uint64 { return st.initialGas - st.gasRemaining } -func (st *StateTransition) addBVMETHBalance(ethRecipient common.Address, ethValue *big.Int) { - key := getBVMETHBalanceKey(ethRecipient) +func (st *StateTransition) mintBVMETH(ethValue *big.Int, rules params.Rules) { + if !rules.IsMantleBVMETHMintUpgrade { + var key common.Hash + var ethRecipient common.Address + if st.msg.To != nil { + ethRecipient = *st.msg.To + } else { + ethRecipient = crypto.CreateAddress(st.msg.From, st.evm.StateDB.GetNonce(st.msg.From)) + } + key = getBVMETHBalanceKey(ethRecipient) + value := st.state.GetState(BVM_ETH_ADDR, key) + bal := value.Big() + bal = bal.Add(bal, ethValue) + st.state.SetState(BVM_ETH_ADDR, key, common.BigToHash(bal)) + + st.addBVMETHTotalSupply(ethValue) + st.generateBVMETHMintEvent(ethRecipient, ethValue) + return + } + key := getBVMETHBalanceKey(st.msg.From) value := st.state.GetState(BVM_ETH_ADDR, key) bal := value.Big() bal = bal.Add(bal, ethValue) st.state.SetState(BVM_ETH_ADDR, key, common.BigToHash(bal)) + + st.addBVMETHTotalSupply(ethValue) + st.generateBVMETHMintEvent(st.msg.From, ethValue) } func (st *StateTransition) addBVMETHTotalSupply(ethValue *big.Int) { @@ -687,6 +709,38 @@ func (st *StateTransition) addBVMETHTotalSupply(ethValue *big.Int) { st.state.SetState(BVM_ETH_ADDR, key, common.BigToHash(bal)) } +func (st *StateTransition) transferBVMETH(ethValue *big.Int, rules params.Rules) { + if !rules.IsMantleBVMETHMintUpgrade { + return + } + var ethRecipient common.Address + if st.msg.To != nil { + ethRecipient = *st.msg.To + } else { + ethRecipient = crypto.CreateAddress(st.msg.From, st.evm.StateDB.GetNonce(st.msg.From)) + } + if ethRecipient == st.msg.From { + return + } + + fromKey := getBVMETHBalanceKey(st.msg.From) + toKey := getBVMETHBalanceKey(ethRecipient) + + fromBalanceValue := st.state.GetState(BVM_ETH_ADDR, fromKey) + toBalanceValue := st.state.GetState(BVM_ETH_ADDR, toKey) + + fromBalance := fromBalanceValue.Big() + toBalance := toBalanceValue.Big() + + fromBalance = new(big.Int).Sub(fromBalance, ethValue) + toBalance = new(big.Int).Add(toBalance, ethValue) + + st.state.SetState(BVM_ETH_ADDR, fromKey, common.BigToHash(fromBalance)) + st.state.SetState(BVM_ETH_ADDR, toKey, common.BigToHash(toBalance)) + + st.generateBVMETHTransferEvent(st.msg.From, ethRecipient, ethValue) +} + func getBVMETHBalanceKey(addr common.Address) common.Hash { position := common.Big0 hasher := sha3.NewLegacyKeccak256() @@ -717,3 +771,22 @@ func (st *StateTransition) generateBVMETHMintEvent(mintAddress common.Address, m BlockNumber: st.evm.Context.BlockNumber.Uint64(), }) } + +func (st *StateTransition) generateBVMETHTransferEvent(from, to common.Address, amount *big.Int) { + // keccak("Transfer(address,address,uint256)") = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + methodHash := common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") + topics := make([]common.Hash, 3) + topics[0] = methodHash + topics[1] = from.Hash() + topics[2] = to.Hash() + //data means the transfer amount in Transfer EVENT. + data := common.HexToHash(common.Bytes2Hex(amount.Bytes())).Bytes() + st.evm.StateDB.AddLog(&types.Log{ + Address: BVM_ETH_ADDR, + Topics: topics, + Data: data, + // This is a non-consensus field, but assigned here because + // core/state doesn't know the current block number. + BlockNumber: st.evm.Context.BlockNumber.Uint64(), + }) +} diff --git a/core/types/deposit_tx.go b/core/types/deposit_tx.go index f4917a11f3..7dece4a6a1 100644 --- a/core/types/deposit_tx.go +++ b/core/types/deposit_tx.go @@ -43,6 +43,8 @@ type DepositTx struct { EthValue *big.Int `rlp:"nil"` // Normal Tx data Data []byte + // EthTxValue means L2 BVM_ETH tx tag, nil means that there is no need to transfer BVM_ETH to msg.To. + EthTxValue *big.Int `rlp:"optional"` } // copy creates a deep copy of the transaction data and initializes all fields. @@ -57,6 +59,7 @@ func (tx *DepositTx) copy() TxData { IsSystemTransaction: tx.IsSystemTransaction, Data: common.CopyBytes(tx.Data), EthValue: nil, + EthTxValue: nil, } if tx.Mint != nil { cpy.Mint = new(big.Int).Set(tx.Mint) @@ -67,6 +70,9 @@ func (tx *DepositTx) copy() TxData { if tx.EthValue != nil { cpy.EthValue = new(big.Int).Set(tx.EthValue) } + if tx.EthTxValue != nil { + cpy.EthTxValue = new(big.Int).Set(tx.EthTxValue) + } return cpy } diff --git a/core/types/transaction.go b/core/types/transaction.go index 02a7e1dca8..d4a6622056 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -354,6 +354,15 @@ func (tx *Transaction) ETHValue() *big.Int { return nil } +// ETHTxValue returns the BVM_ETH to be transferred in the deposit tx. +// This returns nil if there is nothing to transfer, or if this is not a deposit tx. +func (tx *Transaction) ETHTxValue() *big.Int { + if dep, ok := tx.inner.(*DepositTx); ok { + return dep.EthTxValue + } + return nil +} + // IsDepositTx returns true if the transaction is a deposit tx type. func (tx *Transaction) IsDepositTx() bool { return tx.Type() == DepositTxType diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index dcf0e2c921..4253380ec4 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -54,6 +54,7 @@ type txJSON struct { From *common.Address `json:"from,omitempty"` Mint *hexutil.Big `json:"mint,omitempty"` EthValue *hexutil.Big `json:"ethValue,omitempty"` + EthTxValue *hexutil.Big `json:"ethTxValue,omitempty"` IsSystemTx *bool `json:"isSystemTx,omitempty"` // Access list transaction fields: @@ -141,6 +142,9 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { if itx.EthValue != nil { enc.EthValue = (*hexutil.Big)(itx.EthValue) } + if itx.EthTxValue != nil { + enc.EthTxValue = (*hexutil.Big)(itx.EthTxValue) + } enc.IsSystemTx = &itx.IsSystemTransaction // other fields will show up as null. } @@ -419,6 +423,8 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { itx.Mint = (*big.Int)(dec.Mint) // ethValue may be omitted or nil if there is nothing to mint. itx.EthValue = (*big.Int)(dec.EthValue) + // ethValue may be omitted or nil if there is nothing to transfer to msg.To. + itx.EthTxValue = (*big.Int)(dec.EthTxValue) if dec.Data == nil { return errors.New("missing required field 'input' in transaction") } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9e2039c8d7..213dd990da 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1503,6 +1503,7 @@ type RPCTransaction struct { SourceHash *common.Hash `json:"sourceHash,omitempty"` Mint *hexutil.Big `json:"mint,omitempty"` EthValue *hexutil.Big `json:"ethValue,omitempty"` + EthTxValue *hexutil.Big `json:"ethTxValue,omitempty"` IsSystemTx *bool `json:"isSystemTx,omitempty"` } @@ -1543,6 +1544,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber } result.Mint = (*hexutil.Big)(tx.Mint()) result.EthValue = (*hexutil.Big)(tx.ETHValue()) + result.EthTxValue = (*hexutil.Big)(tx.ETHTxValue()) if receipt != nil && receipt.DepositNonce != nil { result.Nonce = hexutil.Uint64(*receipt.DepositNonce) } diff --git a/params/config.go b/params/config.go index a7aad50fff..d753381307 100644 --- a/params/config.go +++ b/params/config.go @@ -458,8 +458,8 @@ type ChainConfig struct { MergeNetsplitBlock *big.Int `json:"mergeNetsplitBlock,omitempty"` // Virtual fork after The Merge to use as a network splitter // Mantle upgrade configs - BaseFeeTime *uint64 `json:"baseFeeTime,omitempty"` // Mantle BaseFee switch time (nil = no fork, 0 = already on mantle baseFee) - + BaseFeeTime *uint64 `json:"baseFeeTime,omitempty"` // Mantle BaseFee switch time (nil = no fork, 0 = already on mantle baseFee) + BVMETHMintUpgradeTime *uint64 `json:"bvmETHMintUpgradeTime,omitempty"` // BVM_ETH mint upgrade switch time (nil = no fork, 0 = already on) // Fork scheduling was switched from blocks to timestamps here ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) @@ -677,6 +677,11 @@ func (c *ChainConfig) IsMantleBaseFee(time uint64) bool { return isTimestampForked(c.BaseFeeTime, time) } +// IsMantleBVMETHMintUpgrade returns whether time is either equal to the BVM_ETH mint upgrade fork time or greater. +func (c *ChainConfig) IsMantleBVMETHMintUpgrade(time uint64) bool { + return isTimestampForked(c.BVMETHMintUpgradeTime, time) +} + // IsArrowGlacier returns whether num is either equal to the Arrow Glacier (EIP-4345) fork block or greater. func (c *ChainConfig) IsArrowGlacier(num *big.Int) bool { return isBlockForked(c.ArrowGlacierBlock, num) @@ -1046,7 +1051,7 @@ type Rules struct { IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsBerlin, IsLondon bool IsMerge, IsShanghai, isCancun, isPrague bool - IsMantleBaseFee bool + IsMantleBaseFee, IsMantleBVMETHMintUpgrade bool IsOptimismBedrock, IsOptimismRegolith bool } @@ -1057,22 +1062,23 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules chainID = new(big.Int) } return Rules{ - ChainID: new(big.Int).Set(chainID), - IsHomestead: c.IsHomestead(num), - IsEIP150: c.IsEIP150(num), - IsEIP155: c.IsEIP155(num), - IsEIP158: c.IsEIP158(num), - IsByzantium: c.IsByzantium(num), - IsConstantinople: c.IsConstantinople(num), - IsPetersburg: c.IsPetersburg(num), - IsIstanbul: c.IsIstanbul(num), - IsBerlin: c.IsBerlin(num), - IsLondon: c.IsLondon(num), - IsMerge: isMerge, - IsMantleBaseFee: c.IsMantleBaseFee(timestamp), - IsShanghai: c.IsShanghai(timestamp), - isCancun: c.IsCancun(timestamp), - isPrague: c.IsPrague(timestamp), + ChainID: new(big.Int).Set(chainID), + IsHomestead: c.IsHomestead(num), + IsEIP150: c.IsEIP150(num), + IsEIP155: c.IsEIP155(num), + IsEIP158: c.IsEIP158(num), + IsByzantium: c.IsByzantium(num), + IsConstantinople: c.IsConstantinople(num), + IsPetersburg: c.IsPetersburg(num), + IsIstanbul: c.IsIstanbul(num), + IsBerlin: c.IsBerlin(num), + IsLondon: c.IsLondon(num), + IsMerge: isMerge, + IsMantleBaseFee: c.IsMantleBaseFee(timestamp), + IsMantleBVMETHMintUpgrade: c.IsMantleBVMETHMintUpgrade(timestamp), + IsShanghai: c.IsShanghai(timestamp), + isCancun: c.IsCancun(timestamp), + isPrague: c.IsPrague(timestamp), // Optimism IsOptimismBedrock: c.IsOptimismBedrock(num), IsOptimismRegolith: c.IsOptimismRegolith(timestamp),