You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
V2 inbound parsing in x/crosschain/types/inbound_parsing.go does not carry event.gasFee or event.protocolFlatFee from Withdrawn / WithdrawnAndCalled into MsgVoteInbound. CCTXGatewayObservers.InitiateOutbound recomputes UserGasFeePaid from the node-side median gas price (x/crosschain/keeper/cctx_gateway_observers.go:77) instead of trusting the amount the contract actually burned.
In production the two bases stay matched. MsgVoteGasPrice writes the crosschain-keeper store and the SystemContract gasPriceByChainId inside a single message handler (x/crosschain/keeper/msg_server_vote_gas_price.go:91-104), so any gateway.withdraw() within a block reads consistent values on both sides. Under matching prices the node value is gasPrice * gasLimit while the contract burns gasPrice * gasLimit + PROTOCOL_FLAT_FEE, so UserGasFeePaid is always <= the user-paid amount and refunds cannot exceed what was paid. Reported as HackenProof ZCNode-254 (report).
This issue tracks the defense-in-depth fix: persist the event-paid gasFee on the CCTX and clamp refundable gas-token credit at min(UserGasFeePaid, event.GasFee). Makes the protocol resilient against any future code path that updates the SystemContract independently of the node-side median.
release/zetacore/v37 (testnet): same accounting model as main. Same atomic gas-price update protects against the PoC's divergence today. Defense-in-depth fix still worth applying.
main: same as v37.
Suggested fix
Add optional event_gas_fee / event_protocol_flat_fee fields to MsgVoteInbound (treated as absent on legacy CCTXs).
Carry the values from the parsed event in NewWithdrawalInbound and NewWithdrawAndCallInbound (x/crosschain/types/inbound_parsing.go:99-148, 203-249). Called does not carry a gasFee field, so this only applies to withdrawal flows.
Persist on OutboundParams and use as a hard cap in refundUnusedGas (x/crosschain/keeper/msg_server_vote_outbound.go:144-251): usableRemainingFees = 95% * (min(UserGasFeePaid, EventGasFee) - outboundTxFeePaid).
Summary
V2 inbound parsing in
x/crosschain/types/inbound_parsing.godoes not carryevent.gasFeeorevent.protocolFlatFeefromWithdrawn/WithdrawnAndCalledintoMsgVoteInbound.CCTXGatewayObservers.InitiateOutboundrecomputesUserGasFeePaidfrom the node-side median gas price (x/crosschain/keeper/cctx_gateway_observers.go:77) instead of trusting the amount the contract actually burned.In production the two bases stay matched.
MsgVoteGasPricewrites the crosschain-keeper store and the SystemContractgasPriceByChainIdinside a single message handler (x/crosschain/keeper/msg_server_vote_gas_price.go:91-104), so anygateway.withdraw()within a block reads consistent values on both sides. Under matching prices the node value isgasPrice * gasLimitwhile the contract burnsgasPrice * gasLimit + PROTOCOL_FLAT_FEE, soUserGasFeePaidis always<=the user-paid amount and refunds cannot exceed what was paid. Reported as HackenProof ZCNode-254 (report).This issue tracks the defense-in-depth fix: persist the event-paid
gasFeeon the CCTX and clamp refundable gas-token credit atmin(UserGasFeePaid, event.GasFee). Makes the protocol resilient against any future code path that updates the SystemContract independently of the node-side median.Branch impact
release/v36(mainnet): not affected. The refund path (feat: refund a portion of remaining unused tokens to user #3734) is not deployed there.release/zetacore/v37(testnet): same accounting model as main. Same atomic gas-price update protects against the PoC's divergence today. Defense-in-depth fix still worth applying.main: same as v37.Suggested fix
event_gas_fee/event_protocol_flat_feefields toMsgVoteInbound(treated as absent on legacy CCTXs).NewWithdrawalInboundandNewWithdrawAndCallInbound(x/crosschain/types/inbound_parsing.go:99-148, 203-249).Calleddoes not carry agasFeefield, so this only applies to withdrawal flows.OutboundParamsand use as a hard cap inrefundUnusedGas(x/crosschain/keeper/msg_server_vote_outbound.go:144-251):usableRemainingFees = 95% * (min(UserGasFeePaid, EventGasFee) - outboundTxFeePaid).Optional field, no state migration needed.
Related