Skip to content

V2 GatewayZEVM inbound getZRC20InboundDetails does not check ForeignCoins.Paused #4585

@kingpinXD

Description

@kingpinXD

Summary

x/crosschain/keeper/v2_zevm_inbound.go getZRC20InboundDetails loads the foreign coin entry but never reads ForeignCoins.Paused before driving the V2 inbound into ValidateInbound / InitiateOutbound. Pause enforcement on the V2 path currently relies entirely on the second EVM hook checkPausedZRC20 reverting the tx when the paused ZRC20 contract is a log emitter.

This is fine for Withdrawn and WithdrawnAndCalled because GatewayZEVM._withdrawZRC20WithGasLimit calls transferFrom and burn on the user-supplied ZRC20 and the resulting Transfer logs trip the hook.

It does not cover Called / NoAssetCall with an ERC20-type ZRC20 used as routing key. The gateway only burns the destination chain's gas-token ZRC20, the user-supplied (paused) ZRC20 is touched only via the view function withdrawGasFeeWithGasLimit, and no logs come from the paused contract. checkPausedZRC20 does not fire, the CCTX persists in PendingOutbound, and an outbound no-asset call is signed against a paused route.

No asset extraction. The residual outbound carries no value and is paid in the unpaused gas token. Reported as part of HackenProof ZCNode-253 (report) and tracked here as a defense-in-depth fix.

Suggested fix

Gate getZRC20InboundDetails on the paused flag so the contract is honored end-to-end across every V2 event, not only the ones that incidentally emit logs from the paused ZRC20.

foreignCoin, found := k.fungibleKeeper.GetForeignCoins(ctx, zrc20.Hex())
if !found {
    ctx.Logger().Info(fmt.Sprintf("cannot find foreign coin associated to the zrc20 address %s", zrc20.Hex()))
    return InboundDetails{}, nil
}

if foreignCoin.Paused {
    return InboundDetails{}, errorsmod.Wrapf(
        fungibletypes.ErrPausedZRC20,
        "zrc20 %s is paused",
        zrc20.Hex(),
    )
}

Optionally, when coinType == NoAssetCall, also check the destination chain's gas-token ZRC20 paused flag (mirroring the x/fungible/keeper/abort.go pattern), so pausing either the routing ZRC20 or the chain's gas token disables the call. This folds naturally into the broader refactor tracked in #2627.

Add regression tests in x/crosschain/keeper/v2_zevm_inbound_test.go for paused Called, Withdrawn, WithdrawnAndCalled plus unpaused controls.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions