-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Chore/increase test coverage and add natspec #314
Chore/increase test coverage and add natspec #314
Conversation
Changes to gas cost
🧾 Summary (20% most significant diffs)
Full diff report 👇
|
Changes to gas cost
🧾 Summary (20% most significant diffs)
Full diff report 👇
|
contracts/LimitOrderSwap.sol
Outdated
@@ -111,6 +110,8 @@ contract LimitOrderSwap is ILimitOrderSwap, Ownable, TokenCollector, EIP712, Ree | |||
|
|||
// unwrap extra WETH in order to pay for ETH taker token and profit | |||
uint256 wethBalance = weth.balanceOf(address(this)); | |||
// the if statement cannot be covered by tests | |||
// because the WETH withdrawal amount is always greater than the balance this contract has |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because the WETH withdrawal amount is always greater than the balance this contract has
Doesn't this means the balance cannot cover the amount being withdrawn hence should revert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is wethBalance > wethToPay
guaranteed? Can't I pass in orders with total weth taker amount (i.e., wethToPay
) greater than the weth maker amount collected (i.e., wethBalance
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the if statement here is just to prevent calling the withdraw
function with a zero amount, instead of guaranteeing that wethBalance
is greater than wethToPay
. This is because we can craft a case to make wethBalance
equal to wethToPay
. However, we cannot craft a case to make wethToPay
greater than wethBalance
, as wethToPay
is accumulated by (makingAmount * order.takerTokenAmount) / order.makerTokenAmount
, and makingAmount
is bounded by orderAvailableAmount
.
The if statement can't cover it here due to the internal require
statement implemented in the withdraw
function of WETH, which we can't trigger under the condition wethBalance - wethToPay
when wethBalance > wethToPay
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, we cannot craft a case to make
wethToPay
greater thanwethBalance
, aswethToPay
is accumulated by(makingAmount * order.takerTokenAmount) / order.makerTokenAmount
, andmakingAmount
is bounded byorderAvailableAmount
.
makingAmount
and orderAvailableAmount
only affect what percentage of order.takerTokenAmount
. It can be 50% or 100% whatever, but order.takerTokenAmount
is a user passed in parameter so I don't see why I can't pass in a large enough order.takerTokenAmount
that will result in wethToPay > wethBalance
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right. We could make wethToPay > wethBalance
by passing a large order.takerTokenAmount
. However, as I mentioned before, the if statement here is to avoid calling the withdraw
function with a zero amount, rather than guaranteeing that wethBalance
is always greater than wethToPay
. Therefore, the if statement cannot be covered because we cannot satisfy the internal require
statement in WETH.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I misunderstood your comment. Then maybe move the comment into the if
code block and rephrase a bit:
if (wethBalance > wethToPay) {
// this if code block cannot be fully covered because WETH withdraw will always succeed as we have checked that wethBalance > wethToPay
unchecked {
weth.withdraw(wethBalance - wethToPay);
}
}
contracts/LimitOrderSwap.sol
Outdated
// get the quote of the fill | ||
// orderAvailableAmount must be greater than 0 here, or it will be reverted by the _validateOrder function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this comment for coverage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, it just reminds developers or auditors of the implicit condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But why do you need a comment for it? You are not using unchecked
to do the arithmetics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It just helps me clarify what _makerTokenAmount
could be if orderAvailableAmount
is always greater than 0. However, we could actually use an unchecked
block to wrap the arithmetic operations."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's for unchecked
then no problem. Otherwise it might not help much as reviewers still have to go over the code to check the conditions themselves.
contracts/RFQ.sol
Outdated
@@ -184,6 +187,8 @@ contract RFQ is IRFQ, Ownable, TokenCollector, EIP712 { | |||
weth.deposit{ value: amount }(); | |||
weth.transfer(to, amount); | |||
} else { | |||
// this branch cannot be covered because we cannot trigger the sendValue internal revert, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe have the to
be a contract and revert at receive
or fallback
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the test case cannot improve the branch coverage because we cannot trigger the internal error AddressInsufficientBalance
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// this branch cannot be covered because we cannot trigger the sendValue internal revert, | |
// this branch cannot be covered because we cannot trigger the AddressInsufficientBalance error in sendValue, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK
contracts/AllowanceTarget.sol
Outdated
contract AllowanceTarget is IAllowanceTarget, Pausable, Ownable { | ||
using SafeERC20 for IERC20; | ||
|
||
/// @notice Mapping of authorized addresses permitted to call spendFromUserTo. | ||
/// @dev Tracks the authorization status of each address to execute the spendFromUserTo function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @dev
comment is basically the same as @notice
. In this case we can omit it.
contracts/AllowanceTarget.sol
Outdated
contract AllowanceTarget is IAllowanceTarget, Pausable, Ownable { | ||
using SafeERC20 for IERC20; | ||
|
||
/// @notice Mapping of authorized addresses permitted to call spendFromUserTo. | ||
/// @dev Tracks the authorization status of each address to execute the spendFromUserTo function. | ||
mapping(address => bool) public authorized; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mapping(address => bool) public authorized; | |
mapping(address trustedCaller => bool isAuthorized) public authorized; |
contracts/AllowanceTarget.sol
Outdated
mapping(address => bool) public authorized; | ||
|
||
/// @notice Constructor to initialize the contract with the owner and trusted callers. | ||
/// @dev Sets up the initial contract owner and authorizes a list of trusted callers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
contracts/AllowanceTarget.sol
Outdated
constructor(address _owner, address[] memory trustedCaller) Ownable(_owner) { | ||
uint256 callerCount = trustedCaller.length; | ||
for (uint256 i = 0; i < callerCount; ++i) { | ||
authorized[trustedCaller[i]] = true; | ||
} | ||
} | ||
|
||
/// @notice Pauses the contract, preventing the execution of spendFromUserTo. | ||
/// @dev Pauses the Contract. Only the owner can call this function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @dev Pauses the Contract. Only the owner can call this function. | |
/// @dev Only the owner can call this function. |
contracts/AllowanceTarget.sol
Outdated
function unpause() external onlyOwner { | ||
_unpause(); | ||
} | ||
|
||
/// @notice Transfers tokens from a user to a specified address. | ||
/// @dev Transfers `amount` of `token` from `from` to `to`. The caller must be authorized. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @dev Transfers `amount` of `token` from `from` to `to`. The caller must be authorized. | |
/// @dev The caller must be authorized. |
/// @param swapData The swap data containing details of the swap. | ||
/// @param takerTokenPermit The permit for the taker token. | ||
/// @return returnAmount The output amount of the swap. | ||
/// @inheritdoc IGenericSwap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto. Why have natspec here too?
/// @param taker The address of the taker. | ||
/// @param takerSig The signature of the taker. | ||
/// @return returnAmount The output amount of the swap. | ||
/// @inheritdoc IGenericSwap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
contracts/GenericSwap.sol
Outdated
/// @param taker Claimed taker address | ||
/// @param takerSig Taker signature | ||
/// @return returnAmount Output amount of the swap | ||
/// @notice Executes a generic swap using predefined strategies with a signature. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @notice Executes a generic swap using predefined strategies with a signature. | |
/// @notice Executes a generic swap using predefined strategies with taker's signature. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And you can add a explanation for why is takerSig
needed in this case using @notice
or @dev
.
contracts/GenericSwap.sol
Outdated
/// @notice Executes a generic swap. | ||
/// @dev Internal function to handle the swap execution. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto. They can be omitted.
contracts/GenericSwap.sol
Outdated
@@ -78,6 +101,12 @@ contract GenericSwap is IGenericSwap, TokenCollector, EIP712 { | |||
_outputToken.transferTo(_swapData.recipient, returnAmount); | |||
} | |||
|
|||
/// @notice Emits the Swap event after executing a generic swap. | |||
/// @dev Internal function to emit the Swap event. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
My bad. I accidentally hit the merge button. |
@@ -5,25 +5,59 @@ import { LimitOrder } from "../libraries/LimitOrder.sol"; | |||
|
|||
/// @title ICoordinatedTaker Interface | |||
/// @author imToken Labs | |||
/// @notice This interface defines the functions and events for a coordinated taker contract. | |||
/// @dev The contract handles limit order fills with additional coordination parameters. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment reads like a limit order swap contract that has additional parameters for "coordination". Then, what does "coordination" mean? What's the difference between this and a limit order swap contract?
@alex0207s Sorry I accidentally merged the PR. Please open a new PR and applies the feedbacks above. |
LimitOrderSwap.sol
to not allow zero takerToken orders or zero makerToken orderspackage.json
for generating coverage -coverage
.