From 603983b075a17ba00f7f3de8e441360364392f2e Mon Sep 17 00:00:00 2001 From: tristan98 Date: Fri, 27 Sep 2024 01:51:38 +0700 Subject: [PATCH 01/16] fix(crypto/bn256/cloudflare): pull in upstream fix for Go 1.21 R18 --- crypto/bn256/cloudflare/mul_arm64.h | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crypto/bn256/cloudflare/mul_arm64.h b/crypto/bn256/cloudflare/mul_arm64.h index 75d5221731..d405eb8f72 100644 --- a/crypto/bn256/cloudflare/mul_arm64.h +++ b/crypto/bn256/cloudflare/mul_arm64.h @@ -12,7 +12,7 @@ UMULH R1, R8, c4 \ ADCS ZR, c4 \ \ - MUL R2, R5, R25 \ + MUL R2, R5, R1 \ UMULH R2, R5, R26 \ MUL R2, R6, R0 \ ADDS R0, R26 \ @@ -24,13 +24,13 @@ ADCS R0, R29 \ UMULH R2, R8, c5 \ ADCS ZR, c5 \ - ADDS R25, c1 \ + ADDS R1, c1 \ ADCS R26, c2 \ ADCS R27, c3 \ ADCS R29, c4 \ ADCS ZR, c5 \ \ - MUL R3, R5, R25 \ + MUL R3, R5, R1 \ UMULH R3, R5, R26 \ MUL R3, R6, R0 \ ADDS R0, R26 \ @@ -42,13 +42,13 @@ ADCS R0, R29 \ UMULH R3, R8, c6 \ ADCS ZR, c6 \ - ADDS R25, c2 \ + ADDS R1, c2 \ ADCS R26, c3 \ ADCS R27, c4 \ ADCS R29, c5 \ ADCS ZR, c6 \ \ - MUL R4, R5, R25 \ + MUL R4, R5, R1 \ UMULH R4, R5, R26 \ MUL R4, R6, R0 \ ADDS R0, R26 \ @@ -60,7 +60,7 @@ ADCS R0, R29 \ UMULH R4, R8, c7 \ ADCS ZR, c7 \ - ADDS R25, c3 \ + ADDS R1, c3 \ ADCS R26, c4 \ ADCS R27, c5 \ ADCS R29, c6 \ @@ -69,15 +69,15 @@ #define gfpReduce() \ \ // m = (T * N') mod R, store m in R1:R2:R3:R4 MOVD ·np+0(SB), R17 \ - MOVD ·np+8(SB), R18 \ + MOVD ·np+8(SB), R25 \ MOVD ·np+16(SB), R19 \ MOVD ·np+24(SB), R20 \ \ MUL R9, R17, R1 \ UMULH R9, R17, R2 \ - MUL R9, R18, R0 \ + MUL R9, R25, R0 \ ADDS R0, R2 \ - UMULH R9, R18, R3 \ + UMULH R9, R25, R3 \ MUL R9, R19, R0 \ ADCS R0, R3 \ UMULH R9, R19, R4 \ @@ -86,9 +86,9 @@ \ MUL R10, R17, R21 \ UMULH R10, R17, R22 \ - MUL R10, R18, R0 \ + MUL R10, R25, R0 \ ADDS R0, R22 \ - UMULH R10, R18, R23 \ + UMULH R10, R25, R23 \ MUL R10, R19, R0 \ ADCS R0, R23 \ ADDS R21, R2 \ @@ -97,7 +97,7 @@ \ MUL R11, R17, R21 \ UMULH R11, R17, R22 \ - MUL R11, R18, R0 \ + MUL R11, R25, R0 \ ADDS R0, R22 \ ADDS R21, R3 \ ADCS R22, R4 \ @@ -107,19 +107,19 @@ \ \ // m * N loadModulus(R5,R6,R7,R8) \ - mul(R17,R18,R19,R20,R21,R22,R23,R24) \ + mul(R17,R25,R19,R20,R21,R22,R23,R24) \ \ \ // Add the 512-bit intermediate to m*N - MOVD ZR, R25 \ + MOVD ZR, R0 \ ADDS R9, R17 \ - ADCS R10, R18 \ + ADCS R10, R25 \ ADCS R11, R19 \ ADCS R12, R20 \ ADCS R13, R21 \ ADCS R14, R22 \ ADCS R15, R23 \ ADCS R16, R24 \ - ADCS ZR, R25 \ + ADCS ZR, R0 \ \ \ // Our output is R21:R22:R23:R24. Reduce mod p if necessary. SUBS R5, R21, R10 \ From ff40ce2b0a1c8f056b72fb4b4eba20c8f991dcef Mon Sep 17 00:00:00 2001 From: tristan98 Date: Tue, 1 Oct 2024 01:52:44 +0700 Subject: [PATCH 02/16] refactor: PR #18445 - CallArgs and estimateGas API --- internal/ethapi/api.go | 205 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 183 insertions(+), 22 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 33376a1071..b70be8c74b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -424,7 +424,8 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs // safely used to calculate a signature from. // // The hash is calulcated as -// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). // // This gives context to the signed message and prevents signing of transactions. func signHash(data []byte) []byte { @@ -1017,12 +1018,12 @@ func (s *PublicBlockChainAPI) getCandidatesFromSmartContract() ([]posv.Masternod // CallArgs represents the arguments for a call. type CallArgs struct { - From common.Address `json:"from"` + From *common.Address `json:"from"` To *common.Address `json:"to"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice hexutil.Big `json:"gasPrice"` - Value hexutil.Big `json:"value"` - Data hexutil.Bytes `json:"data"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Value *hexutil.Big `json:"value"` + Data *hexutil.Bytes `json:"data"` } func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) ([]byte, uint64, bool, error) { @@ -1033,26 +1034,39 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr return nil, 0, false, err } // Set sender address or use a default if none specified - addr := args.From - if addr == (common.Address{}) { + var addr common.Address + if args.From == nil { if wallets := s.b.AccountManager().Wallets(); len(wallets) > 0 { if accounts := wallets[0].Accounts(); len(accounts) > 0 { addr = accounts[0].Address } } + } else { + addr = *args.From } // Set default gas & gas price if none were set - gas, gasPrice := uint64(args.Gas), args.GasPrice.ToInt() - if gas == 0 { - gas = math.MaxUint64 / 2 + gas := uint64(math.MaxUint64 / 2) + if args.Gas != nil { + gas = uint64(*args.Gas) + } + gasPrice := new(big.Int).SetUint64(defaultGasPrice) + if args.GasPrice != nil { + gasPrice = args.GasPrice.ToInt() + } + + value := new(big.Int) + if args.Value != nil { + value = args.Value.ToInt() } - if gasPrice.Sign() == 0 { - gasPrice = new(big.Int).SetUint64(defaultGasPrice) + + var data []byte + if args.Data != nil { + data = []byte(*args.Data) } balanceTokenFee := big.NewInt(0).SetUint64(gas) balanceTokenFee = balanceTokenFee.Mul(balanceTokenFee, gasPrice) // Create new call message - msg := types.NewMessage(addr, args.To, 0, args.Value.ToInt(), gas, gasPrice, args.Data, false, balanceTokenFee) + msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false, balanceTokenFee) // Setup context so it may be cancelled the call has completed // or, in case of unmetered gas, setup a context with a timeout. @@ -1117,8 +1131,8 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h hi uint64 cap uint64 ) - if uint64(args.Gas) >= params.TxGas { - hi = uint64(args.Gas) + if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { + hi = uint64(*args.Gas) } else { // Retrieve the current pending block to act as the gas ceiling block, err := s.b.BlockByNumber(ctx, rpc.LatestBlockNumber) @@ -1131,7 +1145,7 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h // Create a helper to check if a gas allowance results in an executable transaction executable := func(gas uint64) bool { - args.Gas = hexutil.Uint64(gas) + args.Gas = (*hexutil.Uint64)(&gas) _, _, failed, err := s.doCall(ctx, args, rpc.LatestBlockNumber, vm.Config{}, 0) if err != nil || failed { @@ -1157,6 +1171,151 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h return hexutil.Uint64(hi), nil } +func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) ([]byte, uint64, bool, error) { + defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) + + state, header, err := b.StateAndHeaderByNumber(ctx, blockNr) + if state == nil || err != nil { + return nil, 0, false, err + } + // Set sender address or use a default if none specified + var addr common.Address + if args.From == nil { + if wallets := b.AccountManager().Wallets(); len(wallets) > 0 { + if accounts := wallets[0].Accounts(); len(accounts) > 0 { + addr = accounts[0].Address + } + } + } else { + addr = *args.From + } + // Set default gas & gas price if none were set + gas := uint64(math.MaxUint64 / 2) + if args.Gas != nil { + gas = uint64(*args.Gas) + } + gasPrice := new(big.Int).SetUint64(defaultGasPrice) + if args.GasPrice != nil { + gasPrice = args.GasPrice.ToInt() + } + + value := new(big.Int) + if args.Value != nil { + value = args.Value.ToInt() + } + + var data []byte + if args.Data != nil { + data = []byte(*args.Data) + } + + balanceTokenFee := big.NewInt(0).SetUint64(gas) + balanceTokenFee = balanceTokenFee.Mul(balanceTokenFee, gasPrice) + + // Create new call message + msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false, balanceTokenFee) + + // Setup context so it may be cancelled the call has completed + // or, in case of unmetered gas, setup a context with a timeout. + var cancel context.CancelFunc + if timeout > 0 { + ctx, cancel = context.WithTimeout(ctx, timeout) + } else { + ctx, cancel = context.WithCancel(ctx) + } + // Make sure the context is cancelled when the call has completed + // this makes sure resources are cleaned up. + defer cancel() + + block, err := b.BlockByNumber(ctx, blockNr) + if err != nil { + return nil, 0, false, err + } + author, err := b.GetEngine().Author(block.Header()) + if err != nil { + return nil, 0, false, err + } + tomoxState, err := b.TomoxService().GetTradingState(block, author) + if err != nil { + return nil, 0, false, err + } + + // Get a new instance of the EVM. + evm, vmError, err := b.GetEVM(ctx, msg, state, tomoxState, header, vmCfg) + if err != nil { + return nil, 0, false, err + } + // Wait for the context to be done and cancel the evm. Even if the + // EVM has finished, cancelling may be done (repeatedly) + go func() { + <-ctx.Done() + evm.Cancel() + }() + + // Setup the gas pool (also for unmetered requests) + // and apply the message. + gp := new(core.GasPool).AddGas(math.MaxUint64) + owner := common.Address{} + res, gas, failed, err := core.ApplyMessage(evm, msg, gp, owner) + if err := vmError(); err != nil { + return nil, 0, false, err + } + return res, gas, failed, err +} + +func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Uint64, error) { + // Binary search the gas requirement, as it may be higher than the amount used + var ( + lo uint64 = params.TxGas - 1 + hi uint64 + cap uint64 + ) + if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { + hi = uint64(*args.Gas) + } else { + // Retrieve the block to act as the gas ceiling + block, err := b.BlockByNumber(ctx, blockNr) + if err != nil { + return 0, err + } + hi = block.GasLimit() + } + cap = hi + + // Create a helper to check if a gas allowance results in an executable transaction + executable := func(gas uint64) bool { + args.Gas = (*hexutil.Uint64)(&gas) + + _, _, failed, err := DoCall(ctx, b, args, rpc.LatestBlockNumber, vm.Config{}, 0) + if err != nil || failed { + return false + } + return true + } + // Execute the binary search and hone in on an executable gas limit + for lo+1 < hi { + mid := (hi + lo) / 2 + if !executable(mid) { + lo = mid + } else { + hi = mid + } + } + // Reject the transaction as invalid if it still fails at the highest allowance + if hi == cap { + if !executable(hi) { + return 0, fmt.Errorf("gas required exceeds allowance or always failing transaction") + } + } + return hexutil.Uint64(hi), nil +} + +// NewEstimateGas returns an estimate of the amount of gas needed to execute the +// given transaction against the current pending block. +func (s *PublicBlockChainAPI) NewEstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { + return DoEstimateGas(ctx, s.b, args, rpc.LatestBlockNumber) +} + // ExecutionResult groups all structured logs emitted by the EVM // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value @@ -1305,8 +1464,8 @@ func (s *PublicBlockChainAPI) findNearestSignedBlock(ctx context.Context, b *typ } /* - findFinalityOfBlock return finality of a block - Use blocksHashCache for to keep track - refer core/blockchain.go for more detail +findFinalityOfBlock return finality of a block +Use blocksHashCache for to keep track - refer core/blockchain.go for more detail */ func (s *PublicBlockChainAPI) findFinalityOfBlock(ctx context.Context, b *types.Block, masternodes []common.Address) (uint, error) { engine, _ := s.b.GetEngine().(*posv.Posv) @@ -1371,7 +1530,7 @@ func (s *PublicBlockChainAPI) findFinalityOfBlock(ctx context.Context, b *types. } /* - Extract signers from block +Extract signers from block */ func (s *PublicBlockChainAPI) getSigners(ctx context.Context, block *types.Block, engine *posv.Posv) ([]common.Address, error) { var err error @@ -2965,7 +3124,8 @@ func GetSignersFromBlocks(b Backend, blockNumber uint64, blockHash common.Hash, // GetStakerROI Estimate ROI for stakers using the last epoc reward // then multiple by epoch per year, if the address is not masternode of last epoch - return 0 // Formular: -// ROI = average_latest_epoch_reward_for_voters*number_of_epoch_per_year/latest_total_cap*100 +// +// ROI = average_latest_epoch_reward_for_voters*number_of_epoch_per_year/latest_total_cap*100 func (s *PublicBlockChainAPI) GetStakerROI() float64 { blockNumber := s.b.CurrentBlock().Number().Uint64() lastCheckpointNumber := blockNumber - (blockNumber % s.b.ChainConfig().Posv.Epoch) - s.b.ChainConfig().Posv.Epoch // calculate for 2 epochs ago @@ -2991,7 +3151,8 @@ func (s *PublicBlockChainAPI) GetStakerROI() float64 { // GetStakerROIMasternode Estimate ROI for stakers of a specific masternode using the last epoc reward // then multiple by epoch per year, if the address is not masternode of last epoch - return 0 // Formular: -// ROI = latest_epoch_reward_for_voters*number_of_epoch_per_year/latest_total_cap*100 +// +// ROI = latest_epoch_reward_for_voters*number_of_epoch_per_year/latest_total_cap*100 func (s *PublicBlockChainAPI) GetStakerROIMasternode(masternode common.Address) float64 { votersReward := s.b.GetVotersRewards(masternode) if votersReward == nil { From 7b74145c58dfe89bb223d3621694387aadfbc244 Mon Sep 17 00:00:00 2001 From: tristan98 Date: Tue, 1 Oct 2024 10:20:26 +0700 Subject: [PATCH 03/16] refactor: PR #20261 #20702 - Use from address only once, Set sender to 0x00 --- internal/ethapi/api.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index b70be8c74b..be45c2a31c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1180,13 +1180,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb } // Set sender address or use a default if none specified var addr common.Address - if args.From == nil { - if wallets := b.AccountManager().Wallets(); len(wallets) > 0 { - if accounts := wallets[0].Accounts(); len(accounts) > 0 { - addr = accounts[0].Address - } - } - } else { + if args.From != nil { addr = *args.From } // Set default gas & gas price if none were set @@ -1282,6 +1276,11 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl } cap = hi + // Use zero address if sender unspecified. + if args.From == nil { + args.From = new(common.Address) + } + // Create a helper to check if a gas allowance results in an executable transaction executable := func(gas uint64) bool { args.Gas = (*hexutil.Uint64)(&gas) From 1313614be846a1ecd48c5b7fee14f4a478ba4fd6 Mon Sep 17 00:00:00 2001 From: tristan98 Date: Wed, 2 Oct 2024 00:42:46 +0700 Subject: [PATCH 04/16] refactor: comment balanceTokenFee related code & add unit test --- common/types.go | 6 + core/chain_makers.go | 13 ++ core/state_transition.go | 75 +++++-- core/types/transaction.go | 6 +- internal/ethapi/api_test.go | 404 ++++++++++++++++++++++++++++++++++++ 5 files changed, 483 insertions(+), 21 deletions(-) create mode 100644 internal/ethapi/api_test.go diff --git a/common/types.go b/common/types.go index ca94230113..73b9e38913 100644 --- a/common/types.go +++ b/common/types.go @@ -17,6 +17,7 @@ package common import ( + "bytes" "encoding/hex" "fmt" "math/big" @@ -180,6 +181,11 @@ func IsHexAddress(s string) bool { return len(s) == 2*AddressLength && isHex(s) } +// Cmp compares two addresses. +func (a Address) Cmp(other Address) int { + return bytes.Compare(a[:], other[:]) +} + // Get the string representation of the underlying address func (a Address) Str() string { return string(a[:]) } func (a Address) Bytes() []byte { return a[:] } diff --git a/core/chain_makers.go b/core/chain_makers.go index 9eac92de6d..b13057acb3 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -242,6 +242,19 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse return blocks, receipts } +// GenerateChainWithGenesis is a wrapper of GenerateChain which will initialize +// genesis block to database first according to the provided genesis specification +// then generate chain on top. +func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) { + db := rawdb.NewMemoryDatabase() + _, err := genesis.Commit(db) + if err != nil { + panic(err) + } + blocks, receipts := GenerateChain(genesis.Config, genesis.ToBlock(db), engine, db, n, gen) + return db, blocks, receipts +} + func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header { var time *big.Int if parent.Time() == nil { diff --git a/core/state_transition.go b/core/state_transition.go index 0c844c3cf4..2b810a7418 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -172,29 +172,50 @@ func (st *StateTransition) useGas(amount uint64) error { return nil } +//func (st *StateTransition) buyGas() error { +// var ( +// state = st.state +// balanceTokenFee = st.balanceTokenFee() +// from = st.from() +// ) +// mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) +// if balanceTokenFee == nil { +// if state.GetBalance(from.Address()).Cmp(mgval) < 0 { +// return errInsufficientBalanceForGas +// } +// } else if balanceTokenFee.Cmp(mgval) < 0 { +// return errInsufficientBalanceForGas +// } +// if err := st.gp.SubGas(st.msg.Gas()); err != nil { +// return err +// } +// st.gas += st.msg.Gas() +// +// st.initialGas = st.msg.Gas() +// if balanceTokenFee == nil { +// state.SubBalance(from.Address(), mgval) +// } +// return nil +//} + func (st *StateTransition) buyGas() error { var ( - state = st.state - balanceTokenFee = st.balanceTokenFee() - from = st.from() + state = st.state + from = st.from() ) mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) - if balanceTokenFee == nil { - if state.GetBalance(from.Address()).Cmp(mgval) < 0 { - return errInsufficientBalanceForGas - } - } else if balanceTokenFee.Cmp(mgval) < 0 { + if state.GetBalance(from.Address()).Cmp(mgval) < 0 { return errInsufficientBalanceForGas } + if err := st.gp.SubGas(st.msg.Gas()); err != nil { return err } st.gas += st.msg.Gas() st.initialGas = st.msg.Gas() - if balanceTokenFee == nil { - state.SubBalance(from.Address(), mgval) - } + state.SubBalance(from.Address(), mgval) + return nil } @@ -279,6 +300,26 @@ func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedG return ret, st.gasUsed(), vmerr != nil, err } +//func (st *StateTransition) refundGas() { +// // Apply refund counter, capped to half of the used gas. +// refund := st.gasUsed() / 2 +// if refund > st.state.GetRefund() { +// refund = st.state.GetRefund() +// } +// st.gas += refund +// +// balanceTokenFee := st.balanceTokenFee() +// if balanceTokenFee == nil { +// from := st.from() +// // Return ETH for remaining gas, exchanged at the original rate. +// remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) +// st.state.AddBalance(from.Address(), remaining) +// } +// // Also return remaining gas to the block gas counter so it is +// // available for the next transaction. +// st.gp.AddGas(st.gas) +//} + func (st *StateTransition) refundGas() { // Apply refund counter, capped to half of the used gas. refund := st.gasUsed() / 2 @@ -287,13 +328,11 @@ func (st *StateTransition) refundGas() { } st.gas += refund - balanceTokenFee := st.balanceTokenFee() - if balanceTokenFee == nil { - from := st.from() - // Return ETH for remaining gas, exchanged at the original rate. - remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) - st.state.AddBalance(from.Address(), remaining) - } + from := st.from() + // Return ETH for remaining gas, exchanged at the original rate. + remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) + st.state.AddBalance(from.Address(), remaining) + // Also return remaining gas to the block gas counter so it is // available for the next transaction. st.gp.AddGas(st.gas) diff --git a/core/types/transaction.go b/core/types/transaction.go index 50a0af2afb..f32a251f35 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -689,9 +689,9 @@ type Message struct { } func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool, balanceTokenFee *big.Int) Message { - if balanceTokenFee != nil { - gasPrice = common.TRC21GasPrice - } + //if balanceTokenFee != nil { + // gasPrice = common.TRC21GasPrice + //} return Message{ from: from, to: to, diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go new file mode 100644 index 0000000000..83bf466c19 --- /dev/null +++ b/internal/ethapi/api_test.go @@ -0,0 +1,404 @@ +package ethapi + +import ( + "context" + "crypto/ecdsa" + "errors" + "fmt" + "github.com/tomochain/tomochain/accounts" + "github.com/tomochain/tomochain/common" + "github.com/tomochain/tomochain/common/hexutil" + "github.com/tomochain/tomochain/consensus" + "github.com/tomochain/tomochain/consensus/ethash" + "github.com/tomochain/tomochain/core" + "github.com/tomochain/tomochain/core/state" + "github.com/tomochain/tomochain/core/types" + "github.com/tomochain/tomochain/core/vm" + "github.com/tomochain/tomochain/crypto" + "github.com/tomochain/tomochain/eth/downloader" + "github.com/tomochain/tomochain/ethclient" + "github.com/tomochain/tomochain/ethdb" + "github.com/tomochain/tomochain/event" + "github.com/tomochain/tomochain/params" + "github.com/tomochain/tomochain/rpc" + "github.com/tomochain/tomochain/tomox" + "github.com/tomochain/tomochain/tomox/tradingstate" + "github.com/tomochain/tomochain/tomoxlending" + "math/big" + "slices" + "testing" + "time" +) + +type testBackend struct { + db ethdb.Database + chain *core.BlockChain + pending *types.Block + TomoX *tomox.TomoX +} + +func (t testBackend) Downloader() *downloader.Downloader { + //TODO implement me + panic("implement me") +} + +func (t testBackend) ProtocolVersion() int { + //TODO implement me + panic("implement me") +} + +func (t testBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { + //TODO implement me + panic("implement me") +} + +func (t testBackend) ChainDb() ethdb.Database { + //TODO implement me + panic("implement me") +} + +func (t testBackend) EventMux() *event.TypeMux { + //TODO implement me + panic("implement me") +} + +func (b testBackend) AccountManager() *accounts.Manager { + return &accounts.Manager{} +} + +func (b testBackend) TomoxService() *tomox.TomoX { + return b.TomoX +} + +func (t testBackend) LendingService() *tomoxlending.Lending { + //TODO implement me + panic("implement me") +} + +func (t testBackend) SetHead(number uint64) { + //TODO implement me + panic("implement me") +} + +func (b testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) { + if blockNr == rpc.LatestBlockNumber { + return b.chain.CurrentBlock().Header(), nil + } + return b.chain.GetHeaderByNumber(uint64(blockNr)), nil +} + +func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + if number == rpc.LatestBlockNumber { + return b.chain.CurrentBlock(), nil + } + if number == rpc.PendingBlockNumber { + return b.pending, nil + } + return b.chain.GetBlockByNumber(uint64(number)), nil +} + +func (b testBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error) { + // Otherwise resolve the block number and return its state + header, err := b.HeaderByNumber(ctx, blockNr) + if header == nil || err != nil { + return nil, nil, err + } + stateDb, err := b.chain.StateAt(header.Root) + return stateDb, header, err +} + +func (t testBackend) GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetTd(blockHash common.Hash) *big.Int { + //TODO implement me + panic("implement me") +} + +func (b testBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, tomoxState *tradingstate.TradingStateDB, header *types.Header, vmCfg vm.Config) (*vm.EVM, func() error, error) { + //state.SetBalance(msg.From(), math.MaxBig256) + vmError := func() error { return nil } + + context := core.NewEVMContext(msg, header, b.chain, nil) + return vm.NewEVM(context, state, tomoxState, b.chain.Config(), vmCfg), vmError, nil +} + +func (t testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + //TODO implement me + panic("implement me") +} + +func (t testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + //TODO implement me + panic("implement me") +} + +func (t testBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { + //TODO implement me + panic("implement me") +} + +func (t testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetPoolTransactions() (types.Transactions, error) { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { + //TODO implement me + panic("implement me") +} + +func (t testBackend) Stats() (pending int, queued int) { + //TODO implement me + panic("implement me") +} + +func (t testBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { + //TODO implement me + panic("implement me") +} + +func (t testBackend) SubscribeTxPreEvent(events chan<- core.TxPreEvent) event.Subscription { + //TODO implement me + panic("implement me") +} + +func (t testBackend) SendOrderTx(ctx context.Context, signedTx *types.OrderTransaction) error { + //TODO implement me + panic("implement me") +} + +func (t testBackend) OrderTxPoolContent() (map[common.Address]types.OrderTransactions, map[common.Address]types.OrderTransactions) { + //TODO implement me + panic("implement me") +} + +func (t testBackend) OrderStats() (pending int, queued int) { + //TODO implement me + panic("implement me") +} + +func (t testBackend) SendLendingTx(ctx context.Context, signedTx *types.LendingTransaction) error { + //TODO implement me + panic("implement me") +} + +func (t testBackend) ChainConfig() *params.ChainConfig { + //TODO implement me + panic("implement me") +} + +func (t testBackend) CurrentBlock() *types.Block { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetIPCClient() (*ethclient.Client, error) { + //TODO implement me + panic("implement me") +} + +func (b testBackend) GetEngine() consensus.Engine { + return b.chain.Engine() +} + +func (t testBackend) GetRewardByHash(hash common.Hash) map[string]map[string]map[string]*big.Int { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetVotersRewards(address common.Address) map[common.Address]*big.Int { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetVotersCap(checkpoint *big.Int, masterAddr common.Address, voters []common.Address) map[common.Address]*big.Int { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetEpochDuration() *big.Int { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetMasternodesCap(checkpoint uint64) map[common.Address]*big.Int { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetBlocksHashCache(blockNr uint64) []common.Hash { + //TODO implement me + panic("implement me") +} + +func (t testBackend) AreTwoBlockSamePath(newBlock common.Hash, oldBlock common.Hash) bool { + //TODO implement me + panic("implement me") +} + +func (t testBackend) GetOrderNonce(address common.Hash) (uint64, error) { + //TODO implement me + panic("implement me") +} + +func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { + var ( + engine = ethash.NewFaker() + cacheConfig = &core.CacheConfig{ + TrieTimeLimit: 5 * time.Minute, + TrieNodeLimit: 256 * 1024 * 1024, + } + ) + // Generate blocks for testing + db, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator) + chain, err := core.NewBlockChain(db, cacheConfig, params.TestChainConfig, engine, vm.Config{}) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + tomo := tomox.New(&tomox.DefaultConfig) + + backend := &testBackend{db: db, chain: chain, TomoX: tomo} + return backend +} + +type Account struct { + key *ecdsa.PrivateKey + addr common.Address +} + +func newAccounts(n int) (accounts []Account) { + for i := 0; i < n; i++ { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + accounts = append(accounts, Account{key: key, addr: addr}) + } + slices.SortFunc(accounts, func(a, b Account) int { + return a.addr.Cmp(b.addr) + }) + return accounts +} + +func TestEstimateGas(t *testing.T) { + t.Parallel() + // Initialize test accounts + var ( + accounts = newAccounts(2) + genesis = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + genBlocks = 10 + signer = types.HomesteadSigner{} + randomAccounts = newAccounts(2) + ) + api := NewPublicBlockChainAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, err := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, nil, nil), signer, accounts[0].key) + if err != nil { + panic(err) + } + b.AddTx(tx) + })) + blockNumber := rpc.LatestBlockNumber + balance, _ := api.GetBalance(context.Background(), randomAccounts[0].addr, blockNumber) + fmt.Println("Random account address:", randomAccounts[0].addr.Hex(), "Balance", balance) + var testSuite = []struct { + blockNumber rpc.BlockNumber + call CallArgs + expectErr error + want uint64 + }{ + // simple transfer on latest block + { + blockNumber: rpc.LatestBlockNumber, + call: CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + expectErr: nil, + want: 21000, + }, + // simple transfer with insufficient funds on latest block + { + blockNumber: rpc.LatestBlockNumber, + call: CallArgs{ + From: &randomAccounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + expectErr: core.ErrInsufficientFunds, + want: 21000, + }, + // empty create + { + blockNumber: rpc.LatestBlockNumber, + call: CallArgs{}, + expectErr: nil, + want: 53000, + }, + { + blockNumber: rpc.LatestBlockNumber, + call: CallArgs{}, + expectErr: nil, + want: 53000, + }, + { + blockNumber: rpc.LatestBlockNumber, + call: CallArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + expectErr: core.ErrInsufficientFunds, + }, + } + for i, tc := range testSuite { + result, err := api.NewEstimateGas(context.Background(), tc.call) + fmt.Println("TestEstimateGas Gas estimation result:", result) + if tc.expectErr != nil { + if err == nil { + t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) + continue + } + if !errors.Is(err, tc.expectErr) { + t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) + } + continue + } + if err != nil { + t.Errorf("test %d: want no error, have %v", i, err) + continue + } + if uint64(result) != tc.want { + t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want) + } + } +} From 36ff14eec4afd9e44b0927b13244c7ae036467e5 Mon Sep 17 00:00:00 2001 From: tristan98 Date: Wed, 2 Oct 2024 10:31:08 +0700 Subject: [PATCH 05/16] fix: PR #20783 unusual balance return, set gasPrice to zero --- eth/api_backend.go | 2 -- internal/ethapi/api.go | 5 ++--- internal/ethapi/api_test.go | 7 ------- les/api_backend.go | 2 -- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/eth/api_backend.go b/eth/api_backend.go index 7fd7aac3b1..9efdd6a4dd 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -34,7 +34,6 @@ import ( "github.com/tomochain/tomochain/accounts" "github.com/tomochain/tomochain/common" - "github.com/tomochain/tomochain/common/math" "github.com/tomochain/tomochain/consensus" "github.com/tomochain/tomochain/contracts" "github.com/tomochain/tomochain/core" @@ -138,7 +137,6 @@ func (b *EthApiBackend) GetTd(blockHash common.Hash) *big.Int { } func (b *EthApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, tomoxState *tradingstate.TradingStateDB, header *types.Header, vmCfg vm.Config) (*vm.EVM, func() error, error) { - state.SetBalance(msg.From(), math.MaxBig256) vmError := func() error { return nil } context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index be45c2a31c..09d3a401b2 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -53,7 +53,6 @@ import ( ) const ( - defaultGasPrice = 50 * params.Shannon // statuses of candidates statusMasternode = "MASTERNODE" statusSlashed = "SLASHED" @@ -1049,7 +1048,7 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr if args.Gas != nil { gas = uint64(*args.Gas) } - gasPrice := new(big.Int).SetUint64(defaultGasPrice) + gasPrice := new(big.Int) if args.GasPrice != nil { gasPrice = args.GasPrice.ToInt() } @@ -1188,7 +1187,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb if args.Gas != nil { gas = uint64(*args.Gas) } - gasPrice := new(big.Int).SetUint64(defaultGasPrice) + gasPrice := new(big.Int) if args.GasPrice != nil { gasPrice = args.GasPrice.ToInt() } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 83bf466c19..9c03e09677 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -123,7 +123,6 @@ func (t testBackend) GetTd(blockHash common.Hash) *big.Int { } func (b testBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, tomoxState *tradingstate.TradingStateDB, header *types.Header, vmCfg vm.Config) (*vm.EVM, func() error, error) { - //state.SetBalance(msg.From(), math.MaxBig256) vmError := func() error { return nil } context := core.NewEVMContext(msg, header, b.chain, nil) @@ -364,12 +363,6 @@ func TestEstimateGas(t *testing.T) { expectErr: nil, want: 53000, }, - { - blockNumber: rpc.LatestBlockNumber, - call: CallArgs{}, - expectErr: nil, - want: 53000, - }, { blockNumber: rpc.LatestBlockNumber, call: CallArgs{ diff --git a/les/api_backend.go b/les/api_backend.go index d8285da97d..f7f8d69acc 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -30,7 +30,6 @@ import ( "github.com/tomochain/tomochain/accounts" "github.com/tomochain/tomochain/common" - "github.com/tomochain/tomochain/common/math" "github.com/tomochain/tomochain/consensus" "github.com/tomochain/tomochain/core" "github.com/tomochain/tomochain/core/bloombits" @@ -106,7 +105,6 @@ func (b *LesApiBackend) GetTd(blockHash common.Hash) *big.Int { } func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, tomoxState *tradingstate.TradingStateDB, header *types.Header, vmCfg vm.Config) (*vm.EVM, func() error, error) { - state.SetBalance(msg.From(), math.MaxBig256) context := core.NewEVMContext(msg, header, b.eth.blockchain, nil) return vm.NewEVM(context, state, tomoxState, b.eth.chainConfig, vmCfg), state.Error, nil } From 6c30566a9e73f428df02e2583b3dff2cdb3e6380 Mon Sep 17 00:00:00 2001 From: tristan98 Date: Fri, 4 Oct 2024 00:45:32 +0700 Subject: [PATCH 06/16] refactor: PR #20830, #21083 add executionResult, return revert reason --- accounts/abi/abi.go | 25 +++++ accounts/abi/abi_test.go | 53 ++++++++-- accounts/abi/bind/backends/simulated.go | 98 +++++++++++++++--- core/error.go | 7 ++ core/state_processor.go | 12 +-- core/state_transition.go | 132 +++++++++++++++--------- core/token_validator.go | 6 +- eth/api_tracer.go | 10 +- eth/tracers/tracers_test.go | 16 +-- internal/ethapi/api.go | 120 +++++++++++++++------ internal/ethapi/api_test.go | 52 +++++----- les/odr_test.go | 8 +- light/odr_test.go | 4 +- tests/state_test_util.go | 2 +- 14 files changed, 386 insertions(+), 159 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 254b1f7fb4..e875d51b65 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -19,7 +19,9 @@ package abi import ( "bytes" "encoding/json" + "errors" "fmt" + "github.com/tomochain/tomochain/crypto" "io" ) @@ -144,3 +146,26 @@ func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { } return nil, fmt.Errorf("no method with id: %#x", sigdata[:4]) } + +// revertSelector is a special function selector for revert reason unpacking. +var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4] + +// UnpackRevert resolves the abi-encoded revert reason. According to the solidity +// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert, +// the provided revert reason is abi-encoded as if it were a call to a function +// `Error(string)`. So it's a special tool for it. +func UnpackRevert(data []byte) (string, error) { + if len(data) < 4 { + return "", errors.New("invalid data for unpacking") + } + if !bytes.Equal(data[:4], revertSelector) { + return "", errors.New("invalid data for unpacking") + } + var reason string + // typ, _ := NewType("string", "", nil) + typ, _ := NewType("string") + if err := (Arguments{{Type: typ}}).Unpack(&reason, data[4:]); err != nil { + return "", err + } + return reason, nil +} diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 5a128bfe54..9092b6cd81 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -19,6 +19,7 @@ package abi import ( "bytes" "encoding/hex" + "errors" "fmt" "log" "math/big" @@ -619,16 +620,19 @@ func TestBareEvents(t *testing.T) { } // TestUnpackEvent is based on this contract: -// contract T { -// event received(address sender, uint amount, bytes memo); -// event receivedAddr(address sender); -// function receive(bytes memo) external payable { -// received(msg.sender, msg.value, memo); -// receivedAddr(msg.sender); -// } -// } +// +// contract T { +// event received(address sender, uint amount, bytes memo); +// event receivedAddr(address sender); +// function receive(bytes memo) external payable { +// received(msg.sender, msg.value, memo); +// receivedAddr(msg.sender); +// } +// } +// // When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt: -// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} +// +// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} func TestUnpackEvent(t *testing.T) { const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` abi, err := JSON(strings.NewReader(abiJSON)) @@ -713,3 +717,34 @@ func TestABI_MethodById(t *testing.T) { } } + +func TestUnpackRevert(t *testing.T) { + t.Parallel() + + var cases = []struct { + input string + expect string + expectErr error + }{ + {"", "", errors.New("invalid data for unpacking")}, + {"08c379a1", "", errors.New("invalid data for unpacking")}, + {"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil}, + } + for index, c := range cases { + t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) { + got, err := UnpackRevert(common.Hex2Bytes(c.input)) + if c.expectErr != nil { + if err == nil { + t.Fatalf("Expected non-nil error") + } + if err.Error() != c.expectErr.Error() { + t.Fatalf("Expected error mismatch, want %v, got %v", c.expectErr, err) + } + return + } + if c.expect != got { + t.Fatalf("Output mismatch, want %v, got %v", c.expect, got) + } + }) + } +} diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 7411f492a8..022c8197ba 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -20,6 +20,8 @@ import ( "context" "errors" "fmt" + "github.com/tomochain/tomochain/accounts/abi" + "github.com/tomochain/tomochain/common/hexutil" "github.com/tomochain/tomochain/consensus" "github.com/tomochain/tomochain/core/rawdb" "math/big" @@ -186,6 +188,36 @@ func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Ad return b.pendingState.GetCode(contract), nil } +func newRevertError(result *core.ExecutionResult) *revertError { + reason, errUnpack := abi.UnpackRevert(result.Revert()) + err := errors.New("execution reverted") + if errUnpack == nil { + err = fmt.Errorf("execution reverted: %v", reason) + } + return &revertError{ + error: err, + reason: hexutil.Encode(result.Revert()), + } +} + +// revertError is an API error that encompassas an EVM revertal with JSON error +// code and a binary data blob. +type revertError struct { + error + reason string // revert reason hex encoded +} + +// ErrorCode returns the JSON error code for a revertal. +// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal +func (e *revertError) ErrorCode() int { + return 3 +} + +// ErrorData returns the hex encoded revert reason. +func (e *revertError) ErrorData() interface{} { + return e.reason +} + // CallContract executes a contract call. func (b *SimulatedBackend) CallContract(ctx context.Context, call tomochain.CallMsg, blockNumber *big.Int) ([]byte, error) { b.mu.Lock() @@ -198,11 +230,15 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call tomochain.Call if err != nil { return nil, err } - rval, _, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) - return rval, err + res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) + // If the result contains a revert reason, try to unpack and return it. + if len(res.Revert()) > 0 { + return nil, newRevertError(res) + } + return res.Return(), res.Err } -//FIXME: please use copyState for this function +// FIXME: please use copyState for this function // CallContractWithState executes a contract call at the given state. func (b *SimulatedBackend) CallContractWithState(call tomochain.CallMsg, chain consensus.ChainContext, statedb *state.StateDB) ([]byte, error) { // Ensure message is initialized properly. @@ -228,11 +264,11 @@ func (b *SimulatedBackend) CallContractWithState(call tomochain.CallMsg, chain c vmenv := vm.NewEVM(evmContext, statedb, nil, chain.Config(), vm.Config{}) gaspool := new(core.GasPool).AddGas(1000000) owner := common.Address{} - rval, _, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner) + result, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner) if err != nil { return nil, err } - return rval, err + return result.Return(), err } // PendingCallContract executes a contract call on the pending state. @@ -241,8 +277,15 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call tomocha defer b.mu.Unlock() defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) - rval, _, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) - return rval, err + result, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) + if err != nil { + return nil, err + } + // If the result contains a revert reason, try to unpack and return it. + if len(result.Revert()) > 0 { + return nil, newRevertError(result) + } + return result.Return(), result.Err } // PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving @@ -280,23 +323,33 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call tomochain.CallM cap = hi // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) bool { + executable := func(gas uint64) (bool, *core.ExecutionResult, error) { call.Gas = gas snapshot := b.pendingState.Snapshot() - _, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) - fmt.Println("EstimateGas",err,failed) + res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) b.pendingState.RevertToSnapshot(snapshot) - if err != nil || failed { - return false + if err != nil { + if err == core.ErrIntrinsicGas { + return true, nil, nil // Special case, raise gas limit + } + return true, nil, err // Bail out } - return true + return res.Failed(), res, nil } // Execute the binary search and hone in on an executable gas limit for lo+1 < hi { mid := (hi + lo) / 2 - if !executable(mid) { + failed, _, err := executable(mid) + + // If the error is not nil(consensus error), it means the provided message + // call or transaction will never be accepted no matter how much gas it is + // assigned. Return the error directly, don't struggle any more + if err != nil { + return 0, err + } + if failed { lo = mid } else { hi = mid @@ -304,8 +357,19 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call tomochain.CallM } // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { - if !executable(hi) { - return 0, errGasEstimationFailed + failed, result, err := executable(hi) + if err != nil { + return 0, err + } + if failed { + if result != nil && result.Err != vm.ErrOutOfGas { + if len(result.Revert()) > 0 { + return 0, newRevertError(result) + } + return 0, result.Err + } + // Otherwise, the specified gas cap is too low + return 0, result.Err } } return hi, nil @@ -313,7 +377,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call tomochain.CallM // callContract implements common code between normal and pending contract calls. // state is modified during execution, make sure to copy it if necessary. -func (b *SimulatedBackend) callContract(ctx context.Context, call tomochain.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) { +func (b *SimulatedBackend) callContract(ctx context.Context, call tomochain.CallMsg, block *types.Block, statedb *state.StateDB) (*core.ExecutionResult, error) { // Ensure message is initialized properly. if call.GasPrice == nil { call.GasPrice = big.NewInt(1) diff --git a/core/error.go b/core/error.go index 63be6ab83d..d47530300b 100644 --- a/core/error.go +++ b/core/error.go @@ -38,4 +38,11 @@ var ( ErrNotFoundM1 = errors.New("list M1 not found ") ErrStopPreparingBlock = errors.New("stop calculating a block not verified by M2") + + // ErrGasUintOverflow is returned when calculating gas usage. + ErrGasUintOverflow = errors.New("gas uint64 overflow") + + // ErrInsufficientFundsForTransfer is returned if the transaction sender doesn't + // have enough funds for transfer(topmost call only). + ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer") ) diff --git a/core/state_processor.go b/core/state_processor.go index 2328525a74..8b4b9406ac 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -417,7 +417,7 @@ func ApplyTransaction(config *params.ChainConfig, tokensFee map[common.Address]* // End Bypass blacklist address // Apply the transaction to the current state (included in the env) - _, gas, failed, err := ApplyMessage(vmenv, msg, gp, coinbaseOwner) + result, err := ApplyMessage(vmenv, msg, gp, coinbaseOwner) if err != nil { return nil, 0, err, false @@ -429,13 +429,13 @@ func ApplyTransaction(config *params.ChainConfig, tokensFee map[common.Address]* } else { root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes() } - *usedGas += gas + *usedGas += result.UsedGas // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx // based on the eip phase, we're passing wether the root touch-delete accounts. - receipt := types.NewReceipt(root, failed, *usedGas) + receipt := types.NewReceipt(root, result.Failed(), *usedGas) receipt.TxHash = tx.Hash() - receipt.GasUsed = gas + receipt.GasUsed = result.UsedGas // if the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) @@ -443,10 +443,10 @@ func ApplyTransaction(config *params.ChainConfig, tokensFee map[common.Address]* // Set the receipt logs and create a bloom for filtering receipt.Logs = statedb.GetLogs(tx.Hash()) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) - if balanceFee != nil && failed { + if balanceFee != nil && result.Failed() { state.PayFeeWithTRC21TxFail(statedb, msg.From(), *tx.To()) } - return receipt, gas, err, balanceFee != nil + return receipt, result.UsedGas, err, balanceFee != nil } func ApplySignTransaction(config *params.ChainConfig, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64) (*types.Receipt, uint64, error, bool) { diff --git a/core/state_transition.go b/core/state_transition.go index 2b810a7418..7ecc68a03b 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,20 +17,14 @@ package core import ( - "errors" "math" "math/big" "github.com/tomochain/tomochain/common" "github.com/tomochain/tomochain/core/vm" - "github.com/tomochain/tomochain/log" "github.com/tomochain/tomochain/params" ) -var ( - errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas") -) - /* The State Transitioning Model @@ -78,6 +72,41 @@ type Message interface { BalanceTokenFee() *big.Int } +// ExecutionResult includes all output after executing given evm +// message no matter the execution itself is successful or not. +type ExecutionResult struct { + UsedGas uint64 // Total used gas but include the refunded gas + Err error // Any error encountered during the execution(listed in core/vm/errors.go) + ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) +} + +// Unwrap returns the internal evm error which allows us for further +// analysis outside. +func (result *ExecutionResult) Unwrap() error { + return result.Err +} + +// Failed returns the indicator whether the execution is successful or not +func (result *ExecutionResult) Failed() bool { return result.Err != nil } + +// Return is a helper function to help caller distinguish between revert reason +// and function return. Return returns the data after execution if no error occurs. +func (result *ExecutionResult) Return() []byte { + if result.Err != nil { + return nil + } + return common.CopyBytes(result.ReturnData) +} + +// Revert returns the concrete revert reason if the execution is aborted by `REVERT` +// opcode. Note the reason can be nil if no data supplied with revert opcode. +func (result *ExecutionResult) Revert() []byte { + if result.Err != vm.ErrExecutionReverted { + return nil + } + return common.CopyBytes(result.ReturnData) +} + // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. func IntrinsicGas(data []byte, contractCreation, homestead bool) (uint64, error) { // Set the starting gas for the raw transaction @@ -98,13 +127,13 @@ func IntrinsicGas(data []byte, contractCreation, homestead bool) (uint64, error) } // Make sure we don't exceed uint64 for all data combinations if (math.MaxUint64-gas)/params.TxDataNonZeroGas < nz { - return 0, vm.ErrOutOfGas + return 0, ErrGasUintOverflow } gas += nz * params.TxDataNonZeroGas z := uint64(len(data)) - nz if (math.MaxUint64-gas)/params.TxDataZeroGas < z { - return 0, vm.ErrOutOfGas + return 0, ErrGasUintOverflow } gas += z * params.TxDataZeroGas } @@ -131,7 +160,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition // the gas used (which includes gas refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. -func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool, owner common.Address) ([]byte, uint64, bool, error) { +func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool, owner common.Address) (*ExecutionResult, error) { return NewStateTransition(evm, msg, gp).TransitionDb(owner) } @@ -163,15 +192,6 @@ func (st *StateTransition) to() vm.AccountRef { return reference } -func (st *StateTransition) useGas(amount uint64) error { - if st.gas < amount { - return vm.ErrOutOfGas - } - st.gas -= amount - - return nil -} - //func (st *StateTransition) buyGas() error { // var ( // state = st.state @@ -205,7 +225,7 @@ func (st *StateTransition) buyGas() error { ) mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) if state.GetBalance(from.Address()).Cmp(mgval) < 0 { - return errInsufficientBalanceForGas + return ErrInsufficientFunds } if err := st.gp.SubGas(st.msg.Gas()); err != nil { @@ -236,11 +256,32 @@ func (st *StateTransition) preCheck() error { } // TransitionDb will transition the state by applying the current message and -// returning the result including the the used gas. It returns an error if it -// failed. An error indicates a consensus issue. -func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedGas uint64, failed bool, err error) { - if err = st.preCheck(); err != nil { - return +// returning the evm execution result with following fields. +// +// - used gas: +// total gas used (including gas being refunded) +// - returndata: +// the returned data from evm +// - concrete execution error: +// various **EVM** error which aborts the execution, +// e.g. ErrOutOfGas, ErrExecutionReverted +// +// However if any consensus issue encountered, return the error directly with +// nil evm execution result. +func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult, error) { + // First check this message satisfies all consensus rules before + // applying the message. The rules include these clauses + // + // 1. the nonce of the message caller is correct + // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) + // 3. the amount of gas required is available in the block + // 4. the purchased gas is enough to cover intrinsic usage + // 5. there is no overflow when calculating intrinsic gas + // 6. caller has enough balance to cover asset transfer for **topmost** call + + // Check clauses 1-3, buy gas if everything is correct + if err := st.preCheck(); err != nil { + return nil, err } msg := st.msg sender := st.from() // err checked in preCheck @@ -248,44 +289,35 @@ func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedG homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber) contractCreation := msg.To() == nil - // Pay intrinsic gas + // Check clauses 4-5, subtract intrinsic gas if everything is correct gas, err := IntrinsicGas(st.data, contractCreation, homestead) if err != nil { - return nil, 0, false, err + return nil, err + } + if st.gas < gas { + return nil, ErrIntrinsicGas } - if err = st.useGas(gas); err != nil { - return nil, 0, false, err + st.gas -= gas + + // Check clause 6 + if msg.Value().Sign() > 0 && !st.evm.CanTransfer(st.state, msg.From(), msg.Value()) { + return nil, ErrInsufficientFundsForTransfer } var ( - evm = st.evm - // vm errors do not effect consensus and are therefor - // not assigned to err, except for insufficient balance - // error. - vmerr error + ret []byte + vmerr error // vm errors do not effect consensus and are therefore not assigned to err ) // for debugging purpose // TODO: clean it after fixing the issue https://github.com/tomochain/tomochain/issues/401 - var contractAction string nonce := uint64(1) if contractCreation { - ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) - contractAction = "contract creation" + ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value) } else { // Increment the nonce for the next transaction nonce = st.state.GetNonce(sender.Address()) + 1 st.state.SetNonce(sender.Address(), nonce) - ret, st.gas, vmerr = evm.Call(sender, st.to().Address(), st.data, st.gas, st.value) - contractAction = "contract call" - } - if vmerr != nil { - log.Debug("VM returned with error", "action", contractAction, "contract address", st.to().Address(), "gas", st.gas, "gasPrice", st.gasPrice, "nonce", nonce, "err", vmerr) - // The only possible consensus-error would be if there wasn't - // sufficient balance to make the transfer happen. The first - // balance transfer may never fail. - if vmerr == vm.ErrInsufficientBalance { - return nil, 0, false, vmerr - } + ret, st.gas, vmerr = st.evm.Call(sender, st.to().Address(), st.data, st.gas, st.value) } st.refundGas() @@ -297,7 +329,11 @@ func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedG st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) } - return ret, st.gasUsed(), vmerr != nil, err + return &ExecutionResult{ + UsedGas: st.gasUsed(), + Err: vmerr, + ReturnData: ret, + }, err } //func (st *StateTransition) refundGas() { diff --git a/core/token_validator.go b/core/token_validator.go index 485ff05c59..26bab72c2e 100644 --- a/core/token_validator.go +++ b/core/token_validator.go @@ -85,7 +85,7 @@ func RunContract(chain consensus.ChainContext, statedb *state.StateDB, contractA return unpackResult, nil } -//FIXME: please use copyState for this function +// FIXME: please use copyState for this function // CallContractWithState executes a contract call at the given state. func CallContractWithState(call ethereum.CallMsg, chain consensus.ChainContext, statedb *state.StateDB) ([]byte, error) { // Ensure message is initialized properly. @@ -111,11 +111,11 @@ func CallContractWithState(call ethereum.CallMsg, chain consensus.ChainContext, vmenv := vm.NewEVM(evmContext, statedb, nil, chain.Config(), vm.Config{}) gaspool := new(GasPool).AddGas(1000000) owner := common.Address{} - rval, _, _, err := NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner) + result, err := NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner) if err != nil { return nil, err } - return rval, err + return result.Return(), err } // make sure that balance of token is at slot 0 diff --git a/eth/api_tracer.go b/eth/api_tracer.go index c71dd0e9bf..e2a32b8510 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -475,7 +475,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, vmenv := vm.NewEVM(vmctx, statedb, tomoxState, api.config, vm.Config{}) owner := common.Address{} - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), owner); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), owner); err != nil { failed = err break } @@ -631,7 +631,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v vmenv := vm.NewEVM(vmctx, statedb, nil, api.config, vm.Config{Debug: true, Tracer: tracer}) owner := common.Address{} - ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), owner) + result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), owner) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) } @@ -639,9 +639,9 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v switch tracer := tracer.(type) { case *vm.StructLogger: return ðapi.ExecutionResult{ - Gas: gas, - Failed: failed, - ReturnValue: fmt.Sprintf("%x", ret), + Gas: result.UsedGas, + Failed: result.Failed(), + ReturnValue: fmt.Sprintf("%x", result.Return()), StructLogs: ethapi.FormatLogs(tracer.StructLogs()), }, nil diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 81b8cb6741..e33078c329 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -135,13 +135,13 @@ func TestPrestateTracerCreate2(t *testing.T) { t.Fatalf("err %v", err) } /** - This comes from one of the test-vectors on the Skinny Create2 - EIP + This comes from one of the test-vectors on the Skinny Create2 - EIP - address 0x00000000000000000000000000000000deadbeef - salt 0x00000000000000000000000000000000000000000000000000000000cafebabe - init_code 0xdeadbeef - gas (assuming no mem expansion): 32006 - result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7 + address 0x00000000000000000000000000000000deadbeef + salt 0x00000000000000000000000000000000000000000000000000000000cafebabe + init_code 0xdeadbeef + gas (assuming no mem expansion): 32006 + result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7 */ origin, _ := signer.Sender(tx) context := vm.Context{ @@ -184,7 +184,7 @@ func TestPrestateTracerCreate2(t *testing.T) { t.Fatalf("failed to prepare transaction for tracing: %v", err) } st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) - if _, _, _, err = st.TransitionDb(common.Address{}); err != nil { + if _, err = st.TransitionDb(common.Address{}); err != nil { t.Fatalf("failed to execute transaction: %v", err) } // Retrieve the trace result and compare against the etalon @@ -259,7 +259,7 @@ func TestCallTracer(t *testing.T) { t.Fatalf("failed to prepare transaction for tracing: %v", err) } st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) - if _, _, _, err = st.TransitionDb(common.Address{}); err != nil { + if _, err = st.TransitionDb(common.Address{}); err != nil { t.Fatalf("failed to execute transaction: %v", err) } // Retrieve the trace result and compare against the etalon diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 09d3a401b2..374964c1e4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "github.com/tomochain/tomochain/accounts/abi" "github.com/tomochain/tomochain/tomoxlending/lendingstate" "math/big" "sort" @@ -1025,12 +1026,12 @@ type CallArgs struct { Data *hexutil.Bytes `json:"data"` } -func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) ([]byte, uint64, bool, error) { +func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) statedb, header, err := s.b.StateAndHeaderByNumber(ctx, blockNr) if statedb == nil || err != nil { - return nil, 0, false, err + return nil, err } // Set sender address or use a default if none specified var addr common.Address @@ -1081,20 +1082,20 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr block, err := s.b.BlockByNumber(ctx, blockNr) if err != nil { - return nil, 0, false, err + return nil, err } author, err := s.b.GetEngine().Author(block.Header()) if err != nil { - return nil, 0, false, err + return nil, err } tomoxState, err := s.b.TomoxService().GetTradingState(block, author) if err != nil { - return nil, 0, false, err + return nil, err } // Get a new instance of the EVM. evm, vmError, err := s.b.GetEVM(ctx, msg, statedb, tomoxState, header, vmCfg) if err != nil { - return nil, 0, false, err + return nil, err } // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -1107,18 +1108,55 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr // and apply the message. gp := new(core.GasPool).AddGas(math.MaxUint64) owner := common.Address{} - res, gas, failed, err := core.ApplyMessage(evm, msg, gp, owner) + result, err := core.ApplyMessage(evm, msg, gp, owner) if err := vmError(); err != nil { - return nil, 0, false, err + return nil, err } - return res, gas, failed, err + return result, err +} + +func newRevertError(result *core.ExecutionResult) *revertError { + reason, errUnpack := abi.UnpackRevert(result.Revert()) + err := errors.New("execution reverted") + if errUnpack == nil { + err = fmt.Errorf("execution reverted: %v", reason) + } + return &revertError{ + error: err, + reason: hexutil.Encode(result.Revert()), + } +} + +// revertError is an API error that encompassas an EVM revertal with JSON error +// code and a binary data blob. +type revertError struct { + error + reason string // revert reason hex encoded +} + +// ErrorCode returns the JSON error code for a revertal. +// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal +func (e *revertError) ErrorCode() int { + return 3 +} + +// ErrorData returns the hex encoded revert reason. +func (e *revertError) ErrorData() interface{} { + return e.reason } // Call executes the given transaction on the state for the given block number. // It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { - result, _, _, err := s.doCall(ctx, args, blockNr, vm.Config{}, 5*time.Second) - return (hexutil.Bytes)(result), err + result, err := s.doCall(ctx, args, blockNr, vm.Config{}, 5*time.Second) + if err != nil { + return nil, err + } + // If the result contains a revert reason, try to unpack and return it. + if len(result.Revert()) > 0 { + return nil, newRevertError(result) + } + return result.Return(), result.Err } // EstimateGas returns an estimate of the amount of gas needed to execute the @@ -1146,8 +1184,8 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h executable := func(gas uint64) bool { args.Gas = (*hexutil.Uint64)(&gas) - _, _, failed, err := s.doCall(ctx, args, rpc.LatestBlockNumber, vm.Config{}, 0) - if err != nil || failed { + result, err := s.doCall(ctx, args, rpc.LatestBlockNumber, vm.Config{}, 0) + if err != nil || result.Failed() { return false } return true @@ -1170,12 +1208,12 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h return hexutil.Uint64(hi), nil } -func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) ([]byte, uint64, bool, error) { +func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) state, header, err := b.StateAndHeaderByNumber(ctx, blockNr) if state == nil || err != nil { - return nil, 0, false, err + return nil, err } // Set sender address or use a default if none specified var addr common.Address @@ -1222,21 +1260,21 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb block, err := b.BlockByNumber(ctx, blockNr) if err != nil { - return nil, 0, false, err + return nil, err } author, err := b.GetEngine().Author(block.Header()) if err != nil { - return nil, 0, false, err + return nil, err } tomoxState, err := b.TomoxService().GetTradingState(block, author) if err != nil { - return nil, 0, false, err + return nil, err } // Get a new instance of the EVM. evm, vmError, err := b.GetEVM(ctx, msg, state, tomoxState, header, vmCfg) if err != nil { - return nil, 0, false, err + return nil, err } // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -1249,11 +1287,11 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb // and apply the message. gp := new(core.GasPool).AddGas(math.MaxUint64) owner := common.Address{} - res, gas, failed, err := core.ApplyMessage(evm, msg, gp, owner) + result, err := core.ApplyMessage(evm, msg, gp, owner) if err := vmError(); err != nil { - return nil, 0, false, err + return nil, err } - return res, gas, failed, err + return result, err } func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Uint64, error) { @@ -1281,19 +1319,30 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl } // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) bool { + executable := func(gas uint64) (bool, *core.ExecutionResult, error) { args.Gas = (*hexutil.Uint64)(&gas) - _, _, failed, err := DoCall(ctx, b, args, rpc.LatestBlockNumber, vm.Config{}, 0) - if err != nil || failed { - return false + result, err := DoCall(ctx, b, args, rpc.LatestBlockNumber, vm.Config{}, 0) + if err != nil { + if err == core.ErrIntrinsicGas { + return true, nil, nil // Special case, raise gas limit + } + return true, nil, err // Bail out } - return true + return result.Failed(), result, nil } // Execute the binary search and hone in on an executable gas limit for lo+1 < hi { mid := (hi + lo) / 2 - if !executable(mid) { + failed, _, err := executable(mid) + + // If the error is not nil(consensus error), it means the provided message + // call or transaction will never be accepted no matter how much gas it is + // assigened. Return the error directly, don't struggle any more. + if err != nil { + return 0, err + } + if failed { lo = mid } else { hi = mid @@ -1301,8 +1350,19 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl } // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { - if !executable(hi) { - return 0, fmt.Errorf("gas required exceeds allowance or always failing transaction") + failed, result, err := executable(hi) + if err != nil { + return 0, err + } + if failed { + if result != nil && result.Err != vm.ErrOutOfGas { + if len(result.Revert()) > 0 { + return 0, newRevertError(result) + } + return 0, result.Err + } + // Otherwise, the specified gas cap is too low + return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) } } return hexutil.Uint64(hi), nil diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 9c03e09677..1f4eb7a02e 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -335,16 +335,16 @@ func TestEstimateGas(t *testing.T) { want uint64 }{ // simple transfer on latest block - { - blockNumber: rpc.LatestBlockNumber, - call: CallArgs{ - From: &accounts[0].addr, - To: &accounts[1].addr, - Value: (*hexutil.Big)(big.NewInt(1000)), - }, - expectErr: nil, - want: 21000, - }, + //{ + // blockNumber: rpc.LatestBlockNumber, + // call: CallArgs{ + // From: &accounts[0].addr, + // To: &accounts[1].addr, + // Value: (*hexutil.Big)(big.NewInt(1000)), + // }, + // expectErr: nil, + // want: 21000, + //}, // simple transfer with insufficient funds on latest block { blockNumber: rpc.LatestBlockNumber, @@ -353,25 +353,25 @@ func TestEstimateGas(t *testing.T) { To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), }, - expectErr: core.ErrInsufficientFunds, + expectErr: core.ErrInsufficientFundsForTransfer, want: 21000, }, // empty create - { - blockNumber: rpc.LatestBlockNumber, - call: CallArgs{}, - expectErr: nil, - want: 53000, - }, - { - blockNumber: rpc.LatestBlockNumber, - call: CallArgs{ - From: &randomAccounts[0].addr, - To: &randomAccounts[1].addr, - Value: (*hexutil.Big)(big.NewInt(1000)), - }, - expectErr: core.ErrInsufficientFunds, - }, + //{ + // blockNumber: rpc.LatestBlockNumber, + // call: CallArgs{}, + // expectErr: nil, + // want: 53000, + //}, + //{ + // blockNumber: rpc.LatestBlockNumber, + // call: CallArgs{ + // From: &randomAccounts[0].addr, + // To: &randomAccounts[1].addr, + // Value: (*hexutil.Big)(big.NewInt(1000)), + // }, + // expectErr: core.ErrInsufficientFundsForTransfer, + //}, } for i, tc := range testSuite { result, err := api.NewEstimateGas(context.Background(), tc.call) diff --git a/les/odr_test.go b/les/odr_test.go index 3858e34028..0524e315a7 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -141,8 +141,8 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) owner := common.Address{} - ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp, owner) - res = append(res, ret...) + result, _ := core.ApplyMessage(vmenv, msg, gp, owner) + res = append(res, result.Return()...) } } else { header := lc.GetHeaderByHash(bhash) @@ -158,9 +158,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai vmenv := vm.NewEVM(context, statedb, nil, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) owner := common.Address{} - ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp, owner) + result, _ := core.ApplyMessage(vmenv, msg, gp, owner) if statedb.Error() == nil { - res = append(res, ret...) + res = append(res, result.Return()...) } } } diff --git a/light/odr_test.go b/light/odr_test.go index 0c5fc78573..aa1d45690d 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -188,8 +188,8 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain vmenv := vm.NewEVM(context, st, nil, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) owner := common.Address{} - ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp, owner) - res = append(res, ret...) + result, _ := core.ApplyMessage(vmenv, msg, gp, owner) + res = append(res, result.Return()...) if st.Error() != nil { return res, st.Error() } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index e532aa8a46..6360457689 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -144,7 +144,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateD snapshot := statedb.Snapshot() coinbase := &t.json.Env.Coinbase - if _, _, _, err := core.ApplyMessage(evm, msg, gaspool, *coinbase); err != nil { + if _, err := core.ApplyMessage(evm, msg, gaspool, *coinbase); err != nil { statedb.RevertToSnapshot(snapshot) } if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) { From 876f2d5615c3cf805fd514894d7548f8cbd0edce Mon Sep 17 00:00:00 2001 From: tristan98 Date: Fri, 4 Oct 2024 02:17:10 +0700 Subject: [PATCH 07/16] feat: PR #21043 recap highest gas limit with account's balance --- accounts/abi/bind/backends/simulated.go | 32 +++++++- accounts/abi/bind/backends/simulated_test.go | 80 ++++++++++++++++++++ internal/ethapi/api.go | 25 ++++++ internal/ethapi/api_test.go | 51 ++++++------- 4 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 accounts/abi/bind/backends/simulated_test.go diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 022c8197ba..1ab9261766 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -24,6 +24,7 @@ import ( "github.com/tomochain/tomochain/common/hexutil" "github.com/tomochain/tomochain/consensus" "github.com/tomochain/tomochain/core/rawdb" + "github.com/tomochain/tomochain/log" "math/big" "sync" "time" @@ -84,6 +85,12 @@ func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend { return backend } +// Close terminates the underlying blockchain's update loop. +func (b *SimulatedBackend) Close() error { + b.blockchain.Stop() + return nil +} + // Commit imports all the pending transactions as a single block and starts a // fresh new state. func (b *SimulatedBackend) Commit() { @@ -320,6 +327,29 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call tomochain.CallM } else { hi = b.pendingBlock.GasLimit() } + + // Recap the highest gas allowance with account's balance. + if call.GasPrice != nil && call.GasPrice.Uint64() != 0 { + balance := b.pendingState.GetBalance(call.From) // from can't be nil + available := new(big.Int).Set(balance) + if call.Value != nil { + if call.Value.Cmp(available) >= 0 { + return 0, errors.New("insufficient funds for transfer") + } + available.Sub(available, call.Value) + } + allowance := new(big.Int).Div(available, call.GasPrice) + if hi > allowance.Uint64() { + transfer := call.Value + if transfer == nil { + transfer = new(big.Int) + } + log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, + "sent", transfer, "gasprice", call.GasPrice, "fundable", allowance) + hi = allowance.Uint64() + } + } + cap = hi // Create a helper to check if a gas allowance results in an executable transaction @@ -369,7 +399,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call tomochain.CallM return 0, result.Err } // Otherwise, the specified gas cap is too low - return 0, result.Err + return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) } } return hi, nil diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go new file mode 100644 index 0000000000..6b403dabb6 --- /dev/null +++ b/accounts/abi/bind/backends/simulated_test.go @@ -0,0 +1,80 @@ +package backends + +import ( + "context" + "errors" + ethereum "github.com/tomochain/tomochain" + "github.com/tomochain/tomochain/common" + "github.com/tomochain/tomochain/core" + "github.com/tomochain/tomochain/crypto" + "github.com/tomochain/tomochain/params" + "math/big" + "testing" +) + +func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + + sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}) + defer sim.Close() + + receipant := common.HexToAddress("deadbeef") + var cases = []struct { + name string + message ethereum.CallMsg + expect uint64 + expectError error + }{ + {"EstimateWithoutPrice", ethereum.CallMsg{ + From: addr, + To: &receipant, + Gas: 0, + GasPrice: big.NewInt(0), + Value: big.NewInt(1000), + Data: nil, + }, 21000, nil}, + + {"EstimateWithPrice", ethereum.CallMsg{ + From: addr, + To: &receipant, + Gas: 0, + GasPrice: big.NewInt(1000), + Value: big.NewInt(1000), + Data: nil, + }, 21000, nil}, + + {"EstimateWithVeryHighPrice", ethereum.CallMsg{ + From: addr, + To: &receipant, + Gas: 0, + GasPrice: big.NewInt(1e14), // gascost = 2.1ether + Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether + Data: nil, + }, 21000, nil}, + + {"EstimateWithSuperhighPrice", ethereum.CallMsg{ + From: addr, + To: &receipant, + Gas: 0, + GasPrice: big.NewInt(2e14), // gascost = 4.2ether + Value: big.NewInt(1000), + Data: nil, + }, 21000, errors.New("gas required exceeds allowance (10999)")}, // 10999=(2.2ether-1000wei)/(2e14) + } + for _, c := range cases { + got, err := sim.EstimateGas(context.Background(), c.message) + if c.expectError != nil { + if err == nil { + t.Fatalf("Expect error, got nil") + } + if c.expectError.Error() != err.Error() { + t.Fatalf("Expect error, want %v, got %v", c.expectError, err) + } + continue + } + if got != c.expect { + t.Fatalf("Gas estimation mismatch, want %d, got %d", c.expect, got) + } + } +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 374964c1e4..93aa1630d8 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1311,6 +1311,31 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl } hi = block.GasLimit() } + // Recap the highest gas limit with account's available balance. + if args.GasPrice != nil && args.GasPrice.ToInt().Uint64() != 0 { + state, _, err := b.StateAndHeaderByNumber(ctx, blockNr) + if err != nil { + return 0, err + } + balance := state.GetBalance(*args.From) // from can't be nil + available := new(big.Int).Set(balance) + if args.Value != nil { + if args.Value.ToInt().Cmp(available) >= 0 { + return 0, errors.New("insufficient funds for transfer") + } + available.Sub(available, args.Value.ToInt()) + } + allowance := new(big.Int).Div(available, args.GasPrice.ToInt()) + if hi > allowance.Uint64() { + transfer := args.Value + if transfer == nil { + transfer = new(hexutil.Big) + } + log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, + "sent", transfer.ToInt(), "gasprice", args.GasPrice.ToInt(), "fundable", allowance) + hi = allowance.Uint64() + } + } cap = hi // Use zero address if sender unspecified. diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 1f4eb7a02e..64e311d0e2 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -335,16 +335,16 @@ func TestEstimateGas(t *testing.T) { want uint64 }{ // simple transfer on latest block - //{ - // blockNumber: rpc.LatestBlockNumber, - // call: CallArgs{ - // From: &accounts[0].addr, - // To: &accounts[1].addr, - // Value: (*hexutil.Big)(big.NewInt(1000)), - // }, - // expectErr: nil, - // want: 21000, - //}, + { + blockNumber: rpc.LatestBlockNumber, + call: CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + expectErr: nil, + want: 21000, + }, // simple transfer with insufficient funds on latest block { blockNumber: rpc.LatestBlockNumber, @@ -357,25 +357,24 @@ func TestEstimateGas(t *testing.T) { want: 21000, }, // empty create - //{ - // blockNumber: rpc.LatestBlockNumber, - // call: CallArgs{}, - // expectErr: nil, - // want: 53000, - //}, - //{ - // blockNumber: rpc.LatestBlockNumber, - // call: CallArgs{ - // From: &randomAccounts[0].addr, - // To: &randomAccounts[1].addr, - // Value: (*hexutil.Big)(big.NewInt(1000)), - // }, - // expectErr: core.ErrInsufficientFundsForTransfer, - //}, + { + blockNumber: rpc.LatestBlockNumber, + call: CallArgs{}, + expectErr: nil, + want: 53000, + }, + { + blockNumber: rpc.LatestBlockNumber, + call: CallArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + expectErr: core.ErrInsufficientFundsForTransfer, + }, } for i, tc := range testSuite { result, err := api.NewEstimateGas(context.Background(), tc.call) - fmt.Println("TestEstimateGas Gas estimation result:", result) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) From 09f740f0cffc9adce451d8c7c4b3cd008f0b9c99 Mon Sep 17 00:00:00 2001 From: tristan98 Date: Fri, 4 Oct 2024 02:21:53 +0700 Subject: [PATCH 08/16] feat: PR #21346 add more condition check prevent unnecessary failures during gas estimation --- accounts/abi/bind/backends/simulated.go | 6 ++++-- internal/ethapi/api.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 1ab9261766..436563cb54 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -329,7 +329,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call tomochain.CallM } // Recap the highest gas allowance with account's balance. - if call.GasPrice != nil && call.GasPrice.Uint64() != 0 { + if call.GasPrice != nil && call.GasPrice.BitLen() != 0 { balance := b.pendingState.GetBalance(call.From) // from can't be nil available := new(big.Int).Set(balance) if call.Value != nil { @@ -339,7 +339,9 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call tomochain.CallM available.Sub(available, call.Value) } allowance := new(big.Int).Div(available, call.GasPrice) - if hi > allowance.Uint64() { + + // If the allowance is larger than maximum uint64, skip checking + if allowance.IsUint64() && hi > allowance.Uint64() { transfer := call.Value if transfer == nil { transfer = new(big.Int) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 93aa1630d8..f82fe4e5d0 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1312,7 +1312,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl hi = block.GasLimit() } // Recap the highest gas limit with account's available balance. - if args.GasPrice != nil && args.GasPrice.ToInt().Uint64() != 0 { + if args.GasPrice != nil && args.GasPrice.ToInt().BitLen() != 0 { state, _, err := b.StateAndHeaderByNumber(ctx, blockNr) if err != nil { return 0, err @@ -1326,7 +1326,9 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl available.Sub(available, args.Value.ToInt()) } allowance := new(big.Int).Div(available, args.GasPrice.ToInt()) - if hi > allowance.Uint64() { + + // If the allowance is larger than maximum uint64, skip checking + if allowance.IsUint64() && hi > allowance.Uint64() { transfer := args.Value if transfer == nil { transfer = new(hexutil.Big) From 419e7fc872c62a6a04c4ff4cfa44f7c9d0ddd527 Mon Sep 17 00:00:00 2001 From: tristan98 Date: Mon, 7 Oct 2024 10:47:05 +0700 Subject: [PATCH 09/16] refactor: remove gas price and gas limit in validator call message --- accounts/abi/bind/base.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index caf1640496..41284e3342 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -128,7 +128,8 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, return err } var ( - msg = tomochain.CallMsg{From: opts.From, To: &c.address, Data: input, GasPrice: common.MinGasPrice, Gas: uint64(4200000)} + //msg = tomochain.CallMsg{From: opts.From, To: &c.address, Data: input, GasPrice: common.MinGasPrice, Gas: uint64(4200000)} + msg = tomochain.CallMsg{From: opts.From, To: &c.address, Data: input} ctx = ensureContext(opts.Context) code []byte output []byte @@ -270,7 +271,7 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int config.ToBlock = new(big.Int).SetUint64(*opts.End) } /* TODO(karalabe): Replace the rest of the method below with this when supported - sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) + sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) */ buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config) if err != nil { From 7f89afbb9900edadd35ed70a11cc413b9e4025a7 Mon Sep 17 00:00:00 2001 From: tristan98 Date: Mon, 7 Oct 2024 11:03:06 +0700 Subject: [PATCH 10/16] feat: PR #21545, #24363: EstimateGas function add block number parameter and use LatestBlockNumber by default --- internal/ethapi/api.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index f82fe4e5d0..fcc2b88d5f 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1321,7 +1321,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl available := new(big.Int).Set(balance) if args.Value != nil { if args.Value.ToInt().Cmp(available) >= 0 { - return 0, errors.New("insufficient funds for transfer") + return 0, core.ErrInsufficientFundsForTransfer } available.Sub(available, args.Value.ToInt()) } @@ -1397,8 +1397,12 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl // NewEstimateGas returns an estimate of the amount of gas needed to execute the // given transaction against the current pending block. -func (s *PublicBlockChainAPI) NewEstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { - return DoEstimateGas(ctx, s.b, args, rpc.LatestBlockNumber) +func (s *PublicBlockChainAPI) NewEstimateGas(ctx context.Context, args CallArgs, blockNr *rpc.BlockNumber) (hexutil.Uint64, error) { + bNr := rpc.BlockNumber(rpc.LatestBlockNumber) + if blockNr != nil { + bNr = *blockNr + } + return DoEstimateGas(ctx, s.b, args, bNr) } // ExecutionResult groups all structured logs emitted by the EVM From 33c58adf28ab64399b12118d349bd40cc573e0af Mon Sep 17 00:00:00 2001 From: tristan98 Date: Mon, 7 Oct 2024 16:00:51 +0700 Subject: [PATCH 11/16] refactor: PR #27505: Each binary search call retrieve new copy of the state Remove old EstimateGas function Fix unit test --- internal/ethapi/api.go | 163 ++++-------------------------------- internal/ethapi/api_test.go | 7 +- 2 files changed, 20 insertions(+), 150 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index fcc2b88d5f..1db5793eff 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1026,95 +1026,6 @@ type CallArgs struct { Data *hexutil.Bytes `json:"data"` } -func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) (*core.ExecutionResult, error) { - defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) - - statedb, header, err := s.b.StateAndHeaderByNumber(ctx, blockNr) - if statedb == nil || err != nil { - return nil, err - } - // Set sender address or use a default if none specified - var addr common.Address - if args.From == nil { - if wallets := s.b.AccountManager().Wallets(); len(wallets) > 0 { - if accounts := wallets[0].Accounts(); len(accounts) > 0 { - addr = accounts[0].Address - } - } - } else { - addr = *args.From - } - // Set default gas & gas price if none were set - gas := uint64(math.MaxUint64 / 2) - if args.Gas != nil { - gas = uint64(*args.Gas) - } - gasPrice := new(big.Int) - if args.GasPrice != nil { - gasPrice = args.GasPrice.ToInt() - } - - value := new(big.Int) - if args.Value != nil { - value = args.Value.ToInt() - } - - var data []byte - if args.Data != nil { - data = []byte(*args.Data) - } - balanceTokenFee := big.NewInt(0).SetUint64(gas) - balanceTokenFee = balanceTokenFee.Mul(balanceTokenFee, gasPrice) - // Create new call message - msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false, balanceTokenFee) - - // Setup context so it may be cancelled the call has completed - // or, in case of unmetered gas, setup a context with a timeout. - var cancel context.CancelFunc - if timeout > 0 { - ctx, cancel = context.WithTimeout(ctx, timeout) - } else { - ctx, cancel = context.WithCancel(ctx) - } - // Make sure the context is cancelled when the call has completed - // this makes sure resources are cleaned up. - defer cancel() - - block, err := s.b.BlockByNumber(ctx, blockNr) - if err != nil { - return nil, err - } - author, err := s.b.GetEngine().Author(block.Header()) - if err != nil { - return nil, err - } - tomoxState, err := s.b.TomoxService().GetTradingState(block, author) - if err != nil { - return nil, err - } - // Get a new instance of the EVM. - evm, vmError, err := s.b.GetEVM(ctx, msg, statedb, tomoxState, header, vmCfg) - if err != nil { - return nil, err - } - // Wait for the context to be done and cancel the evm. Even if the - // EVM has finished, cancelling may be done (repeatedly) - go func() { - <-ctx.Done() - evm.Cancel() - }() - - // Setup the gas pool (also for unmetered requests) - // and apply the message. - gp := new(core.GasPool).AddGas(math.MaxUint64) - owner := common.Address{} - result, err := core.ApplyMessage(evm, msg, gp, owner) - if err := vmError(); err != nil { - return nil, err - } - return result, err -} - func newRevertError(result *core.ExecutionResult) *revertError { reason, errUnpack := abi.UnpackRevert(result.Revert()) err := errors.New("execution reverted") @@ -1148,7 +1059,7 @@ func (e *revertError) ErrorData() interface{} { // Call executes the given transaction on the state for the given block number. // It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { - result, err := s.doCall(ctx, args, blockNr, vm.Config{}, 5*time.Second) + result, err := DoCall(ctx, s.b, args, blockNr, vm.Config{}, 5*time.Second) if err != nil { return nil, err } @@ -1159,55 +1070,6 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr r return result.Return(), result.Err } -// EstimateGas returns an estimate of the amount of gas needed to execute the -// given transaction against the current pending block. -func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { - // Binary search the gas requirement, as it may be higher than the amount used - var ( - lo uint64 = params.TxGas - 1 - hi uint64 - cap uint64 - ) - if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { - hi = uint64(*args.Gas) - } else { - // Retrieve the current pending block to act as the gas ceiling - block, err := s.b.BlockByNumber(ctx, rpc.LatestBlockNumber) - if err != nil { - return 0, err - } - hi = block.GasLimit() - } - cap = hi - - // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) bool { - args.Gas = (*hexutil.Uint64)(&gas) - - result, err := s.doCall(ctx, args, rpc.LatestBlockNumber, vm.Config{}, 0) - if err != nil || result.Failed() { - return false - } - return true - } - // Execute the binary search and hone in on an executable gas limit - for lo+1 < hi { - mid := (hi + lo) / 2 - if !executable(mid) { - lo = mid - } else { - hi = mid - } - } - // Reject the transaction as invalid if it still fails at the highest allowance - if hi == cap { - if !executable(hi) { - return 0, fmt.Errorf("gas required exceeds allowance or always failing transaction") - } - } - return hexutil.Uint64(hi), nil -} - func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) @@ -1215,6 +1077,11 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb if state == nil || err != nil { return nil, err } + + return doCall(ctx, b, args, state, header, vmCfg, timeout) +} + +func doCall(ctx context.Context, b Backend, args CallArgs, state *state.StateDB, header *types.Header, vmCfg vm.Config, timeout time.Duration) (*core.ExecutionResult, error) { // Set sender address or use a default if none specified var addr common.Address if args.From != nil { @@ -1258,7 +1125,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb // this makes sure resources are cleaned up. defer cancel() - block, err := b.BlockByNumber(ctx, blockNr) + block, err := b.BlockByNumber(ctx, rpc.BlockNumber(header.Number.Int64())) if err != nil { return nil, err } @@ -1346,10 +1213,10 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl } // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) (bool, *core.ExecutionResult, error) { + executable := func(gas uint64, state *state.StateDB, header *types.Header) (bool, *core.ExecutionResult, error) { args.Gas = (*hexutil.Uint64)(&gas) - result, err := DoCall(ctx, b, args, rpc.LatestBlockNumber, vm.Config{}, 0) + result, err := doCall(ctx, b, args, state, header, vm.Config{}, 0) if err != nil { if err == core.ErrIntrinsicGas { return true, nil, nil // Special case, raise gas limit @@ -1358,10 +1225,16 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl } return result.Failed(), result, nil } + + state, header, err := b.StateAndHeaderByNumber(ctx, blockNr) + if state == nil || err != nil { + return 0, err + } // Execute the binary search and hone in on an executable gas limit for lo+1 < hi { + s := state.Copy() mid := (hi + lo) / 2 - failed, _, err := executable(mid) + failed, _, err := executable(mid, s, header) // If the error is not nil(consensus error), it means the provided message // call or transaction will never be accepted no matter how much gas it is @@ -1377,7 +1250,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl } // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { - failed, result, err := executable(hi) + failed, result, err := executable(hi, state, header) if err != nil { return 0, err } @@ -1397,7 +1270,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl // NewEstimateGas returns an estimate of the amount of gas needed to execute the // given transaction against the current pending block. -func (s *PublicBlockChainAPI) NewEstimateGas(ctx context.Context, args CallArgs, blockNr *rpc.BlockNumber) (hexutil.Uint64, error) { +func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs, blockNr *rpc.BlockNumber) (hexutil.Uint64, error) { bNr := rpc.BlockNumber(rpc.LatestBlockNumber) if blockNr != nil { bNr = *blockNr diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 64e311d0e2..06c64b1aba 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -4,7 +4,6 @@ import ( "context" "crypto/ecdsa" "errors" - "fmt" "github.com/tomochain/tomochain/accounts" "github.com/tomochain/tomochain/common" "github.com/tomochain/tomochain/common/hexutil" @@ -325,9 +324,7 @@ func TestEstimateGas(t *testing.T) { } b.AddTx(tx) })) - blockNumber := rpc.LatestBlockNumber - balance, _ := api.GetBalance(context.Background(), randomAccounts[0].addr, blockNumber) - fmt.Println("Random account address:", randomAccounts[0].addr.Hex(), "Balance", balance) + var testSuite = []struct { blockNumber rpc.BlockNumber call CallArgs @@ -374,7 +371,7 @@ func TestEstimateGas(t *testing.T) { }, } for i, tc := range testSuite { - result, err := api.NewEstimateGas(context.Background(), tc.call) + result, err := api.EstimateGas(context.Background(), tc.call, &tc.blockNumber) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) From e3d8644bed0c982e0c39c1807bba73cd33dd8530 Mon Sep 17 00:00:00 2001 From: tristan98 Date: Mon, 7 Oct 2024 17:38:40 +0700 Subject: [PATCH 12/16] refactor: PR #27710: Optimize and clean EstimateGas function --- internal/ethapi/api.go | 114 +++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 51 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 1db5793eff..4bfb5a2c71 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1161,13 +1161,37 @@ func doCall(ctx context.Context, b Backend, args CallArgs, state *state.StateDB, return result, err } +// executeEstimate is a helper that executes the transaction under a given gas limit and returns +// true if the transaction fails for a reason that might be related to not enough gas. A non-nil +// error means execution failed due to reasons unrelated to the gas limit. +func executeEstimate(ctx context.Context, b Backend, args CallArgs, state *state.StateDB, header *types.Header, gasLimit uint64) (bool, *core.ExecutionResult, error) { + args.Gas = (*hexutil.Uint64)(&gasLimit) + result, err := doCall(ctx, b, args, state, header, vm.Config{}, 0) + if err != nil { + if errors.Is(err, core.ErrIntrinsicGas) { + return true, nil, nil // Special case, raise gas limit + } + return true, nil, err // Bail out + } + return result.Failed(), result, nil +} + +// DoEstimateGas returns the lowest possible gas limit that allows the transaction to run +// successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if +// there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil & +// non-zero) and `gasCap` (if non-zero). func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Uint64, error) { - // Binary search the gas requirement, as it may be higher than the amount used + // Binary search the gas limit, as it may need to be higher than the amount used var ( - lo uint64 = params.TxGas - 1 - hi uint64 - cap uint64 + lo uint64 // lowest-known gas limit where tx execution fails + hi uint64 // lowest-known gas limit where tx execution succeeds ) + + // Use zero address if sender unspecified. + if args.From == nil { + args.From = new(common.Address) + } + if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { hi = uint64(*args.Gas) } else { @@ -1178,12 +1202,14 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl } hi = block.GasLimit() } + + state, header, err := b.StateAndHeaderByNumber(ctx, blockNr) + if state == nil || err != nil { + return 0, err + } + // Recap the highest gas limit with account's available balance. if args.GasPrice != nil && args.GasPrice.ToInt().BitLen() != 0 { - state, _, err := b.StateAndHeaderByNumber(ctx, blockNr) - if err != nil { - return 0, err - } balance := state.GetBalance(*args.From) // from can't be nil available := new(big.Int).Set(balance) if args.Value != nil { @@ -1205,41 +1231,43 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl hi = allowance.Uint64() } } - cap = hi - - // Use zero address if sender unspecified. - if args.From == nil { - args.From = new(common.Address) - } - // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64, state *state.StateDB, header *types.Header) (bool, *core.ExecutionResult, error) { - args.Gas = (*hexutil.Uint64)(&gas) + // We first execute the transaction at the highest allowable gas limit, since if this fails we + // can return error immediately. + failed, result, err := executeEstimate(ctx, b, args, state.Copy(), header, hi) - result, err := doCall(ctx, b, args, state, header, vm.Config{}, 0) - if err != nil { - if err == core.ErrIntrinsicGas { - return true, nil, nil // Special case, raise gas limit + if err != nil { + return 0, err + } + if failed { + if result != nil && result.Err != vm.ErrOutOfGas { + if len(result.Revert()) > 0 { + return 0, newRevertError(result) } - return true, nil, err // Bail out + return 0, result.Err } - return result.Failed(), result, nil + return 0, fmt.Errorf("gas required exceeds allowance (%d)", hi) } + // For almost any transaction, the gas consumed by the unconstrained execution above + // lower-bounds the gas limit required for it to succeed. One exception is those txs that + // explicitly check gas remaining in order to successfully execute within a given limit, but we + // probably don't want to return a lowest possible gas limit for these cases anyway. + lo = result.UsedGas - 1 - state, header, err := b.StateAndHeaderByNumber(ctx, blockNr) - if state == nil || err != nil { - return 0, err - } - // Execute the binary search and hone in on an executable gas limit + // Binary search for the smallest gas limit that allows the tx to execute successfully. for lo+1 < hi { - s := state.Copy() mid := (hi + lo) / 2 - failed, _, err := executable(mid, s, header) - - // If the error is not nil(consensus error), it means the provided message - // call or transaction will never be accepted no matter how much gas it is - // assigened. Return the error directly, don't struggle any more. + if mid > lo*2 { + // Most txs don't need much higher gas limit than their gas used, and most txs don't + // require near the full block limit of gas, so the selection of where to bisect the + // range here is skewed to favor the low side. + mid = lo * 2 + } + failed, _, err = executeEstimate(ctx, b, args, state.Copy(), header, mid) if err != nil { + // This should not happen under normal conditions since if we make it this far the + // transaction had run without error at least once before. + log.Error("execution error in estimate gas", "err", err) return 0, err } if failed { @@ -1248,23 +1276,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl hi = mid } } - // Reject the transaction as invalid if it still fails at the highest allowance - if hi == cap { - failed, result, err := executable(hi, state, header) - if err != nil { - return 0, err - } - if failed { - if result != nil && result.Err != vm.ErrOutOfGas { - if len(result.Revert()) > 0 { - return 0, newRevertError(result) - } - return 0, result.Err - } - // Otherwise, the specified gas cap is too low - return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) - } - } + return hexutil.Uint64(hi), nil } From 7765eed71d899812112a38193ee929f0a1cbc9e7 Mon Sep 17 00:00:00 2001 From: c98tristan Date: Fri, 11 Oct 2024 01:58:12 +0700 Subject: [PATCH 13/16] feat: add balanceTokenFee logic, remove comment code --- accounts/abi/bind/base.go | 1 - core/state_transition.go | 44 +++++++++++---------------------------- core/types/transaction.go | 3 --- internal/ethapi/api.go | 13 ++++++++---- 4 files changed, 21 insertions(+), 40 deletions(-) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 41284e3342..cb745d597c 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -128,7 +128,6 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, return err } var ( - //msg = tomochain.CallMsg{From: opts.From, To: &c.address, Data: input, GasPrice: common.MinGasPrice, Gas: uint64(4200000)} msg = tomochain.CallMsg{From: opts.From, To: &c.address, Data: input} ctx = ensureContext(opts.Context) code []byte diff --git a/core/state_transition.go b/core/state_transition.go index 7ecc68a03b..cd5fb5399a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -192,50 +192,30 @@ func (st *StateTransition) to() vm.AccountRef { return reference } -//func (st *StateTransition) buyGas() error { -// var ( -// state = st.state -// balanceTokenFee = st.balanceTokenFee() -// from = st.from() -// ) -// mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) -// if balanceTokenFee == nil { -// if state.GetBalance(from.Address()).Cmp(mgval) < 0 { -// return errInsufficientBalanceForGas -// } -// } else if balanceTokenFee.Cmp(mgval) < 0 { -// return errInsufficientBalanceForGas -// } -// if err := st.gp.SubGas(st.msg.Gas()); err != nil { -// return err -// } -// st.gas += st.msg.Gas() -// -// st.initialGas = st.msg.Gas() -// if balanceTokenFee == nil { -// state.SubBalance(from.Address(), mgval) -// } -// return nil -//} - func (st *StateTransition) buyGas() error { var ( - state = st.state - from = st.from() + state = st.state + balanceTokenFee = st.balanceTokenFee() + from = st.from() ) mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) - if state.GetBalance(from.Address()).Cmp(mgval) < 0 { + + if balanceTokenFee == nil { + if state.GetBalance(from.Address()).Cmp(mgval) < 0 { + return ErrInsufficientFunds + } + } else if balanceTokenFee.Cmp(mgval) < 0 { return ErrInsufficientFunds } - if err := st.gp.SubGas(st.msg.Gas()); err != nil { return err } st.gas += st.msg.Gas() st.initialGas = st.msg.Gas() - state.SubBalance(from.Address(), mgval) - + if balanceTokenFee == nil { + state.SubBalance(from.Address(), mgval) + } return nil } diff --git a/core/types/transaction.go b/core/types/transaction.go index f32a251f35..2414913946 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -689,9 +689,6 @@ type Message struct { } func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool, balanceTokenFee *big.Int) Message { - //if balanceTokenFee != nil { - // gasPrice = common.TRC21GasPrice - //} return Message{ from: from, to: to, diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 4bfb5a2c71..34927df7dd 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1081,7 +1081,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb return doCall(ctx, b, args, state, header, vmCfg, timeout) } -func doCall(ctx context.Context, b Backend, args CallArgs, state *state.StateDB, header *types.Header, vmCfg vm.Config, timeout time.Duration) (*core.ExecutionResult, error) { +func doCall(ctx context.Context, b Backend, args CallArgs, st *state.StateDB, header *types.Header, vmCfg vm.Config, timeout time.Duration) (*core.ExecutionResult, error) { // Set sender address or use a default if none specified var addr common.Address if args.From != nil { @@ -1107,8 +1107,13 @@ func doCall(ctx context.Context, b Backend, args CallArgs, state *state.StateDB, data = []byte(*args.Data) } - balanceTokenFee := big.NewInt(0).SetUint64(gas) - balanceTokenFee = balanceTokenFee.Mul(balanceTokenFee, gasPrice) + var balanceTokenFee *big.Int + feeCapacity := state.GetTRC21FeeCapacityFromState(st) + if args.To != nil { + if value, ok := feeCapacity[*args.To]; ok { + balanceTokenFee = new(big.Int).Set(value) + } + } // Create new call message msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false, balanceTokenFee) @@ -1139,7 +1144,7 @@ func doCall(ctx context.Context, b Backend, args CallArgs, state *state.StateDB, } // Get a new instance of the EVM. - evm, vmError, err := b.GetEVM(ctx, msg, state, tomoxState, header, vmCfg) + evm, vmError, err := b.GetEVM(ctx, msg, st, tomoxState, header, vmCfg) if err != nil { return nil, err } From 1bc0cc5d8e7a2d240b756e6f28be25320df776ff Mon Sep 17 00:00:00 2001 From: tristan98 Date: Mon, 14 Oct 2024 14:17:00 +0700 Subject: [PATCH 14/16] fix: refundGas if balanceTokenFee == nil --- core/state_transition.go | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index cd5fb5399a..398da290ac 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -316,26 +316,6 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult, }, err } -//func (st *StateTransition) refundGas() { -// // Apply refund counter, capped to half of the used gas. -// refund := st.gasUsed() / 2 -// if refund > st.state.GetRefund() { -// refund = st.state.GetRefund() -// } -// st.gas += refund -// -// balanceTokenFee := st.balanceTokenFee() -// if balanceTokenFee == nil { -// from := st.from() -// // Return ETH for remaining gas, exchanged at the original rate. -// remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) -// st.state.AddBalance(from.Address(), remaining) -// } -// // Also return remaining gas to the block gas counter so it is -// // available for the next transaction. -// st.gp.AddGas(st.gas) -//} - func (st *StateTransition) refundGas() { // Apply refund counter, capped to half of the used gas. refund := st.gasUsed() / 2 @@ -344,11 +324,13 @@ func (st *StateTransition) refundGas() { } st.gas += refund - from := st.from() - // Return ETH for remaining gas, exchanged at the original rate. - remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) - st.state.AddBalance(from.Address(), remaining) - + balanceTokenFee := st.balanceTokenFee() + if balanceTokenFee == nil { + from := st.from() + // Return ETH for remaining gas, exchanged at the original rate. + remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) + st.state.AddBalance(from.Address(), remaining) + } // Also return remaining gas to the block gas counter so it is // available for the next transaction. st.gp.AddGas(st.gas) From 2f1435d40a5638208987e1581fbb33505476c9cb Mon Sep 17 00:00:00 2001 From: tristan98 Date: Tue, 22 Oct 2024 01:57:13 +0700 Subject: [PATCH 15/16] chore: integration test free gas transaction --- internal/ethapi/api_test.go | 110 ++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 06c64b1aba..3b6a6c664c 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -4,11 +4,14 @@ import ( "context" "crypto/ecdsa" "errors" + "fmt" "github.com/tomochain/tomochain/accounts" + "github.com/tomochain/tomochain/accounts/abi/bind" "github.com/tomochain/tomochain/common" "github.com/tomochain/tomochain/common/hexutil" "github.com/tomochain/tomochain/consensus" "github.com/tomochain/tomochain/consensus/ethash" + "github.com/tomochain/tomochain/contracts/trc21issuer" "github.com/tomochain/tomochain/core" "github.com/tomochain/tomochain/core/state" "github.com/tomochain/tomochain/core/types" @@ -391,3 +394,110 @@ func TestEstimateGas(t *testing.T) { } } } + +func TestTRC21(t *testing.T) { + // Initialize test accounts + testPriKey, _ := crypto.HexToECDSA("0d782c534042ab93092d1baaf188e041ae429ca27d28d1a0d2ded2d3dd04c717") + testAddr := crypto.PubkeyToAddress(testPriKey.PublicKey) + fmt.Println("Public key: ", testAddr.String()) // 0x5C845F19EB923eEE213b620c12cc6D1d4E6E3506 + + client, err := ethclient.Dial("http://127.0.0.1:8547") + if err != nil { + t.Fatal("Can't connect to RPC server: %", err) + } + + nonce, _ := client.NonceAt(context.Background(), testAddr, nil) + fmt.Println("Nonce", nonce) + + // Setup transactOpts + auth := bind.NewKeyedTransactor(testPriKey) + auth.Nonce = big.NewInt(int64(nonce)) + auth.Value = big.NewInt(0) // in wei + + // Deploy TRC21 + trc21Addr, trc21Instance, err := trc21issuer.DeployTRC21(auth, client, "Viction", "VIC", 18, big.NewInt(1000000000000000000), big.NewInt(0)) + if err != nil { + t.Fatal("Can't deploy TRC21: ", err) + } + fmt.Println("TRC21 address: ", trc21Addr.String()) + time.Sleep(10 * time.Second) + + // Get TRC21 name + name, err := trc21Instance.Name() + if err != nil { + t.Fatal("Can't get name of TRC21: ", err) + } + fmt.Println("TRC21 name: ", name) + + // Attach TRC21Issuer to TRC21Issuer address + trc21issuerAddr := common.TRC21IssuerSMC + trc21issuerInstance, _ := trc21issuer.NewTRC21Issuer(auth, trc21issuerAddr, client) + trc21IssuerMincap, err := trc21issuerInstance.MinCap() + if err != nil { + t.Fatal("Can't get min cap of trc21 issuer smart contract:", err) + } + fmt.Println("TRC21 Issuer min cap: ", trc21IssuerMincap) + + // Apply TRC21 issuer + trc21issuerInstance.TransactOpts.Nonce = big.NewInt(int64(nonce + 1)) + trc21issuerInstance.TransactOpts.Value = new(big.Int).SetUint64(10000000000000000000) + applyTx, err := trc21issuerInstance.Apply(trc21Addr) + if err != nil { + t.Fatal("Can't Apply free gas for token: ", err) + } + fmt.Println("Apply TRC21Issuer transaction: ", applyTx.Hash().Hex()) + time.Sleep(10 * time.Second) + applyTxReceipt, err := client.TransactionReceipt(context.Background(), applyTx.Hash()) + if err != nil { + t.Fatal("Can't get transaction receipt: ", err) + } + fmt.Println("Transaction receipt: ", applyTxReceipt) + + // Get balance token + balanceBefore, err := trc21issuerInstance.GetTokenCapacity(trc21Addr) + if err != nil { + t.Fatal("Can't get token capacity of trc21 issuer smart contract:", err) + } + fmt.Println("TRC21 Issuer token capacity: ", balanceBefore) + + // Get test account balance + testAccountBalanceBefore, err := client.BalanceAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatal("Can't get balance of test account: ", err) + } + + // Transfer token to another address + trc21Instance.TransactOpts.Nonce = big.NewInt(int64(nonce + 2)) + transferTx, err := trc21Instance.Transfer(common.HexToAddress("0x8A244cfdd4777E44bedEDCD478e62AC311EC30Dc"), big.NewInt(1000000000000000000)) + if err != nil { + t.Fatal("Can't transfer token: ", err) + } + fmt.Println("Transfer token transaction: ", transferTx.Hash().Hex()) + time.Sleep(10 * time.Second) + transferTxReceipt, err := client.TransactionReceipt(context.Background(), transferTx.Hash()) + if err != nil { + t.Fatal("Can't get transaction receipt: ", err) + } + fmt.Println("Transaction receipt: ", transferTxReceipt) + + // Get test account balance after transfer + testAccountBalanceAfter, err := client.BalanceAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatal("Can't get balance of test account: ", err) + } + + if testAccountBalanceBefore.Cmp(testAccountBalanceAfter) != 0 { + fmt.Println("Test failed: Test account balance before and after transfer is not equal") + } + + // Get balance token + balanceAfter, err := trc21issuerInstance.GetTokenCapacity(trc21Addr) + if err != nil { + t.Fatal("Can't get token capacity of trc21 issuer smart contract:", err) + } + fmt.Println("TRC21 Issuer token capacity: ", balanceAfter) + + if balanceBefore.Cmp(balanceAfter) <= 0 { + t.Fatal("Test failed: Token balance fee before and after transfer is not correct") + } +} From d2c4b91ab7f66ed553a79cf42dd5bd62fce6f29c Mon Sep 17 00:00:00 2001 From: tristan98 Date: Tue, 22 Oct 2024 02:01:53 +0700 Subject: [PATCH 16/16] fix: PR #21601 fix null pointer exception, add estimate gas to js --- internal/ethapi/api.go | 3 +++ internal/web3ext/web3ext.go | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 34927df7dd..fd37e3aeb0 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1205,6 +1205,9 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl if err != nil { return 0, err } + if block == nil { + return 0, errors.New("block not found") + } hi = block.GasLimit() } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 2287e88081..983d886dff 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -456,6 +456,13 @@ web3._extend({ params: 1, inputFormatter: [web3._extend.formatters.inputTransactionFormatter] }), + new web3._extend.Method({ + name: 'estimateGas', + call: 'eth_estimateGas', + params: 2, + inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputBlockNumberFormatter], + outputFormatter: web3._extend.utils.toDecimal + }), new web3._extend.Method({ name: 'submitTransaction', call: 'eth_submitTransaction', @@ -667,7 +674,7 @@ web3._extend({ call: 'tomox_sendLendingRawTransaction', params: 1 }), - + new web3._extend.Method({ name: 'sendOrderTransaction', call: 'tomox_sendOrder',