diff --git a/jsonrpc/endpoints_eth.go b/jsonrpc/endpoints_eth.go index d95a0cba58..d0ac27e537 100644 --- a/jsonrpc/endpoints_eth.go +++ b/jsonrpc/endpoints_eth.go @@ -199,10 +199,15 @@ func (e *EthEndpoints) EstimateGas(arg *types.TxArgs, blockArg *types.BlockNumbe return RPCErrorResponse(types.DefaultErrorCode, "failed to convert arguments into an unsigned transaction", err, false) } + isGasFreeSender, err := e.pool.IsFreeGasAddr(ctx, sender) + if err != nil { + return nil, types.NewRPCError(types.DefaultErrorCode, "failed to check gas-free", err) + } + t2 := time.Now() toTxTime := t2.Sub(t1) - gasEstimation, returnValue, err := e.state.EstimateGas(tx, sender, blockToProcess, dbTx) + gasEstimation, returnValue, err := e.state.EstimateGas(tx, sender, isGasFreeSender, blockToProcess, dbTx) if errors.Is(err, runtime.ErrExecutionReverted) { data := make([]byte, len(returnValue)) copy(data, returnValue) diff --git a/jsonrpc/endpoints_zkevm.go b/jsonrpc/endpoints_zkevm.go index ffeeed4535..7c07120b78 100644 --- a/jsonrpc/endpoints_zkevm.go +++ b/jsonrpc/endpoints_zkevm.go @@ -520,7 +520,12 @@ func (z *ZKEVMEndpoints) internalEstimateGasPriceAndFee(ctx context.Context, arg return nil, nil, types.NewRPCError(types.DefaultErrorCode, "failed to convert arguments into an unsigned transaction") } - gasEstimation, returnValue, err := z.state.EstimateGas(tx, sender, blockToProcess, dbTx) + isGasFreeSender, err := z.pool.IsFreeGasAddr(ctx, sender) + if err != nil { + return nil, nil, types.NewRPCError(types.DefaultErrorCode, "failed to check gas-free", err) + } + + gasEstimation, returnValue, err := z.state.EstimateGas(tx, sender, isGasFreeSender, blockToProcess, dbTx) if errors.Is(err, runtime.ErrExecutionReverted) { data := make([]byte, len(returnValue)) copy(data, returnValue) diff --git a/jsonrpc/mocks/mock_pool_xlayer.go b/jsonrpc/mocks/mock_pool_xlayer.go index 96b0069083..7e72c2423b 100644 --- a/jsonrpc/mocks/mock_pool_xlayer.go +++ b/jsonrpc/mocks/mock_pool_xlayer.go @@ -114,3 +114,8 @@ func (_m *PoolMock) GetReadyTxCount(ctx context.Context) (uint64, error) { return r0, r1 } + +// IsFreeGasAddr check if the address is gas-free +func (_m *PoolMock) IsFreeGasAddr(ctx context.Context, addr common.Address) (bool, error) { + return false, nil +} diff --git a/jsonrpc/mocks/mock_state.go b/jsonrpc/mocks/mock_state.go index 36f552fe65..bc9070e6a6 100644 --- a/jsonrpc/mocks/mock_state.go +++ b/jsonrpc/mocks/mock_state.go @@ -115,7 +115,7 @@ func (_m *StateMock) DebugTransaction(ctx context.Context, transactionHash commo } // EstimateGas provides a mock function with given fields: transaction, senderAddress, l2BlockNumber, dbTx -func (_m *StateMock) EstimateGas(transaction *coretypes.Transaction, senderAddress common.Address, l2BlockNumber *uint64, dbTx pgx.Tx) (uint64, []byte, error) { +func (_m *StateMock) EstimateGas(transaction *coretypes.Transaction, senderAddress common.Address, isGasFreeSender bool, l2BlockNumber *uint64, dbTx pgx.Tx) (uint64, []byte, error) { ret := _m.Called(transaction, senderAddress, l2BlockNumber, dbTx) if len(ret) == 0 { diff --git a/jsonrpc/types/interfaces.go b/jsonrpc/types/interfaces.go index 3d1333c74f..54c0561c98 100644 --- a/jsonrpc/types/interfaces.go +++ b/jsonrpc/types/interfaces.go @@ -30,6 +30,7 @@ type PoolInterface interface { GetInnerTx(ctx context.Context, txHash common.Hash) (string, error) GetMinSuggestedGasPriceWithDelta(ctx context.Context, delta time.Duration) (uint64, error) GetReadyTxCount(ctx context.Context) (uint64, error) + IsFreeGasAddr(ctx context.Context, addr common.Address) (bool, error) } // StateInterface gathers the methods required to interact with the state. @@ -37,7 +38,7 @@ type StateInterface interface { StartToMonitorNewL2Blocks() BeginStateTransaction(ctx context.Context) (pgx.Tx, error) DebugTransaction(ctx context.Context, transactionHash common.Hash, traceConfig state.TraceConfig, dbTx pgx.Tx) (*runtime.ExecutionResult, error) - EstimateGas(transaction *types.Transaction, senderAddress common.Address, l2BlockNumber *uint64, dbTx pgx.Tx) (uint64, []byte, error) + EstimateGas(transaction *types.Transaction, senderAddress common.Address, isGasFreeSender bool, l2BlockNumber *uint64, dbTx pgx.Tx) (uint64, []byte, error) GetBalance(ctx context.Context, address common.Address, root common.Hash) (*big.Int, error) GetCode(ctx context.Context, address common.Address, root common.Hash) ([]byte, error) GetL2BlockByHash(ctx context.Context, hash common.Hash, dbTx pgx.Tx) (*state.L2Block, error) diff --git a/pool/pool_xlayer.go b/pool/pool_xlayer.go index b086479ea1..b7ea2d5147 100644 --- a/pool/pool_xlayer.go +++ b/pool/pool_xlayer.go @@ -143,7 +143,7 @@ func (p *Pool) checkFreeGp(ctx context.Context, poolTx Transaction, from common. case MainnetChainID: bridgeURL = MainnetBridgeURL } - return false, fmt.Errorf("you are unable to initiate a gas-free transaction from this address unless you have previously transferred funds to this address via the X Layer Bridge (%s) or the OKX Exchange, and only the first %d transactions(address nonce less than %d)", + return false, fmt.Errorf("you are unable to initiate a gas-free transaction from this address unless you have previously transferred funds to this address via the X Layer Bridge (%s) or the OKX Exchange, only the first %d transactions (address nonce must be less than %d) can be gas-free", bridgeURL, freeGasCountPerAddrConfig, freeGasCountPerAddrConfig) @@ -157,13 +157,16 @@ func (p *Pool) checkAndUpdateFreeGasAddr(ctx context.Context, poolTx Transaction var freeGpAddr common.Address inputHex := hex.EncodeToHex(poolTx.Data()) // hard code - if isFreeGasExAddress(p.cfg.FreeGasExAddress, from) && - strings.HasPrefix(inputHex, ExWithdrawalMethodSignature) && - len(inputHex) > 74 { // erc20 contract transfer - addrHex := "0x" + inputHex[10:74] - freeGpAddr = common.HexToAddress(addrHex) - } else if poolTx.IsClaims && - len(inputHex) > 4554 { // bridge contract claim + if isFreeGasExAddress(p.cfg.FreeGasExAddress, from) { + if strings.HasPrefix(inputHex, ExWithdrawalMethodSignature) && len(inputHex) > 74 { // erc20 contract transfer + addrHex := "0x" + inputHex[10:74] + freeGpAddr = common.HexToAddress(addrHex) + } else { + // the to address of any Ex withdrawal okb tx will be considered as a gas-free address + // even if this address is a contract, it will not affect the gas-free + freeGpAddr = *poolTx.To() + } + } else if poolTx.IsClaims && len(inputHex) > 4554 { // bridge contract claim addrHex := "0x" + inputHex[4490:4554] freeGpAddr = common.HexToAddress(addrHex) } diff --git a/state/test/forkid_dragonfruit/dragonfruit_test.go b/state/test/forkid_dragonfruit/dragonfruit_test.go index a7b1a5de80..b32906ed00 100644 --- a/state/test/forkid_dragonfruit/dragonfruit_test.go +++ b/state/test/forkid_dragonfruit/dragonfruit_test.go @@ -332,7 +332,7 @@ func TestExecutorEstimateGas(t *testing.T) { blockNumber, err := testState.GetLastL2BlockNumber(ctx, nil) require.NoError(t, err) - estimatedGas, _, err := testState.EstimateGas(signedTx2, sequencerAddress, &blockNumber, nil) + estimatedGas, _, err := testState.EstimateGas(signedTx2, sequencerAddress, false, &blockNumber, nil) require.NoError(t, err) log.Debugf("Estimated gas = %v", estimatedGas) @@ -340,7 +340,7 @@ func TestExecutorEstimateGas(t *testing.T) { tx3 := types.NewTransaction(nonce, scAddress, new(big.Int), 40000, new(big.Int).SetUint64(1), common.Hex2Bytes("4abbb40a")) signedTx3, err := auth.Signer(auth.From, tx3) require.NoError(t, err) - _, _, err = testState.EstimateGas(signedTx3, sequencerAddress, &blockNumber, nil) + _, _, err = testState.EstimateGas(signedTx3, sequencerAddress, false, &blockNumber, nil) require.Error(t, err) } @@ -701,7 +701,7 @@ func TestExecutorGasEstimationMultisig(t *testing.T) { blockNumber, err := testState.GetLastL2BlockNumber(ctx, nil) require.NoError(t, err) - estimatedGas, _, err := testState.EstimateGas(signedTx6, sequencerAddress, &blockNumber, nil) + estimatedGas, _, err := testState.EstimateGas(signedTx6, sequencerAddress, false, &blockNumber, nil) require.NoError(t, err) log.Debugf("Estimated gas = %v", estimatedGas) diff --git a/state/transaction.go b/state/transaction.go index 7158fe1966..ef62cc14fe 100644 --- a/state/transaction.go +++ b/state/transaction.go @@ -713,7 +713,7 @@ func CheckSupersetBatchTransactions(existingTxHashes []common.Hash, processedTxs } // EstimateGas for a transaction -func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common.Address, l2BlockNumber *uint64, dbTx pgx.Tx) (uint64, []byte, error) { +func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common.Address, isGasFreeSender bool, l2BlockNumber *uint64, dbTx pgx.Tx) (uint64, []byte, error) { const ethTransferGas = 21000 ctx := context.Background() @@ -764,7 +764,7 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common // if gas price is set, set the highEnd to the max amount // of the account afford - isGasPriceSet := transaction.GasPrice().BitLen() != 0 + isGasPriceSet := !isGasFreeSender && transaction.GasPrice().BitLen() != 0 if isGasPriceSet { senderBalance, err := s.tree.GetBalance(ctx, senderAddress, l2Block.Root().Bytes()) if errors.Is(err, ErrNotFound) { @@ -833,9 +833,9 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common log.Debugf("Estimate gas. Trying to execute TX with %v gas", highEnd) var estimationResult *testGasEstimationResult if forkID < FORKID_ETROG { - estimationResult, err = s.internalTestGasEstimationTransactionV1(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, highEnd, nonce, false) + estimationResult, err = s.internalTestGasEstimationTransactionV1(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, isGasFreeSender, highEnd, nonce, false) } else { - estimationResult, err = s.internalTestGasEstimationTransactionV2(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, highEnd, nonce, false) + estimationResult, err = s.internalTestGasEstimationTransactionV2(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, isGasFreeSender, highEnd, nonce, false) } if err != nil { return 0, nil, err @@ -867,9 +867,9 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common optimisticGasLimit := (estimationResult.gasUsed + estimationResult.gasRefund + params.CallStipend) * 64 / 63 // nolint:gomnd if optimisticGasLimit < highEnd { if forkID < FORKID_ETROG { - estimationResult, err = s.internalTestGasEstimationTransactionV1(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, optimisticGasLimit, nonce, false) + estimationResult, err = s.internalTestGasEstimationTransactionV1(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, isGasFreeSender, optimisticGasLimit, nonce, false) } else { - estimationResult, err = s.internalTestGasEstimationTransactionV2(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, optimisticGasLimit, nonce, false) + estimationResult, err = s.internalTestGasEstimationTransactionV2(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, isGasFreeSender, optimisticGasLimit, nonce, false) } if err != nil { // This should not happen under normal conditions since if we make it this far the @@ -897,9 +897,9 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common log.Debugf("Estimate gas. Trying to execute TX with %v gas", mid) if forkID < FORKID_ETROG { - estimationResult, err = s.internalTestGasEstimationTransactionV1(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, mid, nonce, true) + estimationResult, err = s.internalTestGasEstimationTransactionV1(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, isGasFreeSender, mid, nonce, true) } else { - estimationResult, err = s.internalTestGasEstimationTransactionV2(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, mid, nonce, true) + estimationResult, err = s.internalTestGasEstimationTransactionV2(ctx, batch, l2Block, latestL2BlockNumber, transaction, forkID, senderAddress, isGasFreeSender, mid, nonce, true) } executionTime := time.Since(txExecutionStart) totalExecutionTime += executionTime @@ -935,19 +935,23 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common // during the binary search process to define the gas estimation of a given tx for l2 blocks // before ETROG func (s *State) internalTestGasEstimationTransactionV1(ctx context.Context, batch *Batch, l2Block *L2Block, latestL2BlockNumber uint64, - transaction *types.Transaction, forkID uint64, senderAddress common.Address, + transaction *types.Transaction, forkID uint64, senderAddress common.Address, isGasFreeSender bool, gas uint64, nonce uint64, shouldOmitErr bool) (*testGasEstimationResult, error) { timestamp := l2Block.Time() if l2Block.NumberU64() == latestL2BlockNumber { timestamp = uint64(time.Now().Unix()) } + gp := transaction.GasPrice() + if isGasFreeSender { + gp = big.NewInt(0) + } tx := types.NewTx(&types.LegacyTx{ Nonce: nonce, To: transaction.To(), Value: transaction.Value(), Gas: gas, - GasPrice: transaction.GasPrice(), + GasPrice: gp, Data: transaction.Data(), }) @@ -1037,17 +1041,21 @@ func (s *State) internalTestGasEstimationTransactionV1(ctx context.Context, batc // during the binary search process to define the gas estimation of a given tx for l2 blocks // after ETROG func (s *State) internalTestGasEstimationTransactionV2(ctx context.Context, batch *Batch, l2Block *L2Block, latestL2BlockNumber uint64, - transaction *types.Transaction, forkID uint64, senderAddress common.Address, + transaction *types.Transaction, forkID uint64, senderAddress common.Address, isGasFreeSender bool, gas uint64, nonce uint64, shouldOmitErr bool) (*testGasEstimationResult, error) { deltaTimestamp := uint32(uint64(time.Now().Unix()) - l2Block.Time()) transactions := s.BuildChangeL2Block(deltaTimestamp, uint32(0)) + gp := transaction.GasPrice() + if isGasFreeSender { + gp = big.NewInt(0) + } tx := types.NewTx(&types.LegacyTx{ Nonce: nonce, To: transaction.To(), Value: transaction.Value(), Gas: gas, - GasPrice: transaction.GasPrice(), + GasPrice: gp, Data: transaction.Data(), }) diff --git a/test/config/test.node.config.toml b/test/config/test.node.config.toml index 633f1dad20..c3bfca6070 100644 --- a/test/config/test.node.config.toml +++ b/test/config/test.node.config.toml @@ -29,6 +29,8 @@ Outputs = ["stderr"] MaxSHA256Hashes = 1596 [Pool] +EnableFreeGasByNonce = false +FreeGasExAddress = [] FreeGasCountPerAddr = 3 FreeGasLimit = 1500000 FreeClaimGasLimit = 1500000