@zarclays/zswap-trident
Version:
56 lines • 332 kB
JSON
{
"language": "Solidity",
"sources": {
"contracts/TridentRouter.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\nimport {Multicall} from \"./abstract/Multicall.sol\";\nimport {SelfPermit} from \"./abstract/SelfPermit.sol\";\nimport {Transfer} from \"./libraries/Transfer.sol\";\nimport {IBentoBoxMinimal} from \"./interfaces/IBentoBoxMinimal.sol\";\nimport {IMasterDeployer} from \"./interfaces/IMasterDeployer.sol\";\nimport {IPool} from \"./interfaces/IPool.sol\";\nimport {ITridentRouter} from \"./interfaces/ITridentRouter.sol\";\nimport {IWETH9} from \"./interfaces/IWETH9.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/// @dev Custom Errors\nerror NotWethSender();\nerror TooLittleReceived();\nerror NotEnoughLiquidityMinted();\nerror IncorrectTokenWithdrawn();\nerror IncorrectSlippageParams();\nerror InsufficientWETH();\nerror InvalidPool();\n\n/// @notice Router contract that helps in swapping across Trident pools.\ncontract TridentRouter is ITridentRouter, SelfPermit, Multicall {\n using Transfer for address;\n\n /// @notice BentoBox token vault.\n IBentoBoxMinimal public immutable bento;\n\n /// @notice Master deployer.\n IMasterDeployer public immutable masterDeployer;\n\n /// @notice ERC-20 token for wrapped ETH (v9).\n address internal immutable wETH;\n\n /// @notice The user should use 0x0 if they want to use native currency, e.g., ETH.\n address constant USE_NATIVE = address(0);\n\n constructor(\n IBentoBoxMinimal _bento,\n IMasterDeployer _masterDeployer,\n address _wETH\n ) {\n bento = _bento;\n masterDeployer = _masterDeployer;\n wETH = _wETH;\n _bento.registerProtocol();\n }\n\n receive() external payable {\n if (msg.sender != wETH) revert NotWethSender();\n }\n\n /// @notice Swaps token A to token B directly. Swaps are done on `bento` tokens.\n /// @param params This includes the address of token A, pool, amount of token A to swap,\n /// minimum amount of token B after the swap and data required by the pool for the swap.\n /// @dev Ensure that the pool is trusted before calling this function. The pool can steal users' tokens.\n function exactInputSingle(ExactInputSingleParams calldata params) public payable returns (uint256 amountOut) {\n // Prefund the pool with token A.\n bento.transfer(params.tokenIn, msg.sender, params.pool, params.amountIn);\n // Trigger the swap in the pool.\n amountOut = IPool(params.pool).swap(params.data);\n // Ensure that the slippage wasn't too much. This assumes that the pool is honest.\n if (amountOut < params.amountOutMinimum) revert TooLittleReceived();\n }\n\n /// @notice Swaps token A to token B indirectly by using multiple hops.\n /// @param params This includes the addresses of the tokens, pools, amount of token A to swap,\n /// minimum amount of token B after the swap and data required by the pools for the swaps.\n /// @dev Ensure that the pools are trusted before calling this function. The pools can steal users' tokens.\n function exactInput(ExactInputParams calldata params) public payable returns (uint256 amountOut) {\n // Pay the first pool directly.\n bento.transfer(params.tokenIn, msg.sender, params.path[0].pool, params.amountIn);\n // Call every pool in the path.\n // Pool `N` should transfer its output tokens to pool `N+1` directly.\n // The last pool should transfer its output tokens to the user.\n // If the user wants to unwrap `wETH`, the final destination should be this contract and\n // a batch call should be made to `unwrapWETH`.\n uint256 n = params.path.length;\n for (uint256 i = 0; i < n; i = _increment(i)) {\n amountOut = IPool(params.path[i].pool).swap(params.path[i].data);\n }\n // Ensure that the slippage wasn't too much. This assumes that the pool is honest.\n if (amountOut < params.amountOutMinimum) revert TooLittleReceived();\n }\n\n /// @notice Swaps token A to token B directly. It's the same as `exactInputSingle` except\n /// it takes raw ERC-20 tokens from the users and deposits them into `bento`.\n /// @param params This includes the address of token A, pool, amount of token A to swap,\n /// minimum amount of token B after the swap and data required by the pool for the swap.\n /// @dev Ensure that the pool is trusted before calling this function. The pool can steal users' tokens.\n function exactInputSingleWithNativeToken(ExactInputSingleParams calldata params) public payable returns (uint256 amountOut) {\n // Deposits the native ERC-20 token from the user into the pool's `bento`.\n _depositToBentoBox(params.tokenIn, params.pool, params.amountIn);\n // Trigger the swap in the pool.\n amountOut = IPool(params.pool).swap(params.data);\n // Ensure that the slippage wasn't too much. This assumes that the pool is honest.\n if (amountOut < params.amountOutMinimum) revert TooLittleReceived();\n }\n\n /// @notice Swaps token A to token B indirectly by using multiple hops. It's the same as `exactInput` except\n /// it takes raw ERC-20 tokens from the users and deposits them into `bento`.\n /// @param params This includes the addresses of the tokens, pools, amount of token A to swap,\n /// minimum amount of token B after the swap and data required by the pools for the swaps.\n /// @dev Ensure that the pools are trusted before calling this function. The pools can steal users' tokens.\n function exactInputWithNativeToken(ExactInputParams calldata params) public payable returns (uint256 amountOut) {\n // Deposits the native ERC-20 token from the user into the pool's `bento`.\n _depositToBentoBox(params.tokenIn, params.path[0].pool, params.amountIn);\n // Call every pool in the path.\n // Pool `N` should transfer its output tokens to pool `N+1` directly.\n // The last pool should transfer its output tokens to the user.\n uint256 n = params.path.length;\n for (uint256 i = 0; i < n; i = _increment(i)) {\n amountOut = IPool(params.path[i].pool).swap(params.path[i].data);\n }\n // Ensure that the slippage wasn't too much. This assumes that the pool is honest.\n if (amountOut < params.amountOutMinimum) revert TooLittleReceived();\n }\n\n /// @notice Swaps multiple input tokens to multiple output tokens using multiple paths, in different percentages.\n /// For example, you can swap 50 DAI + 100 USDC into 60% ETH and 40% BTC.\n /// @param params This includes everything needed for the swap. Look at the `ComplexPathParams` struct for more details.\n /// @dev This function is not optimized for single swaps and should only be used in complex cases where\n /// the amounts are large enough that minimizing slippage by using multiple paths is worth the extra gas.\n function complexPath(ComplexPathParams calldata params) public payable {\n // Deposit all initial tokens to respective pools and initiate the swaps.\n // Input tokens come from the user - output goes to following pools.\n uint256 n = params.initialPath.length;\n for (uint256 i = 0; i < n; i = _increment(i)) {\n if (params.initialPath[i].native) {\n _depositToBentoBox(params.initialPath[i].tokenIn, params.initialPath[i].pool, params.initialPath[i].amount);\n } else {\n bento.transfer(params.initialPath[i].tokenIn, msg.sender, params.initialPath[i].pool, params.initialPath[i].amount);\n }\n IPool(params.initialPath[i].pool).swap(params.initialPath[i].data);\n }\n // Do all the middle swaps. Input comes from previous pools.\n n = params.percentagePath.length;\n for (uint256 i = 0; i < n; i = _increment(i)) {\n uint256 balanceShares = bento.balanceOf(params.percentagePath[i].tokenIn, address(this));\n uint256 transferShares = (balanceShares * params.percentagePath[i].balancePercentage) / uint256(10)**8;\n bento.transfer(params.percentagePath[i].tokenIn, address(this), params.percentagePath[i].pool, transferShares);\n IPool(params.percentagePath[i].pool).swap(params.percentagePath[i].data);\n }\n // Ensure enough was received and transfer the ouput to the recipient.\n n = params.output.length;\n for (uint256 i = 0; i < n; i = _increment(i)) {\n uint256 balanceShares = bento.balanceOf(params.output[i].token, address(this));\n if (balanceShares < params.output[i].minAmount) revert TooLittleReceived();\n if (params.output[i].unwrapBento) {\n bento.withdraw(params.output[i].token, address(this), params.output[i].to, 0, balanceShares);\n } else {\n bento.transfer(params.output[i].token, address(this), params.output[i].to, balanceShares);\n }\n }\n }\n\n /// @notice Add liquidity to a pool.\n /// @param tokenInput Token address and amount to add as liquidity.\n /// @param pool Pool address to add liquidity to.\n /// @param minLiquidity Minimum output liquidity - caps slippage.\n /// @param data Data required by the pool to add liquidity.\n function addLiquidity(\n TokenInput[] calldata tokenInput,\n address pool,\n uint256 minLiquidity,\n bytes calldata data\n ) public payable returns (uint256 liquidity) {\n // Send all input tokens to the pool.\n uint256 n = tokenInput.length;\n for (uint256 i = 0; i < n; i = _increment(i)) {\n if (tokenInput[i].native) {\n _depositToBentoBox(tokenInput[i].token, pool, tokenInput[i].amount);\n } else {\n bento.transfer(tokenInput[i].token, msg.sender, pool, tokenInput[i].amount);\n }\n }\n liquidity = IPool(pool).mint(data);\n if (liquidity < minLiquidity) revert NotEnoughLiquidityMinted();\n }\n\n /// @notice Burn liquidity tokens to get back `bento` tokens.\n /// @param pool Pool address.\n /// @param liquidity Amount of liquidity tokens to burn.\n /// @param data Data required by the pool to burn liquidity.\n /// @param minWithdrawals Minimum amount of `bento` tokens to be returned.\n function burnLiquidity(\n address pool,\n uint256 liquidity,\n bytes calldata data,\n IPool.TokenAmount[] calldata minWithdrawals\n ) public payable {\n pool.safeTransferFrom(msg.sender, pool, liquidity);\n IPool.TokenAmount[] memory withdrawnLiquidity = IPool(pool).burn(data);\n uint256 n = minWithdrawals.length;\n for (uint256 i = 0; i < n; i = _increment(i)) {\n if (minWithdrawals[i].token != withdrawnLiquidity[i].token) revert IncorrectSlippageParams();\n if (withdrawnLiquidity[i].amount < minWithdrawals[i].amount) revert TooLittleReceived();\n }\n }\n\n /// @notice Burn liquidity tokens to get back `bento` tokens.\n /// @dev The tokens are swapped automatically and the output is in a single token.\n /// @param pool Pool address.\n /// @param liquidity Amount of liquidity tokens to burn.\n /// @param data Data required by the pool to burn liquidity.\n /// @param minWithdrawal Minimum amount of tokens to be returned.\n function burnLiquiditySingle(\n address pool,\n uint256 liquidity,\n bytes calldata data,\n uint256 minWithdrawal\n ) public payable {\n // Use 'liquidity = 0' for prefunding.\n pool.safeTransferFrom(msg.sender, pool, liquidity);\n uint256 withdrawn = IPool(pool).burnSingle(data);\n if (withdrawn < minWithdrawal) revert TooLittleReceived();\n }\n\n /// @notice Recover mistakenly sent tokens.\n function sweep(\n address token,\n address recipient,\n bool fromBento\n ) external payable {\n if (fromBento) {\n uint256 shares = bento.balanceOf(token, address(this));\n bento.transfer(token, address(this), recipient, shares);\n } else {\n uint256 amount = token == USE_NATIVE ? address(this).balance : (IERC20(token).balanceOf(address(this)) - 1);\n token == USE_NATIVE ? recipient.safeTransferETH(amount) : token.safeTransfer(recipient, amount);\n }\n }\n\n /// @notice Unwrap this contract's wETH into ETH.\n function unwrapWETH(address recipient) external payable {\n uint256 balance = IWETH9(wETH).balanceOf(address(this));\n IWETH9(wETH).withdraw(balance);\n recipient.safeTransferETH(balance);\n }\n\n /// @notice Wrapper function to allow pool deployment to be batched.\n function deployPool(address factory, bytes calldata deployData) external payable returns (address) {\n return masterDeployer.deployPool(factory, deployData);\n }\n\n /// @notice Wrapper function to allow bento set master contract approval to be batched, so the first trade can happen in one transaction.\n function approveMasterContract(\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external payable {\n bento.setMasterContractApproval(msg.sender, address(this), true, v, r, s);\n }\n\n /// @notice Call BentoBox harvest function to rebalance a BentoBox token strategy and ensure there are enough tokens available to withdraw a swap output.\n /// @dev Should be batched in before a swap.\n function harvest(address token, uint256 maxChangeAmount) external payable {\n bento.harvest(token, true, maxChangeAmount);\n }\n\n /// @notice Deposit from the user's wallet into BentoBox.\n /// @dev Amount is the native token amount. We let BentoBox do the conversion into shares.\n function _depositToBentoBox(\n address token,\n address recipient,\n uint256 amount\n ) internal {\n bento.deposit{value: token == USE_NATIVE ? amount : 0}(token, msg.sender, recipient, amount, 0);\n }\n\n function _increment(uint256 i) internal pure returns (uint256) {\n unchecked {\n return i + 1;\n }\n }\n}\n"
},
"contracts/abstract/Multicall.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\n/// @notice Helper utility that enables calling multiple local methods in a single call.\n/// @author Modified from Uniswap (https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/Multicall.sol)\n/// License-Identifier: GPL-2.0-or-later\nabstract contract Multicall {\n function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {\n results = new bytes[](data.length);\n \n for (uint256 i; i < data.length;) {\n (bool success, bytes memory result) = address(this).delegatecall(data[i]);\n\n if (!success) {\n // Next 5 lines from https://ethereum.stackexchange.com/a/83577\n if (result.length < 68) revert();\n assembly {\n result := add(result, 0x04)\n }\n revert(abi.decode(result, (string)));\n }\n\n results[i] = result;\n\n // cannot realistically overflow on human timescales\n unchecked {\n ++i;\n }\n }\n }\n}\n"
},
"contracts/abstract/SelfPermit.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol\";\n\nimport \"../interfaces/IERC20PermitAllowed.sol\";\n\nabstract contract SelfPermit {\n function selfPermit(\n address token,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public payable {\n IERC20Permit(token).permit(msg.sender, address(this), value, deadline, v, r, s);\n }\n\n function selfPermitIfNecessary(\n address token,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external payable {\n if (IERC20(token).allowance(msg.sender, address(this)) < value) selfPermit(token, value, deadline, v, r, s);\n }\n\n function selfPermitAllowed(\n address token,\n uint256 nonce,\n uint256 expiry,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public payable {\n IERC20PermitAllowed(token).permit(msg.sender, address(this), nonce, expiry, true, v, r, s);\n }\n\n function selfPermitAllowedIfNecessary(\n address token,\n uint256 nonce,\n uint256 expiry,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external payable {\n if (IERC20(token).allowance(msg.sender, address(this)) < type(uint256).max)\n selfPermitAllowed(token, nonce, expiry, v, r, s);\n }\n}"
},
"contracts/libraries/Transfer.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\nlibrary Transfer {\n /// @notice Transfers tokens from the targeted address to the given destination\n /// @notice Errors with 'STF' if transfer fails\n /// @param token The contract address of the token to be transferred\n /// @param from The originating address from which the tokens will be transferred\n /// @param to The destination address of the transfer\n /// @param value The amount to be transferred\n function safeTransferFrom(\n address token,\n address from,\n address to,\n uint256 value\n ) internal {\n (bool success, bytes memory data) =\n token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));\n require(success && (data.length == 0 || abi.decode(data, (bool))), \"STF\");\n }\n\n /// @notice Transfers tokens from msg.sender to a recipient\n /// @dev Errors with ST if transfer fails\n /// @param token The contract address of the token which will be transferred\n /// @param to The recipient of the transfer\n /// @param value The value of the transfer\n function safeTransfer(\n address token,\n address to,\n uint256 value\n ) internal {\n (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));\n require(success && (data.length == 0 || abi.decode(data, (bool))), \"ST\");\n }\n\n /// @notice Approves the stipulated contract to spend the given allowance in the given token\n /// @dev Errors with 'SA' if transfer fails\n /// @param token The contract address of the token to be approved\n /// @param to The target of the approval\n /// @param value The amount of the given token the target will be allowed to spend\n function safeApprove(\n address token,\n address to,\n uint256 value\n ) internal {\n (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));\n require(success && (data.length == 0 || abi.decode(data, (bool))), \"SA\");\n }\n\n /// @notice Transfers ETH to the recipient address\n /// @dev Fails with `STE`\n /// @param to The destination of the transfer\n /// @param value The value to be transferred\n function safeTransferETH(address to, uint256 value) internal {\n (bool success, ) = to.call{value: value}(new bytes(0));\n require(success, \"STE\");\n }\n}"
},
"contracts/interfaces/IBentoBoxMinimal.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\nimport \"../libraries/RebaseLibrary.sol\";\n\n/// @notice Minimal BentoBox vault interface.\n/// @dev `token` is aliased as `address` from `IERC20` for simplicity.\ninterface IBentoBoxMinimal {\n /// @notice Balance per ERC-20 token per account in shares.\n function balanceOf(address, address) external view returns (uint256);\n\n /// @dev Helper function to represent an `amount` of `token` in shares.\n /// @param token The ERC-20 token.\n /// @param amount The `token` amount.\n /// @param roundUp If the result `share` should be rounded up.\n /// @return share The token amount represented in shares.\n function toShare(\n address token,\n uint256 amount,\n bool roundUp\n ) external view returns (uint256 share);\n\n /// @dev Helper function to represent shares back into the `token` amount.\n /// @param token The ERC-20 token.\n /// @param share The amount of shares.\n /// @param roundUp If the result should be rounded up.\n /// @return amount The share amount back into native representation.\n function toAmount(\n address token,\n uint256 share,\n bool roundUp\n ) external view returns (uint256 amount);\n\n /// @notice Registers this contract so that users can approve it for BentoBox.\n function registerProtocol() external;\n\n /// @notice Deposit an amount of `token` represented in either `amount` or `share`.\n /// @param token The ERC-20 token to deposit.\n /// @param from which account to pull the tokens.\n /// @param to which account to push the tokens.\n /// @param amount Token amount in native representation to deposit.\n /// @param share Token amount represented in shares to deposit. Takes precedence over `amount`.\n /// @return amountOut The amount deposited.\n /// @return shareOut The deposited amount represented in shares.\n function deposit(\n address token,\n address from,\n address to,\n uint256 amount,\n uint256 share\n ) external payable returns (uint256 amountOut, uint256 shareOut);\n\n /// @notice Withdraws an amount of `token` from a user account.\n /// @param token_ The ERC-20 token to withdraw.\n /// @param from which user to pull the tokens.\n /// @param to which user to push the tokens.\n /// @param amount of tokens. Either one of `amount` or `share` needs to be supplied.\n /// @param share Like above, but `share` takes precedence over `amount`.\n function withdraw(\n address token_,\n address from,\n address to,\n uint256 amount,\n uint256 share\n ) external returns (uint256 amountOut, uint256 shareOut);\n\n /// @notice Transfer shares from a user account to another one.\n /// @param token The ERC-20 token to transfer.\n /// @param from which user to pull the tokens.\n /// @param to which user to push the tokens.\n /// @param share The amount of `token` in shares.\n function transfer(\n address token,\n address from,\n address to,\n uint256 share\n ) external;\n\n /// @dev Reads the Rebase `totals`from storage for a given token\n function totals(address token) external view returns (Rebase memory total);\n\n /// @dev Approves users' BentoBox assets to a \"master\" contract.\n function setMasterContractApproval(\n address user,\n address masterContract,\n bool approved,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n function harvest(\n address token,\n bool balance,\n uint256 maxChangeAmount\n ) external;\n}\n"
},
"contracts/interfaces/IMasterDeployer.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\n/// @notice Trident pool deployer interface.\ninterface IMasterDeployer {\n function barFee() external view returns (uint256);\n\n function barFeeTo() external view returns (address);\n\n function bento() external view returns (address);\n\n function migrator() external view returns (address);\n\n function pools(address pool) external view returns (bool);\n\n function deployPool(address factory, bytes calldata deployData) external returns (address);\n}\n"
},
"contracts/interfaces/IPool.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.5.0;\npragma experimental ABIEncoderV2;\n\n/// @notice Trident pool interface.\ninterface IPool {\n /// @notice Executes a swap from one token to another.\n /// @dev The input tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that were sent to the user.\n function swap(bytes calldata data) external returns (uint256 finalAmountOut);\n\n /// @notice Executes a swap from one token to another with a callback.\n /// @dev This function allows borrowing the output tokens and sending the input tokens in the callback.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that were sent to the user.\n function flashSwap(bytes calldata data) external returns (uint256 finalAmountOut);\n\n /// @notice Mints liquidity tokens.\n /// @param data ABI-encoded params that the pool requires.\n /// @return liquidity The amount of liquidity tokens that were minted for the user.\n function mint(bytes calldata data) external returns (uint256 liquidity);\n\n /// @notice Burns liquidity tokens.\n /// @dev The input LP tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return withdrawnAmounts The amount of various output tokens that were sent to the user.\n function burn(bytes calldata data) external returns (TokenAmount[] memory withdrawnAmounts);\n\n /// @notice Burns liquidity tokens for a single output token.\n /// @dev The input LP tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return amountOut The amount of output tokens that were sent to the user.\n function burnSingle(bytes calldata data) external returns (uint256 amountOut);\n\n /// @return A unique identifier for the pool type.\n function poolIdentifier() external pure returns (bytes32);\n\n /// @return An array of tokens supported by the pool.\n function getAssets() external view returns (address[] memory);\n\n /// @notice Simulates a trade and returns the expected output.\n /// @dev The pool does not need to include a trade simulator directly in itself - it can use a library.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that will be sent to the user if the trade is executed.\n function getAmountOut(bytes calldata data) external view returns (uint256 finalAmountOut);\n\n /// @notice Simulates a trade and returns the expected output.\n /// @dev The pool does not need to include a trade simulator directly in itself - it can use a library.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountIn The amount of input tokens that are required from the user if the trade is executed.\n function getAmountIn(bytes calldata data) external view returns (uint256 finalAmountIn);\n\n /// @dev This event must be emitted on all swaps.\n event Swap(address indexed recipient, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);\n\n /// @dev This struct frames output tokens for burns.\n struct TokenAmount {\n address token;\n uint256 amount;\n }\n}\n"
},
"contracts/interfaces/ITridentRouter.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\n/// @notice Trident pool router interface.\ninterface ITridentRouter {\n struct Path {\n address pool;\n bytes data;\n }\n\n struct ExactInputSingleParams {\n uint256 amountIn;\n uint256 amountOutMinimum;\n address pool;\n address tokenIn;\n bytes data;\n }\n\n struct ExactInputParams {\n address tokenIn;\n uint256 amountIn;\n uint256 amountOutMinimum;\n Path[] path;\n }\n\n struct TokenInput {\n address token;\n bool native;\n uint256 amount;\n }\n\n struct InitialPath {\n address tokenIn;\n address pool;\n bool native;\n uint256 amount;\n bytes data;\n }\n\n struct PercentagePath {\n address tokenIn;\n address pool;\n uint64 balancePercentage; // Multiplied by 10^6. 100% = 100_000_000\n bytes data;\n }\n\n struct Output {\n address token;\n address to;\n bool unwrapBento;\n uint256 minAmount;\n }\n\n struct ComplexPathParams {\n InitialPath[] initialPath;\n PercentagePath[] percentagePath;\n Output[] output;\n }\n}\n"
},
"contracts/interfaces/IWETH9.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\ninterface IWETH9 is IERC20 {\n function deposit() external payable;\n function withdraw(uint256) external;\n}"
},
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n"
},
"contracts/interfaces/IERC20PermitAllowed.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\ninterface IERC20PermitAllowed {\n function permit(\n address holder,\n address spender,\n uint256 nonce,\n uint256 expiry,\n bool allowed,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n}"
},
"@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n */\ninterface IERC20Permit {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n"
},
"contracts/libraries/RebaseLibrary.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity ^0.8;\n\nstruct Rebase {\n uint128 elastic;\n uint128 base;\n}\n\n/// @notice A rebasing library\nlibrary RebaseLibrary {\n /// @notice Calculates the base value in relationship to `elastic` and `total`.\n function toBase(Rebase memory total, uint256 elastic) internal pure returns (uint256 base) {\n if (total.elastic == 0) {\n base = elastic;\n } else {\n base = (elastic * total.base) / total.elastic;\n }\n }\n\n /// @notice Calculates the elastic value in relationship to `base` and `total`.\n function toElastic(Rebase memory total, uint256 base) internal pure returns (uint256 elastic) {\n if (total.base == 0) {\n elastic = base;\n } else {\n elastic = (base * total.elastic) / total.base;\n }\n }\n}\n"
},
"contracts/mocks/RouterMock.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\nimport \"../interfaces/IPoolFactory.sol\";\nimport \"../abstract/PoolDeployer.sol\";\nimport \"../TridentRouter.sol\";\n\ncontract RouterMock is TridentRouter {\n constructor(\n IBentoBoxMinimal bento,\n IMasterDeployer masterDeployer,\n address wETH\n ) TridentRouter(bento, masterDeployer, wETH) {\n //\n }\n}\n"
},
"contracts/interfaces/IPoolFactory.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\n/// @notice Trident pool deployment interface.\ninterface IPoolFactory {\n function deployPool(bytes calldata _deployData) external returns (address pool);\n\n function configAddress(bytes32 data) external returns (address pool);\n}\n"
},
"contracts/abstract/PoolDeployer.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later-only\n\npragma solidity >=0.8.0;\n\n/// @dev Custom Errors\nerror UnauthorisedDeployer();\nerror ZeroAddress();\nerror InvalidTokenOrder();\n\n/// @notice Trident pool deployer for whitelisted template factories.\n/// @author Mudit Gupta.\nabstract contract PoolDeployer {\n address public immutable masterDeployer;\n\n mapping(address => mapping(address => address[])) public pools;\n mapping(bytes32 => address) public configAddress;\n\n modifier onlyMaster() {\n if (msg.sender != masterDeployer) revert UnauthorisedDeployer();\n _;\n }\n\n constructor(address _masterDeployer) {\n if (_masterDeployer == address(0)) revert ZeroAddress();\n masterDeployer = _masterDeployer;\n }\n\n function _registerPool(\n address pool,\n address[] memory tokens,\n bytes32 salt\n ) internal onlyMaster {\n // Store the address of the deployed contract.\n configAddress[salt] = pool;\n // Attacker used underflow, it was not very effective. poolimon!\n // null token array would cause deployment to fail via out of bounds memory axis/gas limit.\n unchecked {\n for (uint256 i; i < tokens.length - 1; ++i) {\n if (tokens[i] >= tokens[i + 1]) revert InvalidTokenOrder();\n for (uint256 j = i + 1; j < tokens.length; ++j) {\n pools[tokens[i]][tokens[j]].push(pool);\n pools[tokens[j]][tokens[i]].push(pool);\n }\n }\n }\n }\n\n function poolsCount(address token0, address token1) external view returns (uint256 count) {\n count = pools[token0][token1].length;\n }\n\n function getPools(\n address token0,\n address token1,\n uint256 startIndex,\n uint256 count\n ) external view returns (address[] memory pairPools) {\n pairPools = new address[](count);\n for (uint256 i = 0; i < count; i++) {\n pairPools[i] = pools[token0][token1][startIndex + i];\n }\n }\n}\n"
},
"contracts/pool/stable/StablePoolFactory.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\nimport {PoolDeployer} from \"../../abstract/PoolDeployer.sol\";\nimport {StablePool} from \"./StablePool.sol\";\nimport {IStablePoolFactory} from \"../../interfaces/IStablePoolFactory.sol\";\nimport {IMasterDeployerV2} from \"../../interfaces/IMasterDeployerV2.sol\";\n\ncontract StablePoolFactory is IStablePoolFactory, PoolDeployer {\n bytes32 public constant bytecodeHash = keccak256(type(StablePool).creationCode);\n\n bytes private cachedDeployData;\n\n constructor(address _masterDeployer) PoolDeployer(_masterDeployer) {}\n\n function deployPool(bytes memory _deployData) external returns (address pool) {\n (address tokenA, address tokenB, uint256 swapFee) = abi.decode(_deployData, (address, address, uint256));\n\n if (tokenA > tokenB) {\n (tokenA, tokenB) = (tokenB, tokenA);\n }\n\n // Strips any extra data.\n _deployData = abi.encode(tokenA, tokenB, swapFee);\n\n address[] memory tokens = new address[](2);\n tokens[0] = tokenA;\n tokens[1] = tokenB;\n\n bytes32 salt = keccak256(_deployData);\n\n cachedDeployData = _deployData;\n\n pool = address(new StablePool{salt: salt}());\n\n cachedDeployData = \"\";\n\n _registerPool(pool, tokens, salt);\n }\n\n // This called in the StablePool constructor.\n function getDeployData() external view override returns (bytes memory, IMasterDeployerV2) {\n return (cachedDeployData, IMasterDeployerV2(masterDeployer));\n }\n\n function calculatePoolAddress(\n address token0,\n address token1,\n uint256 swapFee\n ) external view returns (address) {\n bytes32 salt = keccak256(abi.encode(token0, token1, swapFee));\n bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, bytecodeHash));\n return address(uint160(uint256(hash)));\n }\n}\n"
},
"contracts/pool/stable/StablePool.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.8.0;\n\nimport {ERC20} from \"@rari-capital/solmate/src/tokens/ERC20.sol\";\nimport {ReentrancyGuard} from \"@rari-capital/solmate/src/utils/ReentrancyGuard.sol\";\nimport {IBentoBoxMinimal} from \"../../interfaces/IBentoBoxMinimal.sol\";\nimport {IMasterDeployerV2} from \"../../interfaces/IMasterDeployerV2.sol\";\nimport {IPool} from \"../../interfaces/IPool.sol\";\nimport {IStablePoolFactory} from \"../../interfaces/IStablePoolFactory.sol\";\nimport {IERC20, SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport {TridentMath} from \"../../libraries/TridentMath.sol\";\nimport \"../../libraries/RebaseLibrary.sol\";\n\n/// @dev Custom Errors\nerror ZeroAddress();\nerror IdenticalAddress();\nerror InvalidSwapFee();\nerror InsufficientLiquidityMinted();\nerror InvalidAmounts();\nerror InvalidInputToken();\nerror PoolUninitialized();\nerror InvalidOutputToken();\n\n/// @notice Trident exchange pool template with stable swap (solidly exchange) for swapping between tightly correlated assets\n\ncontract StablePool is IPool, ERC20, ReentrancyGuard {\n using RebaseLibrary for Rebase;\n using SafeERC20 for IERC20;\n\n event Mint(address indexed sender, uint256 amount0, uint256 amount1, address indexed recipient);\n event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed recipient);\n event Sync(uint256 reserve0, uint256 reserve1);\n\n uint256 internal constant MINIMUM_LIQUIDITY = 1000;\n\n uint256 internal constant MAX_FEE = 10000; // @dev 100%.\n uint256 public immutable swapFee;\n uint256 internal immutable MAX_FEE_MINUS_SWAP_FEE;\n\n IBentoBoxMinimal public immutable bento;\n IMasterDeployerV2 public immutable masterDeployer;\n address public immutable token0;\n address public immutable token1;\n\n uint256 public barFee;\n address public barFeeTo;\n uint256 public kLast;\n\n uint256 internal reserve0;\n uint256 internal reserve1;\n\n uint256 public immutable decimals0;\n uint256 public immutable decimals1;\n\n bytes32 public constant poolIdentifier = \"Trident:StablePool\";\n\n constructor() ERC20(\"Sushi Stable LP Token\", \"SSLP\", 18) {\n (bytes memory _deployData, IMasterDeployerV2 _masterDeployer) = IStablePoolFactory(msg.sender).getDeployData();\n\n (address _token0, address _token1, uint256 _swapFee) = abi.decode(_deployData, (address, address, uint256));\n\n // Factory ensures that the tokens are sorted.\n if (_token0 == address(0)) revert ZeroAddress();\n if (_token0 == _token1) revert IdenticalAddress();\n if (_swapFee > MAX_FEE) revert InvalidSwapFee();\n\n token0 = _token0;\n token1 = _token1;\n swapFee = _swapFee;\n\n // This is safe from underflow - `swapFee` cannot exceed `MAX_FEE` per previous check.\n unchecked {\n MAX_FEE_MINUS_SWAP_FEE = MAX_FEE - _swapFee;\n }\n\n decimals0 = uint256(10)**(ERC20(_token0).decimals());\n decimals1 = uint256(10)**(ERC20(_token1).decimals());\n\n barFee = _masterDeployer.barFee();\n barFeeTo = _masterDeployer.barFeeTo();\n bento = IBentoBoxMinimal(_masterDeployer.bento());\n masterDeployer = _masterDeployer;\n }\n\n /// @dev Mints LP tokens - should be called via the router after transferring `bento` tokens.\n /// The router must ensure that sufficient LP tokens are minted by using the return value.\n function mint(bytes calldata data) public override nonReentrant returns (uint256 liquidity) {\n address recipient = abi.decode(data, (address));\n (uint256 _reserve0, uint256 _reserve1) = _getReserves();\n (uint256 balance0, uint256 balance1) = _balance();\n\n uint256 newLiq = _computeLiquidity(balance0, balance1);\n\n uint256 amount0 = balance0 - _reserve0;\n uint256 amount1 = balance1 - _reserve1;\n\n (uint256 fee0, uint256 fee1) = _nonOptimalMintFee(amount0, amount1, _reserve0, _reserve1);\n\n _reserve0 += uint112(fee0);\n _reserve1 += uint112(fee1);\n\n (uint256 _totalSupply, uint256 oldLiq) = _mintFee(_reserve0, _reserve1);\n\n if (_totalSupply == 0) {\n if (amount0 == 0 || amount1 == 0) revert InvalidAmounts();\n liquidity = newLiq - MINIMUM_LIQUIDITY;\n _mint(address(0), MINIMUM_LIQUIDITY);\n } else {\n liquidity = ((newLiq - oldLiq) * _totalSupply) / oldLiq;\n }\n\n if (liquidity == 0) revert InsufficientLiquidityMinted();\n\n _mint(recipient, liquidity);\n\n _updateReserves();\n\n kLast = newLiq;\n emit Mint(msg.sender, amount0, amount1, recipient);\n }\n\n /// @dev Burns LP tokens sent to this contract. The router must ensure that the user gets sufficient output tokens.\n function burn(bytes calldata data) public override nonReentrant returns (IPool.TokenAmount[] memory withdrawnAmounts) {\n (address recipient, bool unwrapBento) = abi.decode(data, (address, bool));\n (uint256 balance0, uint256 balance1) = _balance();\n uint256 liquidity = balanceOf[address(this)];\n\n (uint256 _totalSupply, ) = _mintFee(balance0, balance1);\n\n uint256 amount0 = (liquidity * balance0) / _totalSupply;\n uint256 amount1 = (liquidity * balance1) / _totalSupply;\n\n _burn(address(this), liquidity);\n _transfer(token0, amount0, recipient, unwrapBento);\n _transfer(token1, amount1, recipient, unwrapBento);\n\n _updateReserves();\n\n withdrawnAmounts = new TokenAmount[](2);\n withdrawnAmounts[0] = TokenAmount({token: token0, amount: amount0});\n withdrawnAmounts[1] = TokenAmount({token: token1, amount: amount1});\n\n kLast = _computeLiquidity(balance0 - amount0, balance1 - amount1);\n\n emit Burn(msg.sender, amount0, amount1, recipient);\n }\n\n function burnSingle(bytes calldata data) public override nonReentrant returns (uint256 amountOut) {\n (address tokenOut, address recipient, bool unwrapBento) = abi.decode(data, (address, address, bool));\n (uint256 _reserve0, uint256 _reserve1) = _getReserves();\n (uint256 balance0, uint256 balance1) = _balance();\n uint256 liquidity = balanceOf[address(this)];\n\n (uint256 _totalSupply, ) = _mintFee(balance0, balance1);\n\n uint256 amount0 = (liquidity * balance0) / _totalSupply;\n uint256 amount1 = (liquidity * balance1) / _totalSupply;\n\n kLast = _computeLiquidity(balance0 - amount0, balance1 - amount1);\n _burn(address(this), liquidity);\n\n unchecked {\n if (tokenOut == token1) {\n amount1 += _getAmountOut(amount0, _reserve0 - amount0, _reserve1 - amount1, true);\n _transfer(token1, amount1, recipient, unwrapBento);\n amountOut = amount1;\n amount0 = 0;\n } else {\n if (tokenOut != token0) revert InvalidOutputToken();\n amount0 += _getAmountOut(amount1, _reserve0 - amount0, _reserve1 - amount1, false);\n _transfer(token0, amount0, recipient, unwrapBento);\n amountOut = amount0;\n amount1 = 0;\n }\n }\n\n _updateReserves();\n\n emit Burn(msg.sender, amount0, amount1, recipient);\n }\n\n /// @dev Swaps one token for another. The router must prefund this contract and ensure there isn't too much slippage.\n function swap(bytes calldata data) public override nonReentrant returns (uint256 amountOut) {\n (address tokenIn, address recipient, bool unwrapBento) = abi.decode(data, (address, address, bool));\n (uint256 _reserve0, uint256 _reserve1, uint256 balance0, uint256 balance1) = _getReservesAndBalances();\n uint256 amountIn;\n address tokenOut;\n\n if (tokenIn == token0) {\n tokenOut = token1;\n unchecked {\n amountIn = balance0 - _reserve0;\n }\n amountOut = _getAmountOut(amountIn, _reserve0, _reserve1, true);\n } else {\n if (tokenIn != token1) revert InvalidInputToken();\n tokenOut = token0;\n unchecked {\n amountIn = balance1 - _reserve1;\n }\n amountOut = _getAmountOut(amountIn, _reserve0, _reserve1, false);\n }\n