Skip to content
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

Conversation

alex0207s
Copy link
Contributor

  • fix the logic in LimitOrderSwap.sol to not allow zero takerToken orders or zero makerToken orders
  • increase test coverage to near 100%; comment on the reasons why some branches cannot be covered.
  • remove dead codes from the Tokenlon contracts.
  • fix typos.
  • add a script to package.json for generating coverage - coverage.
  • add natspec to Tokenlon contracts (ongoing)

@alex0207s alex0207s requested review from 108356037 and NIC619 July 4, 2024 09:27
Copy link

github-actions bot commented Jul 4, 2024

Changes to gas cost

Generated at commit: 16235e6618ed731dfec3294cbd00dd4776643882, compared to commit: 019650b2eaf9c86d0e3fce3c04acfe001f5f4cd9

🧾 Summary (20% most significant diffs)

Contract Method Avg (+/-) %
AllowanceTarget spendFromUserTo
unpause
+1,756 ❌
+2,113 ❌
+3.55%
+9.02%

Full diff report 👇
Contract Deployment Cost (+/-) Method Min (+/-) % Avg (+/-) % Median (+/-) % Max (+/-) % # Calls (+/-)
AllowanceTarget 613,454 (0) pause
spendFromUserTo
unpause
23,422 (0)
25,029 (0)
23,423 (0)
0.00%
0.00%
0.00%
26,608 (+354)
51,264 (+1,756)
25,536 (+2,113)
+1.35%
+3.55%
+9.02%
27,671 (0)
49,849 (+13,708)
25,536 (+2,113)
0.00%
+37.93%
+9.02%
27,671 (0)
91,606 (0)
27,649 (+4,226)
0.00%
0.00%
+18.04%
4 (+1)
8 (+1)
2 (+1)

Copy link

github-actions bot commented Jul 4, 2024

Changes to gas cost

Generated at commit: 16235e6618ed731dfec3294cbd00dd4776643882, compared to commit: 019650b2eaf9c86d0e3fce3c04acfe001f5f4cd9

🧾 Summary (20% most significant diffs)

Contract Method Avg (+/-) %
LimitOrderSwap fillLimitOrder
fillLimitOrderGroup
-246,932 ✅
-68,112 ✅
-17.31%
-28.05%

Full diff report 👇
Contract Deployment Cost (+/-) Method Min (+/-) % Avg (+/-) % Median (+/-) % Max (+/-) % # Calls (+/-)
LimitOrderSwap 2,730,640 (+36,206) fillLimitOrder
fillLimitOrderFullOrKill
fillLimitOrderGroup
36,094 (0)
48,304 (+42)
31,168 (-166,968)
0.00%
+0.09%
-84.27%
1,179,473 (-246,932)
104,056 (+34)
174,739 (-68,112)
-17.31%
+0.03%
-28.05%
161,770 (-6,889)
48,304 (+42)
198,266 (-47,337)
-4.08%
+0.09%
-19.27%
29,534,880 (+1)
215,562 (+19)
291,975 (+195)
+0.00%
+0.01%
+0.07%
28 (+5)
3 (0)
9 (+3)
RFQ 2,465,421 (+21,622) cancelRFQOffer
feeCollector
fillRFQ
setFeeCollector
23,996 (+22)
382 (-44)
31,822 (0)
23,793 (+22)
+0.09%
-10.33%
0.00%
+0.09%
37,923 (+22)
382 (-44)
131,171 (+1,527)
25,964 (+22)
+0.06%
-10.33%
+1.18%
+0.08%
38,918 (+22)
382 (-44)
147,976 (0)
23,998 (+22)
+0.06%
-10.33%
0.00%
+0.09%
49,859 (+22)
382 (-44)
238,908 (+15,387)
30,102 (+22)
+0.04%
-10.33%
+6.88%
+0.07%
4 (0)
2 (+1)
24 (+2)
3 (0)
SmartOrderStrategy 1,226,691 (0) approveTokens
executeStrategy
56,282 (0)
23,234 (0)
0.00%
0.00%
185,084 (+10,734)
123,683 (+3,118)
+6.16%
+2.59%
268,806 (0)
90,796 (+51,578)
0.00%
+131.52%
268,806 (0)
610,220 (0)
0.00%
0.00%
33 (+6)
18 (+3)
CoordinatedTaker 2,367,349 (+1,248) approveTokens
submitLimitOrderFill
27,095 (0)
33,103 (0)
0.00%
0.00%
169,261 (+3,043)
128,473 (+9)
+1.83%
+0.01%
189,038 (0)
70,073 (0)
0.00%
0.00%
189,038 (0)
256,667 (+19)
0.00%
+0.01%
15 (+2)
7 (0)
GenericSwap 1,756,522 (+22,941) executeSwap
executeSwapWithSig
32,322 (+45)
39,528 (+44)
+0.14%
+0.11%
117,670 (+45)
168,429 (+44)
+0.04%
+0.03%
115,374 (+45)
175,155 (+44)
+0.04%
+0.03%
252,012 (+45)
283,880 (+44)
+0.02%
+0.02%
12 (0)
4 (0)

