Omniscia Xcaliswap Audit

SwapPair Manual Review Findings

SwapPair Manual Review Findings

SPR-01M: Potentially Misleading Fee Amounts

TypeSeverityLocation
Logical FaultSwapPair.sol:L164, L178

Description:

The Fees event is meant to emit the total amount of fees acquired for either the token0 or token1 asset, however, the Xcaliswap protocol imposes a custom 10% tariff that is redirected to a protocol fee collector and does not benefit the pair.

Example:

contracts/Core/SwapPair.sol
153// Accrue fees on token0
154function _update0(uint amount) internal {
155 address _feeTo = ISwapFactory(factory).feeCollector();
156 uint256 _protocolFee = amount / 10; // 10% of the amount
157 uint256 _feeIncrease = amount - _protocolFee; // Might leave tokens in this contract due to rounding but ok, reserves updated after this function
158 _safeTransfer(token0, _feeTo, _protocolFee);
159 _safeTransfer(token0, fees, _feeIncrease); // transfer the fees out to SwapFees
160 uint256 _ratio = _feeIncrease * 1e18 / totalSupply; // 1e18 adjustment is removed during claim
161 if (_ratio > 0) {
162 index0 += _ratio;
163 }
164 emit Fees(msg.sender, amount, 0);
165}
166
167// Accrue fees on token1
168function _update1(uint amount) internal {
169 address _feeTo = ISwapFactory(factory).feeCollector();
170 uint256 _protocolFee = amount / 10; // 10% of the amount
171 uint256 _feeIncrease = amount - _protocolFee; // Might leave tokens in this contract due to rounding but ok, reserves updated after this function
172 _safeTransfer(token1, _feeTo, _protocolFee); // Transfer protocol fee to _feeTo
173 _safeTransfer(token1, fees, _feeIncrease); // transfer the fees out to SwapFees
174 uint256 _ratio = _feeIncrease * 1e18 / totalSupply;
175 if (_ratio > 0) {
176 index1 += _ratio;
177 }
178 emit Fees(msg.sender, 0, amount);
179}

Recommendation:

We advise the Fees events to either introduce a new variable denoting the protocol fee or to instead emit the _feeIncrease value instead as otherwise it is misleading.

Alleviation:

The Xcaliswap team has fixed this issue by emitting the _feeIncrease value in the Fees events.

SPR-02M: Insecure Elliptic Curve Recovery Mechanism

TypeSeverityLocation
Language SpecificSwapPair.sol:L477, L495

Description:

The ecrecover function is a low-level cryptographic function that should be utilized after appropriate sanitizations have been enforced on its arguments, namely on the s and v values. This is due to the inherent trait of the curve to be symmetrical on the x-axis and thus permitting signatures to be replayed with the same x value (r) but a different y value (s).

Impact:

Should the payload being verified by the signature rely on differentiation based on the s or v arguments, it will be possible to replay the signature for the same data validly and acquire authorization twice.

Example:

contracts/Core/SwapPair.sol
477function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
478 require(deadline >= block.timestamp, 'SwapPair: EXPIRED');
479 DOMAIN_SEPARATOR = keccak256(
480 abi.encode(
481 keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
482 keccak256(bytes(name)),
483 keccak256('1'),
484 block.chainid,
485 address(this)
486 )
487 );
488 bytes32 digest = keccak256(
489 abi.encodePacked(
490 '\x19\x01',
491 DOMAIN_SEPARATOR,
492 keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
493 )
494 );
495 address recoveredAddress = ecrecover(digest, v, r, s);

Recommendation:

We advise them to be sanitized by ensuring that v is equal to either 27 or 28 (v ∈ {27, 28}) and to ensure that s is existent in the lower half order of the elliptic curve (0 < s < secp256k1n ÷ 2 + 1) by ensuring it is less than 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A1. A reference implementation of those checks can be observed in the ECDSA library of OpenZeppelin and the rationale behind those restrictions exists within Appendix F of the Yellow Paper.

Alleviation:

While the Xcaliswap team has added require statements to validate the v and s values, the validation condition for s has been implemented incorrectly. The bytesX and unitary denominations (i.e. uintX) may differ in underlying binary representation, hence the s variable should be casted to uint256 before comparing it to 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A1.