Skip to content

Commit

Permalink
include NatSpec comments in contracts, libraries and interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
alex0207s committed Jul 16, 2024
1 parent b90e10c commit e7cf3c4
Show file tree
Hide file tree
Showing 24 changed files with 567 additions and 90 deletions.
13 changes: 12 additions & 1 deletion contracts/AllowanceTarget.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,33 @@ import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol";
import { Ownable } from "./abstracts/Ownable.sol";
import { IAllowanceTarget } from "./interfaces/IAllowanceTarget.sol";

/// @title AllowanceTarget Contract
/// @author imToken Labs
/// @notice This contract manages allowances and authorizes spenders to transfer tokens on behalf of users.
contract AllowanceTarget is IAllowanceTarget, Pausable, Ownable {
using SafeERC20 for IERC20;

mapping(address => bool) public authorized;
/// @notice Mapping of authorized addresses permitted to call spendFromUserTo.
mapping(address trustedCaller => bool isAuthorized) public authorized;

/// @notice Constructor to initialize the contract with the owner and trusted callers.
/// @param _owner The address of the contract owner.
/// @param trustedCaller An array of addresses that are initially authorized to call spendFromUserTo.
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 Only the owner can call this function.
function pause() external onlyOwner {
_pause();
}

/// @notice Unpauses the contract, allowing the execution of spendFromUserTo.
/// @dev Only the owner can call this function.
function unpause() external onlyOwner {
_unpause();
}
Expand Down
24 changes: 20 additions & 4 deletions contracts/CoordinatedTaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,26 @@ import { SignatureValidator } from "./libraries/SignatureValidator.sol";

/// @title CoordinatedTaker Contract
/// @author imToken Labs
/// @notice This contract is a taker contract for the LimitOrderSwap.
/// @dev It helps users avoid collisions when filling a limit order and provides an off-chain order canceling mechanism.
/// For more details, check the reference: https://github.com/consenlabs/tokenlon-contracts/blob/v6.0.1/doc/CoordinatedTaker.md
contract CoordinatedTaker is ICoordinatedTaker, AdminManagement, TokenCollector, EIP712 {
using Asset for address;

IWETH public immutable weth;
ILimitOrderSwap public immutable limitOrderSwap;
address public coordinator;

mapping(bytes32 => bool) public allowFillUsed;
/// @notice Mapping to keep track of used allow fill hashes.
mapping(bytes32 allowFillHash => bool isUsed) public allowFillUsed;

/// @notice Constructor to initialize the contract with the owner, Uniswap permit2, allowance target, WETH, coordinator and LimitOrderSwap contract.
/// @param _owner The address of the contract owner.
/// @param _uniswapPermit2 The address for Uniswap permit2.
/// @param _allowanceTarget The address for the allowance target.
/// @param _weth The WETH contract instance.
/// @param _coordinator The initial coordinator address.
/// @param _limitOrderSwap The LimitOrderSwap contract address.
constructor(
address _owner,
address _uniswapPermit2,
Expand All @@ -36,15 +47,20 @@ contract CoordinatedTaker is ICoordinatedTaker, AdminManagement, TokenCollector,
limitOrderSwap = _limitOrderSwap;
}

/// @notice Receive function to receive ETH.
receive() external payable {}

/// @notice Sets a new coordinator address.
/// @dev Only the owner can call this function.
/// @param _newCoordinator The address of the new coordinator.
function setCoordinator(address _newCoordinator) external onlyOwner {
if (_newCoordinator == address(0)) revert ZeroAddress();
coordinator = _newCoordinator;

emit SetCoordinator(_newCoordinator);
}

/// @inheritdoc ICoordinatedTaker
function submitLimitOrderFill(
LimitOrder calldata order,
bytes calldata makerSignature,
Expand All @@ -59,15 +75,15 @@ contract CoordinatedTaker is ICoordinatedTaker, AdminManagement, TokenCollector,
if (crdParams.expiry < block.timestamp) revert ExpiredPermission();

bytes32 orderHash = getLimitOrderHash(order);

bytes32 allowFillHash = getEIP712Hash(
getAllowFillHash(
AllowFill({ orderHash: orderHash, taker: msg.sender, fillAmount: makerTokenAmount, salt: crdParams.salt, expiry: crdParams.expiry })
)
);
if (!SignatureValidator.validateSignature(coordinator, allowFillHash, crdParams.sig)) revert InvalidSignature();

if (!SignatureValidator.validateSignature(coordinator, allowFillHash, crdParams.sig)) revert InvalidSignature();
if (allowFillUsed[allowFillHash]) revert ReusedPermission();

allowFillUsed[allowFillHash] = true;

emit CoordinatorFill({ user: msg.sender, orderHash: orderHash, allowFillHash: allowFillHash });
Expand All @@ -80,7 +96,7 @@ contract CoordinatedTaker is ICoordinatedTaker, AdminManagement, TokenCollector,
}

// send order to limit order contract
// use fullOrKill since coordinator should manage fill amount distribution
// use fillLimitOrderFullOrKill since coordinator should manage fill amount distribution
limitOrderSwap.fillLimitOrderFullOrKill{ value: msg.value }(
order,
makerSignature,
Expand Down
29 changes: 22 additions & 7 deletions contracts/GenericSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,32 @@ import { GenericSwapData, getGSDataHash } from "./libraries/GenericSwapData.sol"
import { Asset } from "./libraries/Asset.sol";
import { SignatureValidator } from "./libraries/SignatureValidator.sol";

/// @title GenericSwap Contract
/// @author imToken Labs
/// @notice This contract facilitates token swaps using SmartOrderStrategy strategies.
contract GenericSwap is IGenericSwap, TokenCollector, EIP712 {
using Asset for address;

mapping(bytes32 => bool) private filledSwap;
/// @notice Mapping to keep track of filled swaps.
/// @dev Stores the status of swaps to ensure they are not filled more than once.
mapping(bytes32 swapHash => bool isFilled) public filledSwap;

/// @notice Constructor to initialize the contract with the permit2 and allowance target.
/// @param _uniswapPermit2 The address for Uniswap permit2.
/// @param _allowanceTarget The address for the allowance target.
constructor(address _uniswapPermit2, address _allowanceTarget) TokenCollector(_uniswapPermit2, _allowanceTarget) {}

/// @notice Receive function to receive ETH.
receive() external payable {}

/// @param swapData Swap data
/// @return returnAmount Output amount of the swap
/// @inheritdoc IGenericSwap
function executeSwap(GenericSwapData calldata swapData, bytes calldata takerTokenPermit) external payable returns (uint256 returnAmount) {
returnAmount = _executeSwap(swapData, msg.sender, takerTokenPermit);

_emitGSExecuted(getGSDataHash(swapData), swapData, msg.sender, returnAmount);
}

/// @param swapData Swap data
/// @param taker Claimed taker address
/// @param takerSig Taker signature
/// @return returnAmount Output amount of the swap
/// @inheritdoc IGenericSwap
function executeSwapWithSig(
GenericSwapData calldata swapData,
bytes calldata takerTokenPermit,
Expand All @@ -47,6 +52,11 @@ contract GenericSwap is IGenericSwap, TokenCollector, EIP712 {
_emitGSExecuted(swapHash, swapData, taker, returnAmount);
}

/// @notice Executes a generic swap.
/// @param _swapData The swap data containing details of the swap.
/// @param _authorizedUser The address authorized to execute the swap.
/// @param _takerTokenPermit The permit for the taker token.
/// @return returnAmount The output amount of the swap.
function _executeSwap(
GenericSwapData calldata _swapData,
address _authorizedUser,
Expand Down Expand Up @@ -78,6 +88,11 @@ contract GenericSwap is IGenericSwap, TokenCollector, EIP712 {
_outputToken.transferTo(_swapData.recipient, returnAmount);
}

/// @notice Emits the Swap event after executing a generic swap.
/// @param _gsOfferHash The hash of the generic swap offer.
/// @param _swapData The swap data containing details of the swap.
/// @param _taker The address of the taker.
/// @param returnAmount The output amount of the swap.
function _emitGSExecuted(bytes32 _gsOfferHash, GenericSwapData calldata _swapData, address _taker, uint256 returnAmount) internal {
emit Swap(
_gsOfferHash,
Expand Down
64 changes: 51 additions & 13 deletions contracts/LimitOrderSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,26 @@ import { SignatureValidator } from "./libraries/SignatureValidator.sol";

/// @title LimitOrderSwap Contract
/// @author imToken Labs
/// @notice This contract allows users to execute limit orders for token swaps
contract LimitOrderSwap is ILimitOrderSwap, Ownable, TokenCollector, EIP712, ReentrancyGuard {
using Asset for address;

/// @dev Mask used to mark order cancellation in `orderHashToMakerTokenFilledAmount`.
/// The left-most bit (bit 255) of `orderHashToMakerTokenFilledAmount[orderHash]` represents order cancellation.
uint256 private constant ORDER_CANCEL_AMOUNT_MASK = 1 << 255;

IWETH public immutable weth;
address payable public feeCollector;

// how much maker token has been filled in an order
mapping(bytes32 => uint256) public orderHashToMakerTokenFilledAmount;
/// @notice Mapping to track the filled amounts of maker tokens for each order hash.
mapping(bytes32 orderHash => uint256 orderFilledAmount) public orderHashToMakerTokenFilledAmount;

/// @notice Constructor to initialize the contract with the owner, Uniswap permit2, allowance target, WETH, and fee collector.
/// @param _owner The address of the contract owner.
/// @param _uniswapPermit2 The address of the Uniswap permit2.
/// @param _allowanceTarget The address of the allowance target.
/// @param _weth The WETH token instance.
/// @param _feeCollector The initial address of the fee collector.
constructor(
address _owner,
address _uniswapPermit2,
Expand All @@ -39,10 +48,12 @@ contract LimitOrderSwap is ILimitOrderSwap, Ownable, TokenCollector, EIP712, Ree
feeCollector = _feeCollector;
}

/// @notice Receive function to receive ETH.
receive() external payable {}

/// @notice Only owner can call
/// @param _newFeeCollector The new address of fee collector
/// @notice Sets a new fee collector address.
/// @dev Only the owner can call this function.
/// @param _newFeeCollector The new address of the fee collector.
function setFeeCollector(address payable _newFeeCollector) external onlyOwner {
if (_newFeeCollector == address(0)) revert ZeroAddress();
feeCollector = _newFeeCollector;
Expand Down Expand Up @@ -93,7 +104,7 @@ contract LimitOrderSwap is ILimitOrderSwap, Ownable, TokenCollector, EIP712, Ree
takerTokenAmounts[i] = ((makingAmount * order.takerTokenAmount) / order.makerTokenAmount);
if (takerTokenAmounts[i] == 0) revert ZeroTakerTokenAmount();

// the if statement cannot be covered by tests, due to the following issue
// this if statement cannot be covered by tests due to the following issue
// https://github.com/foundry-rs/foundry/issues/3600
if (order.takerToken == address(weth)) {
wethToPay += takerTokenAmounts[i];
Expand All @@ -106,7 +117,7 @@ contract LimitOrderSwap is ILimitOrderSwap, Ownable, TokenCollector, EIP712, Ree
// collect maker tokens
_collect(order.makerToken, order.maker, address(this), makingAmount, order.makerTokenPermit);

// transfer fee if present
// Transfer fee if present
uint256 fee = (makingAmount * order.feeFactor) / Constant.BPS_MAX;
order.makerToken.transferTo(_feeCollector, fee);

Expand Down Expand Up @@ -148,11 +159,17 @@ contract LimitOrderSwap is ILimitOrderSwap, Ownable, TokenCollector, EIP712, Ree
emit OrderCanceled(orderHash, order.maker);
}

/// @inheritdoc ILimitOrderSwap
function isOrderCanceled(bytes32 orderHash) external view returns (bool) {
uint256 orderFilledAmount = orderHashToMakerTokenFilledAmount[orderHash];
return (orderFilledAmount & ORDER_CANCEL_AMOUNT_MASK) != 0;
}

/// @notice Fills a limit order.
/// @param order The limit order details.
/// @param makerSignature The maker's signature for the order.
/// @param takerParams The taker's parameters for the order.
/// @param fullOrKill Whether the order should be filled completely or not at all.
function _fillLimitOrder(LimitOrder calldata order, bytes calldata makerSignature, TakerParams calldata takerParams, bool fullOrKill) private {
(bytes32 orderHash, uint256 takerSpendingAmount, uint256 makerSpendingAmount) = _validateOrderAndQuote(
order,
Expand All @@ -172,7 +189,7 @@ contract LimitOrderSwap is ILimitOrderSwap, Ownable, TokenCollector, EIP712, Ree

if (takerParams.extraAction.length != 0) {
(address strategy, bytes memory strategyData) = abi.decode(takerParams.extraAction, (address, bytes));
// The coverage report indicates that the following line causes the if statement to not be fully covered.
// the coverage report indicates that the following line causes the if statement to not be fully covered,
// even if the logic of the executeStrategy function is empty, this if statement is still not covered.
IStrategy(strategy).executeStrategy(order.makerToken, order.takerToken, makerSpendingAmount - fee, strategyData);
}
Expand All @@ -192,6 +209,15 @@ contract LimitOrderSwap is ILimitOrderSwap, Ownable, TokenCollector, EIP712, Ree
_emitLimitOrderFilled(order, orderHash, takerSpendingAmount, makerSpendingAmount - fee, fee, takerParams.recipient);
}

/// @notice Validates an order and quotes the taker and maker spending amounts.
/// @param _order The limit order details.
/// @param _makerSignature The maker's signature for the order.
/// @param _takerTokenAmount The amount of taker token.
/// @param _makerTokenAmount The amount of maker token.
/// @param _fullOrKill Whether the order should be filled completely or not at all.
/// @return orderHash The hash of the validated order.
/// @return takerSpendingAmount The calculated taker spending amount.
/// @return makerSpendingAmount The calculated maker spending amount.
function _validateOrderAndQuote(
LimitOrder calldata _order,
bytes calldata _makerSignature,
Expand Down Expand Up @@ -229,39 +255,51 @@ contract LimitOrderSwap is ILimitOrderSwap, Ownable, TokenCollector, EIP712, Ree
makerSpendingAmount = _makerTokenAmount;
}
uint256 minTakerTokenAmount = ((makerSpendingAmount * _order.takerTokenAmount) / _order.makerTokenAmount);
// check if taker provide enough amount for this fill (better price is allowed)
// check if taker provides enough amount for this fill (better price is allowed)
if (_takerTokenAmount < minTakerTokenAmount) revert InvalidTakingAmount();
takerSpendingAmount = _takerTokenAmount;

// record fill amount of this tx
orderHashToMakerTokenFilledAmount[orderHash] = orderFilledAmount + makerSpendingAmount;
}

/// @notice Validates an order and its signature.
/// @param _order The limit order details.
/// @param _makerSignature The maker's signature for the order.
/// @return orderHash The hash of the validated order.
/// @return orderFilledAmount The filled amount of the validated order.
function _validateOrder(LimitOrder calldata _order, bytes calldata _makerSignature) private view returns (bytes32, uint256) {
// validate the constrain of the order
// validate the constraints of the order
if (_order.expiry < block.timestamp) revert ExpiredOrder();
if (_order.taker != address(0)) {
if (msg.sender != _order.taker) revert InvalidTaker();
}
if (_order.takerTokenAmount == 0) revert ZeroTakerTokenAmount();
if (_order.makerTokenAmount == 0) revert ZeroMakerTokenAmount();

// validate the status of the order
bytes32 orderHash = getLimitOrderHash(_order);

// check whether the order is fully filled or not
uint256 orderFilledAmount = orderHashToMakerTokenFilledAmount[orderHash];
// validate maker signature only once per order

if (orderFilledAmount == 0) {
// validate maker signature only once per order
if (!SignatureValidator.validateSignature(_order.maker, getEIP712Hash(orderHash), _makerSignature)) revert InvalidSignature();
}

// validate the status of the order
if ((orderFilledAmount & ORDER_CANCEL_AMOUNT_MASK) != 0) revert CanceledOrder();
// check whether the order is fully filled or not
if (orderFilledAmount >= _order.makerTokenAmount) revert FilledOrder();

return (orderHash, orderFilledAmount);
}

/// @notice Emits the LimitOrderFilled event after executing a limit order swap.
/// @param _order The limit order details.
/// @param _orderHash The hash of the limit order.
/// @param _takerTokenSettleAmount The settled amount of taker token.
/// @param _makerTokenSettleAmount The settled amount of maker token.
/// @param _fee The fee amount.
/// @param _recipient The recipient of the order settlement.
function _emitLimitOrderFilled(
LimitOrder calldata _order,
bytes32 _orderHash,
Expand Down
Loading

0 comments on commit e7cf3c4

Please sign in to comment.