@alex0207s alex0207s requested a review from shawnbc July 4, 2024 09:32
@alex0207s alex0207s changed the base branch from master to chore/cleanup-and-upgrade-codebase July 4, 2024 09:43
@@ -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
Copy link
Contributor

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?

Copy link
Contributor

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)?

Copy link
Contributor Author

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.

Copy link
Contributor

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 than wethBalance, as wethToPay is accumulated by (makingAmount * order.takerTokenAmount) / order.makerTokenAmount, and makingAmount is bounded by orderAvailableAmount.

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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);
            }
        }

// get the quote of the fill
// orderAvailableAmount must be greater than 0 here, or it will be reverted by the _validateOrder function
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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."

Copy link
Contributor

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.

@@ -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,
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK

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.
Copy link
Contributor

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.

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mapping(address => bool) public authorized;
mapping(address trustedCaller => bool isAuthorized) public authorized;

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @dev Pauses the Contract. Only the owner can call this function.
/// @dev Only the owner can call this function.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @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
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

/// @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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @notice Executes a generic swap using predefined strategies with a signature.
/// @notice Executes a generic swap using predefined strategies with taker's signature.

Copy link
Contributor

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.

Comment on lines 67 to 68
/// @notice Executes a generic swap.
/// @dev Internal function to handle the swap execution.
Copy link
Contributor

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.

@@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

@NIC619 NIC619 merged commit 25d088e into chore/cleanup-and-upgrade-codebase Jul 16, 2024
2 checks passed
@NIC619
Copy link
Contributor

NIC619 commented Jul 16, 2024

My bad. I accidentally hit the merge button.

contracts/CoordinatedTaker.sol Show resolved Hide resolved
contracts/interfaces/ICoordinatedTaker.sol Show resolved Hide resolved
@@ -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.
Copy link
Contributor

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?

contracts/interfaces/ICoordinatedTaker.sol Show resolved Hide resolved
contracts/interfaces/ICoordinatedTaker.sol Show resolved Hide resolved
contracts/interfaces/IWETH.sol Show resolved Hide resolved
contracts/interfaces/IWETH.sol Show resolved Hide resolved
contracts/interfaces/IWETH.sol Show resolved Hide resolved
contracts/interfaces/IWETH.sol Show resolved Hide resolved
contracts/libraries/Asset.sol Show resolved Hide resolved
@NIC619
Copy link
Contributor

NIC619 commented Jul 16, 2024

@alex0207s Sorry I accidentally merged the PR. Please open a new PR and applies the feedbacks above.

@alex0207s alex0207s deleted the chore/increase-test-coverage-and-add-natspec branch July 16, 2024 09:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants