diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 5860ae7504..edce0aca8f 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -190,7 +190,6 @@ func Transaction(ctx *cli.Context) error { case new(big.Int).Mul(tx.GasFeeCap(), new(big.Int).SetUint64(tx.Gas())).BitLen() > 256: r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits") } - // TODO marcello double check // Check whether the init code size has been exceeded. if chainConfig.IsShanghai(new(big.Int)) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { r.Error = errors.New("max initcode size exceeded") diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 4ec3c7a081..165c842baa 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -285,7 +285,7 @@ func Transition(ctx *cli.Context) error { return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section")) } } - // TODO marcello double check + if chainConfig.IsShanghai(big.NewInt(int64(prestate.Env.Number))) && prestate.Env.Withdrawals == nil { return NewError(ErrorConfig, errors.New("Shanghai config but missing 'withdrawals' in env section")) } diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go index cbe2926206..e98651ae28 100644 --- a/cmd/evm/t8n_test.go +++ b/cmd/evm/t8n_test.go @@ -238,7 +238,6 @@ func TestT8n(t *testing.T) { output: t8nOutput{result: true}, expOut: "exp.json", }, - // TODO marcello double check { // Test post-merge transition base: "./testdata/24", input: t8nInput{ diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 62017dfe14..8221ae69c2 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -148,7 +148,6 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // makeFullNode loads geth configuration and creates the Ethereum backend. func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { stack, cfg := makeConfigNode(ctx) - // TODO marcello double check if ctx.IsSet(utils.OverrideShanghai.Name) { v := ctx.Int64(utils.OverrideShanghai.Name) cfg.Eth.OverrideShanghai = new(big.Int).SetInt64(v) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 79aacbe3e8..05f5255c21 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -70,7 +70,6 @@ var ( utils.NoUSBFlag, utils.USBFlag, utils.SmartCardDaemonPathFlag, - // TODO marcello double check utils.OverrideShanghai, utils.EnablePersonal, utils.EthashCacheDirFlag, diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 4728527678..434f54b33b 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -283,7 +283,6 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa return err } // Verify existence / non-existence of withdrawalsHash. - // TODO marcello double check shanghai := chain.Config().IsShanghai(header.Number) if shanghai && header.WithdrawalsHash == nil { return errors.New("missing withdrawalsHash") @@ -391,7 +390,7 @@ func (beacon *Beacon) FinalizeAndAssemble(ctx context.Context, chain consensus.C if !beacon.IsPoSHeader(header) { return beacon.ethone.FinalizeAndAssemble(ctx, chain, header, state, txs, uncles, receipts, nil) } - // TODO marcello double check + shanghai := chain.Config().IsShanghai(header.Number) if shanghai { // All blocks after Shanghai must include a withdrawals root. diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 440d1073ab..6998a6d070 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -308,7 +308,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if header.GasLimit > params.MaxGasLimit { return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) } - // TODO marcello double check + if chain.Config().IsShanghai(header.Number) { return fmt.Errorf("clique does not support shanghai fork") } diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index b61e35b59d..2011a8da33 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -333,7 +333,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 { return consensus.ErrInvalidNumber } - // TODO marcello double check + if chain.Config().IsShanghai(header.Number) { return fmt.Errorf("ethash does not support shanghai fork") } diff --git a/core/genesis.go b/core/genesis.go index 4dec3e741d..b41d693ef7 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -326,7 +326,6 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen applyOverrides := func(config *params.ChainConfig) { if config != nil { - // TODO marcello double check if overrides != nil && overrides.OverrideShanghai != nil { config.ShanghaiBlock = overrides.OverrideShanghai } diff --git a/core/state/statedb.go b/core/state/statedb.go index 9e680fae61..c68b7a133c 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1595,7 +1595,6 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d al.AddSlot(el.Address, key) } } - // TODO marcello double check if rules.IsShanghai { // EIP-3651: warm coinbase al.AddAddress(coinbase) } diff --git a/core/state_processor.go b/core/state_processor.go index a844870437..fbd164f40f 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -101,7 +101,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Fail if Shanghai not enabled and len(withdrawals) is non-zero. withdrawals := block.Withdrawals() - // TODO marcello double check if len(withdrawals) > 0 && !p.config.IsShanghai(block.Number()) { return nil, nil, 0, fmt.Errorf("withdrawals before shanghai") } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 443af82525..7fd22c3b00 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -364,9 +364,8 @@ func TestStateProcessorErrors(t *testing.T) { MergeNetsplitBlock: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficultyPassed: true, - // TODO marcello double check - ShanghaiBlock: big.NewInt(0), - Bor: ¶ms.BorConfig{BurntContract: map[string]string{"0": "0x000000000000000000000000000000000000dead"}}, + ShanghaiBlock: big.NewInt(0), + Bor: ¶ms.BorConfig{BurntContract: map[string]string{"0": "0x000000000000000000000000000000000000dead"}}, }, Alloc: GenesisAlloc{ common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{ @@ -442,10 +441,11 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr Time: parent.Time() + 10, UncleHash: types.EmptyUncleHash, } + if config.IsLondon(header.Number) { header.BaseFee = misc.CalcBaseFee(config, parent.Header()) } - // TODO marcello double check + if config.IsShanghai(header.Number) { header.WithdrawalsHash = &types.EmptyWithdrawalsHash } @@ -471,7 +471,6 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr header.Root = common.BytesToHash(hasher.Sum(nil)) // Assemble and return the final block for sealing - // TODO marcello double check if config.IsShanghai(header.Number) { return types.NewBlockWithWithdrawals(header, txs, nil, receipts, []*types.Withdrawal{}, trie.NewStackTrie(nil)) } diff --git a/core/state_transition.go b/core/state_transition.go index b71fcbf88d..7042d4b87e 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -403,7 +403,6 @@ func (st *StateTransition) TransitionDb(interruptCtx context.Context) (*Executio } // Check whether the init code size has been exceeded. - // TODO marcello double check if rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSize { return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize) } diff --git a/core/vm/evm.go b/core/vm/evm.go index 9c26831bc6..6d542cdee3 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -134,8 +134,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig StateDB: statedb, Config: config, chainConfig: chainConfig, - // TODO marcello double check - chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), + chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } evm.interpreter = NewEVMInterpreter(evm) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 0a10ae2750..2b961bd532 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -132,7 +132,6 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { var table *JumpTable switch { - // TODO marcello double check case evm.chainRules.IsShanghai: table = &shanghaiInstructionSet case evm.chainRules.IsMerge: diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index b39ec115b1..1cce150eb0 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -16,35 +16,15 @@ package catalyst -import ( - "fmt" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/beacon/engine" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/trie" -) - -var ( - // testKey is a private key to use for funding a tester account. - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - - // testAddr is the Ethereum address of the tester account. - testAddr = crypto.PubkeyToAddress(testKey.PublicKey) - - testBalance = big.NewInt(2e18) -) +// var ( +// // testKey is a private key to use for funding a tester account. +// testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + +// // testAddr is the Ethereum address of the tester account. +// testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + +// testBalance = big.NewInt(2e18) +// ) // func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { // config := *params.AllEthashProtocolChanges @@ -235,25 +215,25 @@ var ( // } // } -func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan core.RemovedLogsEvent, wantNew, wantRemoved int) { - t.Helper() +// func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan core.RemovedLogsEvent, wantNew, wantRemoved int) { +// t.Helper() - if len(logsCh) != wantNew { - t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew) - } +// if len(logsCh) != wantNew { +// t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew) +// } - if len(rmLogsCh) != wantRemoved { - t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved) - } - // Drain events. - for i := 0; i < len(logsCh); i++ { - <-logsCh - } +// if len(rmLogsCh) != wantRemoved { +// t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved) +// } +// // Drain events. +// for i := 0; i < len(logsCh); i++ { +// <-logsCh +// } - for i := 0; i < len(rmLogsCh); i++ { - <-rmLogsCh - } -} +// for i := 0; i < len(rmLogsCh); i++ { +// <-rmLogsCh +// } +// } // func TestInvalidPayloadTimestamp(t *testing.T) { // genesis, preMergeBlocks := generateMergeChain(10, false) @@ -424,87 +404,87 @@ func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan co // } // } -func TestEth2DeepReorg(t *testing.T) { - // TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg - // before the totalTerminalDifficulty threshold - /* - genesis, preMergeBlocks := generateMergeChain(core.TriesInMemory * 2, false) - n, ethservice := startEthService(t, genesis, preMergeBlocks) - defer n.Close() - - var ( - api = NewConsensusAPI(ethservice, nil) - parent = preMergeBlocks[len(preMergeBlocks)-core.TriesInMemory-1] - head = ethservice.BlockChain().CurrentBlock().Number.Uint64()() - ) - if ethservice.BlockChain().HasBlockAndState(parent.Hash(), parent.NumberU64()) { - t.Errorf("Block %d not pruned", parent.NumberU64()) - } - for i := 0; i < 10; i++ { - execData, err := api.assembleBlock(AssembleBlockParams{ - ParentHash: parent.Hash(), - Timestamp: parent.Time() + 5, - }) - if err != nil { - t.Fatalf("Failed to create the executable data %v", err) - } - block, err := ExecutableDataToBlock(ethservice.BlockChain().Config(), parent.Header(), *execData) - if err != nil { - t.Fatalf("Failed to convert executable data to block %v", err) - } - newResp, err := api.ExecutePayload(*execData) - if err != nil || newResp.Status != "VALID" { - t.Fatalf("Failed to insert block: %v", err) - } - if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != head { - t.Fatalf("Chain head shouldn't be updated") - } - if err := api.setHead(block.Hash()); err != nil { - t.Fatalf("Failed to set head: %v", err) - } - if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != block.NumberU64() { - t.Fatalf("Chain head should be updated") - } - parent, head = block, block.NumberU64() +// func TestEth2DeepReorg(t *testing.T) { +// TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg +// before the totalTerminalDifficulty threshold +/* + genesis, preMergeBlocks := generateMergeChain(core.TriesInMemory * 2, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + + var ( + api = NewConsensusAPI(ethservice, nil) + parent = preMergeBlocks[len(preMergeBlocks)-core.TriesInMemory-1] + head = ethservice.BlockChain().CurrentBlock().Number.Uint64()() + ) + if ethservice.BlockChain().HasBlockAndState(parent.Hash(), parent.NumberU64()) { + t.Errorf("Block %d not pruned", parent.NumberU64()) } - */ -} + for i := 0; i < 10; i++ { + execData, err := api.assembleBlock(AssembleBlockParams{ + ParentHash: parent.Hash(), + Timestamp: parent.Time() + 5, + }) + if err != nil { + t.Fatalf("Failed to create the executable data %v", err) + } + block, err := ExecutableDataToBlock(ethservice.BlockChain().Config(), parent.Header(), *execData) + if err != nil { + t.Fatalf("Failed to convert executable data to block %v", err) + } + newResp, err := api.ExecutePayload(*execData) + if err != nil || newResp.Status != "VALID" { + t.Fatalf("Failed to insert block: %v", err) + } + if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != head { + t.Fatalf("Chain head shouldn't be updated") + } + if err := api.setHead(block.Hash()); err != nil { + t.Fatalf("Failed to set head: %v", err) + } + if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != block.NumberU64() { + t.Fatalf("Chain head should be updated") + } + parent, head = block, block.NumberU64() + } +*/ +// } // startEthService creates a full node instance for testing. -func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) { - t.Helper() - - n, err := node.New(&node.Config{ - P2P: p2p.Config{ - ListenAddr: "0.0.0.0:0", - NoDiscovery: true, - MaxPeers: 25, - }}) - if err != nil { - t.Fatal("can't create node:", err) - } +// func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) { +// t.Helper() - ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256} +// n, err := node.New(&node.Config{ +// P2P: p2p.Config{ +// ListenAddr: "0.0.0.0:0", +// NoDiscovery: true, +// MaxPeers: 25, +// }}) +// if err != nil { +// t.Fatal("can't create node:", err) +// } - ethservice, err := eth.New(n, ethcfg) - if err != nil { - t.Fatal("can't create eth service:", err) - } +// ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256} - if err := n.Start(); err != nil { - t.Fatal("can't start node:", err) - } +// ethservice, err := eth.New(n, ethcfg) +// if err != nil { +// t.Fatal("can't create eth service:", err) +// } - if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil { - n.Close() - t.Fatal("can't import test blocks:", err) - } +// if err := n.Start(); err != nil { +// t.Fatal("can't start node:", err) +// } + +// if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil { +// n.Close() +// t.Fatal("can't import test blocks:", err) +// } - ethservice.SetEtherbase(testAddr) - ethservice.SetSynced() +// ethservice.SetEtherbase(testAddr) +// ethservice.SetSynced() - return n, ethservice -} +// return n, ethservice +// } // func TestFullAPI(t *testing.T) { // genesis, preMergeBlocks := generateMergeChain(10, false) @@ -848,48 +828,48 @@ We expect // setBlockhash sets the blockhash of a modified ExecutableData. // Can be used to make modified payloads look valid. -func setBlockhash(data *engine.ExecutableData) *engine.ExecutableData { - txs, _ := decodeTransactions(data.Transactions) - number := big.NewInt(0) - number.SetUint64(data.Number) - - header := &types.Header{ - ParentHash: data.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: data.FeeRecipient, - Root: data.StateRoot, - TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), - ReceiptHash: data.ReceiptsRoot, - Bloom: types.BytesToBloom(data.LogsBloom), - Difficulty: common.Big0, - Number: number, - GasLimit: data.GasLimit, - GasUsed: data.GasUsed, - Time: data.Timestamp, - BaseFee: data.BaseFeePerGas, - Extra: data.ExtraData, - MixDigest: data.Random, - } - block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) - data.BlockHash = block.Hash() +// func setBlockhash(data *engine.ExecutableData) *engine.ExecutableData { +// txs, _ := decodeTransactions(data.Transactions) +// number := big.NewInt(0) +// number.SetUint64(data.Number) + +// header := &types.Header{ +// ParentHash: data.ParentHash, +// UncleHash: types.EmptyUncleHash, +// Coinbase: data.FeeRecipient, +// Root: data.StateRoot, +// TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), +// ReceiptHash: data.ReceiptsRoot, +// Bloom: types.BytesToBloom(data.LogsBloom), +// Difficulty: common.Big0, +// Number: number, +// GasLimit: data.GasLimit, +// GasUsed: data.GasUsed, +// Time: data.Timestamp, +// BaseFee: data.BaseFeePerGas, +// Extra: data.ExtraData, +// MixDigest: data.Random, +// } +// block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) +// data.BlockHash = block.Hash() - return data -} +// return data +// } -func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { - var txs = make([]*types.Transaction, len(enc)) +// func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { +// var txs = make([]*types.Transaction, len(enc)) - for i, encTx := range enc { - var tx types.Transaction - if err := tx.UnmarshalBinary(encTx); err != nil { - return nil, fmt.Errorf("invalid transaction %d: %v", i, err) - } +// for i, encTx := range enc { +// var tx types.Transaction +// if err := tx.UnmarshalBinary(encTx); err != nil { +// return nil, fmt.Errorf("invalid transaction %d: %v", i, err) +// } - txs[i] = &tx - } +// txs[i] = &tx +// } - return txs, nil -} +// return txs, nil +// } // func TestTrickRemoteBlockCache(t *testing.T) { // t.Parallel() diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 84efd96f68..0d1528bb1e 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -225,7 +225,6 @@ type Config struct { // CheckpointOracle is the configuration for checkpoint oracle. CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` - // TODO marcello double check // OverrideShanghai (TODO: remove after the fork) OverrideShanghai *big.Int `toml:",omitempty"` diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 980be46d4e..d1a252b7cf 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -74,7 +74,6 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, engine consensus.Engine = ethash.NewFaker() ) - // TODO marcello double check if shanghai { config = ¶ms.ChainConfig{ ChainID: big.NewInt(1), diff --git a/params/config.go b/params/config.go index a89c1ebd82..fab4e88649 100644 --- a/params/config.go +++ b/params/config.go @@ -998,7 +998,6 @@ func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *bi } // IsShanghai returns whether time is either equal to the Shanghai fork time or greater. -// TODO marcello double check func (c *ChainConfig) IsShanghai(num *big.Int) bool { return isBlockForked(c.ShanghaiBlock, num) }