Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions e2e/e2etests/legacy/test_zeta_withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/utils"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
observertypes "github.com/zeta-chain/node/x/observer/types"
)

func TestZetaWithdraw(r *runner.E2ERunner, args []string) {
Expand All @@ -14,10 +15,23 @@ func TestZetaWithdraw(r *runner.E2ERunner, args []string) {
// parse withdraw amount
amount := utils.ParseBigInt(r, args[0])

evmChainID, err := r.EVMClient.ChainID(r.Ctx)
require.NoError(r, err)

r.LegacyDepositAndApproveWZeta(amount)
tx := r.LegacyWithdrawZeta(amount, true)

cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
r.Logger.CCTX(*cctx, "zeta withdraw")
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// Get chain params for stability pool percentage
chainParams, err := r.ObserverClient.GetChainParamsForChain(
r.Ctx,
&observertypes.QueryGetChainParamsForChainRequest{ChainId: evmChainID.Int64()},
)
require.NoError(r, err)

// Verify gas accounting and log refund amounts
utils.VerifyOutboundGasAccounting(r, cctx, chainParams.ChainParams.StabilityPoolPercentage, r.Logger)
}
14 changes: 14 additions & 0 deletions e2e/e2etests/test_erc20_withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ import (
"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/utils"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
observertypes "github.com/zeta-chain/node/x/observer/types"
)

func TestERC20Withdraw(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)

amount := utils.ParseBigInt(r, args[0])

evmChainID, err := r.EVMClient.ChainID(r.Ctx)
require.NoError(r, err)

r.ApproveERC20ZRC20(r.GatewayZEVMAddr)
r.ApproveETHZRC20(r.GatewayZEVMAddr)

Expand All @@ -26,4 +30,14 @@ func TestERC20Withdraw(r *runner.E2ERunner, args []string) {
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
r.Logger.CCTX(*cctx, "withdraw")
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// Get chain params for stability pool percentage
chainParams, err := r.ObserverClient.GetChainParamsForChain(
r.Ctx,
&observertypes.QueryGetChainParamsForChainRequest{ChainId: evmChainID.Int64()},
)
require.NoError(r, err)

// Verify gas accounting and log refund amounts
utils.VerifyOutboundGasAccounting(r, cctx, chainParams.ChainParams.StabilityPoolPercentage, r.Logger)
}
14 changes: 14 additions & 0 deletions e2e/e2etests/test_eth_withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ import (
"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/utils"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
observertypes "github.com/zeta-chain/node/x/observer/types"
)

func TestETHWithdraw(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)

amount := utils.ParseBigInt(r, args[0])

evmChainID, err := r.EVMClient.ChainID(r.Ctx)
require.NoError(r, err)

oldBalance, err := r.EVMClient.BalanceAt(r.Ctx, r.EVMAddress(), nil)
require.NoError(r, err)

Expand All @@ -29,6 +33,16 @@ func TestETHWithdraw(r *runner.E2ERunner, args []string) {
r.Logger.CCTX(*cctx, "withdraw")
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// Get chain params for stability pool percentage
chainParams, err := r.ObserverClient.GetChainParamsForChain(
r.Ctx,
&observertypes.QueryGetChainParamsForChainRequest{ChainId: evmChainID.Int64()},
)
require.NoError(r, err)

// Verify gas accounting and log refund amounts
utils.VerifyOutboundGasAccounting(r, cctx, chainParams.ChainParams.StabilityPoolPercentage, r.Logger)

// check the balance was updated, we just check newBalance is greater than oldBalance because of the gas fee
newBalance, err := r.EVMClient.BalanceAt(r.Ctx, r.EVMAddress(), nil)
require.NoError(r, err)
Expand Down
11 changes: 11 additions & 0 deletions e2e/e2etests/test_zeta_withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/utils"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
observertypes "github.com/zeta-chain/node/x/observer/types"
)

func TestZetaWithdraw(r *runner.E2ERunner, args []string) {
Expand All @@ -29,6 +30,16 @@ func TestZetaWithdraw(r *runner.E2ERunner, args []string) {
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
r.Logger.CCTX(*cctx, "zeta_withdraw")
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// Get chain params for stability pool percentage
chainParams, err := r.ObserverClient.GetChainParamsForChain(
r.Ctx,
&observertypes.QueryGetChainParamsForChainRequest{ChainId: evmChainID.Int64()},
)
require.NoError(r, err)

// Verify gas accounting and log refund amounts
utils.VerifyOutboundGasAccounting(r, cctx, chainParams.ChainParams.StabilityPoolPercentage, r.Logger)
} else {
// V2 ZETA flows disabled: tx should revert on GatewayZEVM, no CCTX created
utils.EnsureNoCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient)
Expand Down
48 changes: 48 additions & 0 deletions e2e/utils/zetacore.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"math/big"
"time"

"cosmossdk.io/math"
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
Expand All @@ -17,6 +18,7 @@ import (
"google.golang.org/grpc/status"

"github.com/zeta-chain/node/pkg/constant"
crosschainkeeper "github.com/zeta-chain/node/x/crosschain/keeper"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
)

Expand Down Expand Up @@ -498,3 +500,49 @@ func WaitAndVerifyZRC20BalanceChange(
return
}
}

// VerifyOutboundGasAccounting verifies the gas accounting for an outbound CCTX.
// It asserts that UserGasFeePaid equals GasLimit * GasPrice (gas token denomination),
// and logs the calculated refund amounts (stability pool and user refund).
// stabilityPoolPercentage should be obtained from chain params (e.g., chainParams.ChainParams.StabilityPoolPercentage)
func VerifyOutboundGasAccounting(
t require.TestingT,
cctx *crosschaintypes.CrossChainTx,
stabilityPoolPercentage uint64,
logger infoLogger,
) {
outboundParams := cctx.GetCurrentOutboundParam()

// Verify UserGasFeePaid == GasLimit * GasPrice (gas token denomination)
gasLimit := ParseUint(t, fmt.Sprintf("%d", outboundParams.CallOptions.GasLimit))
gasPrice := ParseUint(t, outboundParams.GasPrice)
expectedUserGasFeePaid := gasLimit.Mul(gasPrice)
require.Equal(
t,
expectedUserGasFeePaid.String(),
outboundParams.UserGasFeePaid.String(),
"UserGasFeePaid should equal GasLimit * GasPrice (gas token denomination)",
)

outboundTxFeePaid := math.NewUint(outboundParams.GasUsed).
Mul(math.NewUintFromBigInt(outboundParams.EffectiveGasPrice.BigInt()))
userGasFeePaid := outboundParams.UserGasFeePaid

logger.Info("Gas accounting - UserGasFeePaid: %s, OutboundTxFeePaid: %s",
userGasFeePaid.String(), outboundTxFeePaid.String())

if outboundTxFeePaid.GTE(userGasFeePaid) {
logger.Info("No remaining fees to refund (outbound used all or more gas than paid)")
return
}

// Calculate refund amounts
totalRemainingFees := userGasFeePaid.Sub(outboundTxFeePaid)
usableRemainingFees := crosschainkeeper.PercentOf(totalRemainingFees, crosschaintypes.UsableRemainingFeesPercentage)
stabilityPoolAmount := crosschainkeeper.PercentOf(usableRemainingFees, stabilityPoolPercentage)
userRefundAmount := usableRemainingFees.Sub(stabilityPoolAmount)

logger.Info("Gas refund - TotalRemaining: %s, UsableRemaining: %s, StabilityPool: %s, UserRefund: %s",
totalRemainingFees.String(), usableRemainingFees.String(),
stabilityPoolAmount.String(), userRefundAmount.String())
}
4 changes: 2 additions & 2 deletions x/crosschain/keeper/gas_payment.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,8 @@ func (k Keeper) PayGasInZetaAndUpdateCctx(
cctx.ZetaFees = cctx.ZetaFees.Add(feeInZeta)
}

// zeta token paid by the user is swapped for gas ZRC20 and burned to pay for fee.
cctx.GetCurrentOutboundParam().UserGasFeePaid = sdkmath.NewUintFromBigInt(outTxGasFeeInZeta)
// Gas fee paid by the user in gas token
cctx.GetCurrentOutboundParam().UserGasFeePaid = outTxGasFee

return nil
}
Loading