Organization
- @kyber
Engagement Type
Spearbit Web3
Period
-
Repositories
Researchers
Findings
Medium Risk
2 findings
0 fixed
2 acknowledged
Informational
6 findings
0 fixed
6 acknowledged
Medium Risk2 findings
Signed swap digest lacks a domain separator
State
- Acknowledged
Severity
- Severity: Medium
Submitted by
r0bert
Description
Both
UniswapV4KEMHookandPancakeSwapInfinityKEMHookrebuild a quote digest by hashing:keccak256( abi.encode( sender, key, params.zeroForOne, maxAmountIn, maxExchangeRate, exchangeRateDenom, nonce, expiryTime ));The tuple ties the authorization to the router (
sender), the fullPoolKey(which includes the hook address), trade direction, price and input caps, nonce and expiry. Crucially, no domain separator is folded in: chain ID, deployment salt, and contract identity outside key are absent. If the same hook instance (or the samePoolKey) is deployed on multiple networks, as CREATE3-based salt mining allows, an attacker can lift any valid signature+nonce from chain A and replay it on chain B. Because the digest matches,SignatureChecker.isValidSignatureNowsucceeds and the swap executes without the signer’s intention. That breaks the core guarantee that signed quotes are single-instance authorizations, allowing cross-chain replay swaps.Recommendation
Introduce domain separation for the signed payload in both hooks. Adopt an EIP‑712 domain that at minimum commits to chainid.
Kyber: Acknowledgment:
- The quote has a very short expiry time and only affects the EG amount, not poolFee.
- To avoid the operational costs and LP migration burden of redeployment across chains, we will implement chain-specific operator signing keys as a mitigation measure.
Spearbit: Acknowledged.
Quotes can be frontrun by replaying them through the router
State
- Acknowledged
Severity
- Severity: Medium
Submitted by
r0bert
Description
Both hooks accept swaps when
SignatureCheckervalidateskeccak256(abi.encode(sender, key, …, nonce, expiryTime)). Thesenderfield is the router contract that called the pool manager. That restricts execution to Kyber’s router, but not to any particular user. Because the router is public, anyone can forward the calldata and signature. If Alice broadcasts a signed swap, an MEV bot can copy the calldata, submit it first and the hook sees the same router address and quote terms: the attacker’s swap succeeds, consuming the nonce. Alice’s transaction then reverts at_useUnorderedNoncebecause the nonce is already marked as used. The attacker, this way, can invalidate the quote with a dust swap. Designers intended router-level exclusivity, yet end users receive no front-running protection, exposing every quote to mempool sniping.Recommendation
Include the router's original caller in the signature. The original caller can be retrieved by the hook by calling
router.msgSender()function. See Uniswap V4 hook guide on accessing msg.sender securely.Kyber: Acknowledgment:
- The UniswapV4KEMHook protects makers (LPs)
- Takers/traders receive front-running protections at the Aggregator contract level, as this is an exclusive liquidity source. Since the signed
senderis the Aggregator contract.
Spearbit: Acknowledged.
Informational6 findings
Trust assumptions
State
- Acknowledged
Severity
- Severity: Informational
≈
Likelihood: Low×
Impact: Low Submitted by
Alireza Arjmand
Description
For the contracts to operate correctly, the following assumptions must hold:
- The hook owner is responsible for maintaining the
claimablemapping as well as keeping thequoteSignerandegRecipientaddresses up to date. - The contract relies on a signature check, where signatures are issued by a backend controlled by the Kyber team. The trust assumptions regarding this backend are:
- It must not sign multiple swaps with the same nonce unless the previous one has expired.
- It must ensure that
egAmountdoes not exceed a threshold that would render a swap unprofitable for the trader.
Kyber: Acknowledgment:
- This is an exclusive hook implementation that serves two key purposes: the EG mechanism & the exclusive logic while maintaining the same risk profile for Liquidity Providers (LPs) as the base Uniswap V4 pool.
- The taker/trader benefits from protections at the Aggregator contract level.
- The EG sharing mechanism operates on a trust basis.
Spearbit: Acknowledged.
Rescue function name does not reflect handling of native assets
State
- Acknowledged
Severity
- Severity: Informational
≈
Likelihood: Low×
Impact: Low Submitted by
Alireza Arjmand
Description
The
rescueERC20sfunction also transfers the chain’s native asset iftoken == address(0). While correct, the current name does not clearly reflect that it rescues both ERC20 tokens and the native coin.Recommendation
Rename the function to something more descriptive to reflect its ability to handle both ERC20s and native assets.
Kyber: Acknowledgment:
- This is a valid finding. However, it remains acceptable at this stage since the functionality serves only as a rescue mechanism for an optional flow, and the hook is not designed to hold any Native or ERC20 tokens. Any Native or ERC20 in the hook contract is sent by mistake.
Spearbit: Acknowledged.
Zero nonce remains reusable until signature expiry
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
r0bert
Description
Both hooks call
_useUnorderedNonce(nonce)inbeforeSwap. The helper ignores and skips any check on the zero nonce:function _useUnorderedNonce(uint256 nonce) internal { // ignore nonce 0 for flexibility if (nonce == 0) return; uint256 wordPos = nonce >> 8; uint256 bitPos = uint8(nonce); uint256 bit = 1 << bitPos; uint256 flipped = nonces[wordPos] ^= bit; if (flipped & bit == 0) revert NonceAlreadyUsed(nonce); emit UseNonce(nonce);}so any quote signed with
nonce = 0is never recorded as used. An attacker who sees such a payload can reuse the same calldata through the router as many times as the signature’sexpiryTimeallows, defeating the “single-use” guarantee that unordered nonces are meant to provide. Because the nonce is router-scoped, a single leaked 0-nonce signature effectively authorises every router caller/user until it expires.Recommendation
Consider making zero nonces invalid. Either add
require(nonce != 0)before calling_useUnorderedNonce, or change_useUnorderedNonceto revert on zero.Kyber: Nullified:
- This is intentional to increase system flexibility. Setting nonce to non-zero prevents replay attacks, while setting it to zero saves gas, so it's a deliberate trade-off.
- The hook protects LPs only; taker protection occurs at the Aggregator contract level.
Spearbit: Acknowledged.
updateClaimable allows duplicate arguments
State
- Acknowledged
Severity
- Severity: Informational
≈
Likelihood: Low×
Impact: Low Submitted by
0xluk3
Description
In
BaseKEMHook.sol, in functionsupdateClaimableand further_updateClaimable, there is no duplicate check on the input array which allows duplicated accounts to be supplied to the function.function _updateClaimable(address[] memory accounts, bool newStatus) internal { for (uint256 i = 0; i < accounts.length; i++) { claimable[accounts[i]] = newStatus; emit UpdateClaimable(accounts[i], newStatus); } }If an account is provided more than once in the input array by mistake, especially with different
newStatusvalues, this won't be noticed by the contract leading to potentially undesired final status of such account.Recommendation
Consider implementing a duplicate check for the input arguments. However, this may come with slightly increased gas cost of execution for such safeguard.
Kyber: Acknowledgment:
- This is an operational function.
- Each call applies the same
newStatus, and the system tracks state through emitted events and the claimable function.
Spearbit: Acknowledged.
Overly restrictive maxAmountIn check against amountSpecified
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
Alireza Arjmand
Description
In
exactInmode,amountSpecifiedserves as an upper bound foramountIn, not its actual value. Enforcing a check ofmaxAmountIndirectly againstamountSpecifiedis unnecessarily restrictive and can cause valid swaps to revert.Recommendation
Perform the
maxAmountInvalidation against the actualamountInused in the swap, which is available in theafterSwaphook. This ensures that the limit is enforced precisely and does not reject valid transactions.Kyber: Acknowledgment:
- This is a valid finding.
- However, since this only affects extreme cases and also requires additional effort in the simulation system, we'll maintain the current implementation.
- In the off-chain simulation system, it always assumes the full
amountSpecifiedis used for the swap, then the maxAmountIn is calculated based onamountSpecified, thus, comparing with this value makes it more consistent between on-chain and off-chain calculations.
Spearbit: Acknowledged.
Unsafe typecast and unchecked arithmetic operations
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
Akshay Srivastav
Description
The
afterswapfunctions ofUniswapV4KEMHook&PancakeSwapInfinityKEMHookcontracts perform unsafe typecasting ofint256toint128which can possibly result in overflow ofint128values.Also in the first
uncheckedblock ofafterswapfunctions, if theamountInis equal totype(int128).minthen its negation will overflow and equals to1, which will makemaxAmountOutvery small and will claim most of theamountOutfor the hook.Both these cases are unintended and can be prevented by better coding practices.
Recommendation
- Consider using
Safecastlibrary for all explicit type conversions. - Either remove the
uncheckedblock or explicitly handle the overflow/underflow scenarios.
Kyber: Acknowledgment:
- If
egAmountreturned byafterSwapis less than the amount minted from here, the transaction will revert. - About
amountIn. This is an extreme case that affects the taker, but still, they have the protection at the Aggregator contract level. - We can also note that hook doesn’t support
amountIn = type(int128).min
Spearbit: Acknowledged.