From 092b008b70f7ba8ff4b40d9614811222d428369f Mon Sep 17 00:00:00 2001 From: RedVeil Date: Fri, 10 Feb 2023 16:47:42 +0100 Subject: [PATCH 01/21] wip - 1inch --- packages/foundry/src/vault/strategy/1inch.sol | 4692 +++++++++++++++++ .../strategy/Pool2SingleAssetCompounder.sol | 19 + 2 files changed, 4711 insertions(+) create mode 100644 packages/foundry/src/vault/strategy/1inch.sol diff --git a/packages/foundry/src/vault/strategy/1inch.sol b/packages/foundry/src/vault/strategy/1inch.sol new file mode 100644 index 00000000..bc163570 --- /dev/null +++ b/packages/foundry/src/vault/strategy/1inch.sol @@ -0,0 +1,4692 @@ +/** + *Submitted for verification at Etherscan.io on 2022-11-04 +*/ + +/* + ,▄▓▓██▌ ,╓▄▄▓▓▓▓▓▓▓▓▄▄▄,, + ,▓██▓███▓▄▓███▓╬╬╬╬╬╬╬╬╬╬╬╬╬▓███▓▄, + ▄█ ▓██╬╣███████╬▓▀╬╬▓▓▓████████████▓█████▄, + ▓██▌ ▓██╬╣██████╬▓▌ ██████████████████████▌╙╙▀ⁿ + ▐████████╬▓████▓▓█╨ ▄ ╟█████████▓▓╬╬╬╬╬▓▓█████▓▄ + └▀▓▓▄╓ ╟█▓╣█████▓██████▀ ╓█▌ ███████▓▓▓▓▓╬╬╬╬╬╬╬╬╬╬╬╬▓██▓▄ + └▀████▓▄╥ ▐██╬╬██████████╙ Æ▀─ ▓███▀╚╠╬╩▀▀███████▓▓╬╬╬╬╬╬╬╬╬██▄ + └▀██▓▀▀█████▓╬▓██████▀ ▄█████▒╠" └╙▓██████▓╬╬╬╬╬╬╬╬██▄ + └▀██▄,└╙▀▀████▌└╙ ^"▀╙╙╙"╙██ @▄ ╙▀███████╬╬╬╬╬╬╬██µ + └▀██▓▄, ██▌ ╒ ╙█▓ ]▓█▓╔ ▀███████▓╬╬╬╬╬▓█▌ + ▀█████ ▓ ╟█▌ ]╠██▓░▒╓ ▀████████╬╬╬╬╣█▌ + ▐████ ╓█▀█▌ ,██▌ ╚Å███▓▒▒╠╓ ╙█████████╬╬╬╣█▌ + └████ ▓█░░▓█ ▀▀▀ φ▒╫████▒▒▒▒╠╓ █████████▓╬╬▓█µ + ╘███µ ▌▄█▓▄▓▀` ,▀ ,╔╠░▓██████▌╠▒▒▒φ ██████████╬╬██ + ▐████µ╙▓▀` ,▀╙,╔╔φφφ╠░▄▓███████▌░▓╙▒▒▒╠ └██╬███████╬▓█⌐ + ╫██ ▓▌ ▌φ▒▒░▓██████████████▌▒░▓╚▒▒▒╠ ▓██╬▓██████╣█▌ + ██▌ ▌╔▒▒▄████████████████▒▒▒░▌╠▒▒▒≥▐██▓╬╬███████▌ + ██▌ ,╓φ╠▓«▒▒▓████▀ ▀█████████▌▒▒▒╟░▒▒▒▒▐███╬╬╣████▓█▌ + ▐██ ╠▒▄▓▓███▓████└ ▀████████▌▒▒░▌╚▒▒▒▐███▓╬╬████ ╙▌ + ███ ) ╠▒░░░▒░╬████▀ └████████░▒▒░╬∩▒▒▓████╬╬╣███ + ▓██ ╠╠▒▒▐█▀▀▌`░╫██ ███████▒▒▒▒░▒▒½█████╬╬╣███ + ███ ,█▄ ╠▒▒▒╫▌,▄▀,▒╫██ ╟██████▒▒▒░╣⌠▒▓█████╬╬╣██▌ + ╘██µ ██` ╠▒▒░██╬φ╠▄▓██` ██████░░▌φ╠░▓█████▓╬╬▓██ + ╟██ .φ╠▒░▄█▀░░▄██▀└ █████▌▒╣φ▒░▓██████╬╬╣██ + ▀██▄▄▄╓▄███████▀ ▐█████░▓φ▒▄███████▓╬╣██ + ╙▀▀▀██▀└ ████▓▄▀φ▄▓████████╬▓█▀ + ▓███╬╩╔╣██████████▓██└ + ╓████▀▄▓████████▀████▀ + ,▓███████████████─]██╙ + ,▄▓██████████████▀└ ╙ + ,╓▄▓███████████████▀╙ + `"▀▀▀████████▀▀▀▀`▄███▀▀└ + └└ + + + + 11\ 11\ 11\ 11\ 11\ 11\ 11\ + 1111 | \__| 11 | 111\ 11 | 11 | 11 | + \_11 | 11\ 1111111\ 1111111\ 1111111\ 1111\ 11 | 111111\ 111111\ 11\ 11\ 11\ 111111\ 111111\ 11 | 11\ + 11 | 11 |11 __11\ 11 _____|11 __11\ 11 11\11 |11 __11\\_11 _| 11 | 11 | 11 |11 __11\ 11 __11\ 11 | 11 | + 11 | 11 |11 | 11 |11 / 11 | 11 | 11 \1111 |11111111 | 11 | 11 | 11 | 11 |11 / 11 |11 | \__|111111 / + 11 | 11 |11 | 11 |11 | 11 | 11 | 11 |\111 |11 ____| 11 |11\ 11 | 11 | 11 |11 | 11 |11 | 11 _11< + 111111\ 11 |11 | 11 |\1111111\ 11 | 11 | 11 | \11 |\1111111\ \1111 |\11111\1111 |\111111 |11 | 11 | \11\ + \______|\__|\__| \__| \_______|\__| \__| \__| \__| \_______| \____/ \_____\____/ \______/ \__| \__| \__| + + + + 111111\ 11\ 11\ + 11 __11\ 11 | \__| + 11 / 11 | 111111\ 111111\ 111111\ 111111\ 111111\ 111111\ 111111\ 11\ 111111\ 1111111\ + 11111111 |11 __11\ 11 __11\ 11 __11\ 11 __11\ 11 __11\ \____11\\_11 _| 11 |11 __11\ 11 __11\ + 11 __11 |11 / 11 |11 / 11 |11 | \__|11111111 |11 / 11 | 1111111 | 11 | 11 |11 / 11 |11 | 11 | + 11 | 11 |11 | 11 |11 | 11 |11 | 11 ____|11 | 11 |11 __11 | 11 |11\ 11 |11 | 11 |11 | 11 | + 11 | 11 |\1111111 |\1111111 |11 | \1111111\ \1111111 |\1111111 | \1111 |11 |\111111 |11 | 11 | + \__| \__| \____11 | \____11 |\__| \_______| \____11 | \_______| \____/ \__| \______/ \__| \__| + 11\ 11 |11\ 11 | 11\ 11 | + \111111 |\111111 | \111111 | + \______/ \______/ \______/ + 1111111\ 11\ + 11 __11\ 11 | + 11 | 11 | 111111\ 11\ 11\ 111111\ 111111\ 111111\ + 1111111 |11 __11\ 11 | 11 |\_11 _| 11 __11\ 11 __11\ + 11 __11< 11 / 11 |11 | 11 | 11 | 11111111 |11 | \__| + 11 | 11 |11 | 11 |11 | 11 | 11 |11\ 11 ____|11 | + 11 | 11 |\111111 |\111111 | \1111 |\1111111\ 11 | + \__| \__| \______/ \______/ \____/ \_______|\__| +*/ + +// SPDX-License-Identifier: MIT + +// File contracts/interfaces/IClipperExchangeInterface.sol + + +pragma solidity 0.8.17; + +/// @title Clipper interface subset used in swaps +interface IClipperExchangeInterface { + struct Signature { + uint8 v; + bytes32 r; + bytes32 s; + } + + function sellEthForToken(address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external payable; + function sellTokenForEth(address inputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external; + function swap(address inputToken, address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external; +} + + +// File contracts/helpers/RouterErrors.sol + + +pragma solidity 0.8.17; + +library RouterErrors { + error ReturnAmountIsNotEnough(); + error InvalidMsgValue(); + error ERC20TransferFailed(); +} + + +// File @1inch/solidity-utils/contracts/EthReceiver.sol@v2.1.1 + + +pragma solidity ^0.8.0; + +abstract contract EthReceiver { + error EthDepositRejected(); + + receive() external payable { + _receive(); + } + + function _receive() internal virtual { + // solhint-disable-next-line avoid-tx-origin + if (msg.sender == tx.origin) revert EthDepositRejected(); + } +} + + +// File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.7.3 + +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} + + +// File @1inch/solidity-utils/contracts/interfaces/IDaiLikePermit.sol@v2.1.1 + + +pragma solidity ^0.8.0; + + +interface IDaiLikePermit { + function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external; +} + + +// File @1inch/solidity-utils/contracts/libraries/RevertReasonForwarder.sol@v2.1.1 + + +pragma solidity ^0.8.0; + +library RevertReasonForwarder { + function reRevert() internal pure { + // bubble up revert reason from latest external call + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + } +} + + +// File @openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol@v4.7.3 + +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + */ +interface IERC20Permit { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} + + +// File @1inch/solidity-utils/contracts/libraries/SafeERC20.sol@v2.1.1 + + +pragma solidity ^0.8.0; + + + + +library SafeERC20 { + error SafeTransferFailed(); + error SafeTransferFromFailed(); + error ForceApproveFailed(); + error SafeIncreaseAllowanceFailed(); + error SafeDecreaseAllowanceFailed(); + error SafePermitBadLength(); + + // Ensures method do not revert or return boolean `true`, admits call to non-smart-contract + function safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal { + bytes4 selector = token.transferFrom.selector; + bool success; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let data := mload(0x40) + + mstore(data, selector) + mstore(add(data, 0x04), from) + mstore(add(data, 0x24), to) + mstore(add(data, 0x44), amount) + success := call(gas(), token, 0, data, 100, 0x0, 0x20) + if success { + switch returndatasize() + case 0 { success := gt(extcodesize(token), 0) } + default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) } + } + } + if (!success) revert SafeTransferFromFailed(); + } + + // Ensures method do not revert or return boolean `true`, admits call to non-smart-contract + function safeTransfer(IERC20 token, address to, uint256 value) internal { + if (!_makeCall(token, token.transfer.selector, to, value)) { + revert SafeTransferFailed(); + } + } + + // If `approve(from, to, amount)` fails, try to `approve(from, to, 0)` before retry + function forceApprove(IERC20 token, address spender, uint256 value) internal { + if (!_makeCall(token, token.approve.selector, spender, value)) { + if (!_makeCall(token, token.approve.selector, spender, 0) || + !_makeCall(token, token.approve.selector, spender, value)) + { + revert ForceApproveFailed(); + } + } + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 allowance = token.allowance(address(this), spender); + if (value > type(uint256).max - allowance) revert SafeIncreaseAllowanceFailed(); + forceApprove(token, spender, allowance + value); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 allowance = token.allowance(address(this), spender); + if (value > allowance) revert SafeDecreaseAllowanceFailed(); + forceApprove(token, spender, allowance - value); + } + + function safePermit(IERC20 token, bytes calldata permit) internal { + bool success; + if (permit.length == 32 * 7) { + success = _makeCalldataCall(token, IERC20Permit.permit.selector, permit); + } else if (permit.length == 32 * 8) { + success = _makeCalldataCall(token, IDaiLikePermit.permit.selector, permit); + } else { + revert SafePermitBadLength(); + } + if (!success) RevertReasonForwarder.reRevert(); + } + + function _makeCall(IERC20 token, bytes4 selector, address to, uint256 amount) private returns(bool success) { + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let data := mload(0x40) + + mstore(data, selector) + mstore(add(data, 0x04), to) + mstore(add(data, 0x24), amount) + success := call(gas(), token, 0, data, 0x44, 0x0, 0x20) + if success { + switch returndatasize() + case 0 { success := gt(extcodesize(token), 0) } + default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) } + } + } + } + + function _makeCalldataCall(IERC20 token, bytes4 selector, bytes calldata args) private returns(bool success) { + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let len := add(4, args.length) + let data := mload(0x40) + + mstore(data, selector) + calldatacopy(add(data, 0x04), args.offset, args.length) + success := call(gas(), token, 0, data, len, 0x0, 0x20) + if success { + switch returndatasize() + case 0 { success := gt(extcodesize(token), 0) } + default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) } + } + } + } +} + + +// File @1inch/solidity-utils/contracts/interfaces/IWETH.sol@v2.1.1 + + +pragma solidity ^0.8.0; + +interface IWETH is IERC20 { + function deposit() external payable; + function withdraw(uint256 amount) external; +} + + +// File contracts/routers/ClipperRouter.sol + + +pragma solidity 0.8.17; + + + + + + +/// @title Clipper router that allows to use `ClipperExchangeInterface` for swaps +contract ClipperRouter is EthReceiver { + using SafeERC20 for IERC20; + + uint256 private constant _SIGNATURE_S_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + uint256 private constant _SIGNATURE_V_SHIFT = 255; + bytes6 private constant _INCH_TAG_WITH_LENGTH_PREFIX = "\x051INCH"; + IERC20 private constant _ETH = IERC20(address(0)); + IWETH private immutable _WETH; // solhint-disable-line var-name-mixedcase + + constructor(IWETH weth) { + _WETH = weth; + } + + /// @notice Same as `clipperSwapTo` but calls permit first, + /// allowing to approve token spending and make a swap in one transaction. + /// @dev See tests for examples + /// @param recipient Address that will receive swap funds + /// @param srcToken Source token + /// @param dstToken Destination token + /// @param inputAmount Amount of source tokens to swap + /// @param outputAmount Amount of destination tokens to receive + /// @param goodUntil Timestamp until the swap will be valid + /// @param r Clipper order signature (r part) + /// @param vs Clipper order signature (vs part) + /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls. + /// @return returnAmount Amount of destination tokens received + function clipperSwapToWithPermit( + IClipperExchangeInterface clipperExchange, + address payable recipient, + IERC20 srcToken, + IERC20 dstToken, + uint256 inputAmount, + uint256 outputAmount, + uint256 goodUntil, + bytes32 r, + bytes32 vs, + bytes calldata permit + ) external returns(uint256 returnAmount) { + srcToken.safePermit(permit); + return clipperSwapTo(clipperExchange, recipient, srcToken, dstToken, inputAmount, outputAmount, goodUntil, r, vs); + } + + /// @notice Same as `clipperSwapTo` but uses `msg.sender` as recipient + /// @param srcToken Source token + /// @param dstToken Destination token + /// @param inputAmount Amount of source tokens to swap + /// @param outputAmount Amount of destination tokens to receive + /// @param goodUntil Timestamp until the swap will be valid + /// @param r Clipper order signature (r part) + /// @param vs Clipper order signature (vs part) + /// @return returnAmount Amount of destination tokens received + function clipperSwap( + IClipperExchangeInterface clipperExchange, + IERC20 srcToken, + IERC20 dstToken, + uint256 inputAmount, + uint256 outputAmount, + uint256 goodUntil, + bytes32 r, + bytes32 vs + ) external payable returns(uint256 returnAmount) { + return clipperSwapTo(clipperExchange, payable(msg.sender), srcToken, dstToken, inputAmount, outputAmount, goodUntil, r, vs); + } + + /// @notice Performs swap using Clipper exchange. Wraps and unwraps ETH if required. + /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited + /// @param recipient Address that will receive swap funds + /// @param srcToken Source token + /// @param dstToken Destination token + /// @param inputAmount Amount of source tokens to swap + /// @param outputAmount Amount of destination tokens to receive + /// @param goodUntil Timestamp until the swap will be valid + /// @param r Clipper order signature (r part) + /// @param vs Clipper order signature (vs part) + /// @return returnAmount Amount of destination tokens received + function clipperSwapTo( + IClipperExchangeInterface clipperExchange, + address payable recipient, + IERC20 srcToken, + IERC20 dstToken, + uint256 inputAmount, + uint256 outputAmount, + uint256 goodUntil, + bytes32 r, + bytes32 vs + ) public payable returns(uint256 returnAmount) { + bool srcETH = srcToken == _ETH; + if (srcETH) { + if (msg.value != inputAmount) revert RouterErrors.InvalidMsgValue(); + } else if (srcToken == _WETH) { + srcETH = true; + if (msg.value != 0) revert RouterErrors.InvalidMsgValue(); + // _WETH.transferFrom(msg.sender, address(this), inputAmount); + // _WETH.withdraw(inputAmount); + address weth = address(_WETH); + bytes4 transferFromSelector = _WETH.transferFrom.selector; + bytes4 withdrawSelector = _WETH.withdraw.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + mstore(ptr, transferFromSelector) + mstore(add(ptr, 0x04), caller()) + mstore(add(ptr, 0x24), address()) + mstore(add(ptr, 0x44), inputAmount) + if iszero(call(gas(), weth, 0, ptr, 0x64, 0, 0)) { + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + + mstore(ptr, withdrawSelector) + mstore(add(ptr, 0x04), inputAmount) + if iszero(call(gas(), weth, 0, ptr, 0x24, 0, 0)) { + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + } + } else { + if (msg.value != 0) revert RouterErrors.InvalidMsgValue(); + srcToken.safeTransferFrom(msg.sender, address(clipperExchange), inputAmount); + } + + if (srcETH) { + // clipperExchange.sellEthForToken{value: inputAmount}(address(dstToken), inputAmount, outputAmount, goodUntil, recipient, signature, _INCH_TAG); + address clipper = address(clipperExchange); + bytes4 selector = clipperExchange.sellEthForToken.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + mstore(ptr, selector) + mstore(add(ptr, 0x04), dstToken) + mstore(add(ptr, 0x24), inputAmount) + mstore(add(ptr, 0x44), outputAmount) + mstore(add(ptr, 0x64), goodUntil) + mstore(add(ptr, 0x84), recipient) + mstore(add(ptr, 0xa4), add(27, shr(_SIGNATURE_V_SHIFT, vs))) + mstore(add(ptr, 0xc4), r) + mstore(add(ptr, 0xe4), and(vs, _SIGNATURE_S_MASK)) + mstore(add(ptr, 0x104), 0x120) + mstore(add(ptr, 0x143), _INCH_TAG_WITH_LENGTH_PREFIX) + if iszero(call(gas(), clipper, inputAmount, ptr, 0x149, 0, 0)) { + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + } + } else if (dstToken == _ETH || dstToken == _WETH) { + // clipperExchange.sellTokenForEth(address(srcToken), inputAmount, outputAmount, goodUntil, recipient, signature, _INCH_TAG); + address clipper = address(clipperExchange); + bytes4 selector = clipperExchange.sellTokenForEth.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + mstore(ptr, selector) + mstore(add(ptr, 0x04), srcToken) + mstore(add(ptr, 0x24), inputAmount) + mstore(add(ptr, 0x44), outputAmount) + mstore(add(ptr, 0x64), goodUntil) + switch iszero(dstToken) + case 1 { + mstore(add(ptr, 0x84), recipient) + } + default { + mstore(add(ptr, 0x84), address()) + } + mstore(add(ptr, 0xa4), add(27, shr(_SIGNATURE_V_SHIFT, vs))) + mstore(add(ptr, 0xc4), r) + mstore(add(ptr, 0xe4), and(vs, _SIGNATURE_S_MASK)) + mstore(add(ptr, 0x104), 0x120) + mstore(add(ptr, 0x143), _INCH_TAG_WITH_LENGTH_PREFIX) + if iszero(call(gas(), clipper, 0, ptr, 0x149, 0, 0)) { + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + } + + if (dstToken == _WETH) { + // _WETH.deposit{value: outputAmount}(); + // _WETH.transfer(recipient, outputAmount); + address weth = address(_WETH); + bytes4 depositSelector = _WETH.deposit.selector; + bytes4 transferSelector = _WETH.transfer.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + mstore(ptr, depositSelector) + if iszero(call(gas(), weth, outputAmount, ptr, 0x04, 0, 0)) { + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + + mstore(ptr, transferSelector) + mstore(add(ptr, 0x04), recipient) + mstore(add(ptr, 0x24), outputAmount) + if iszero(call(gas(), weth, 0, ptr, 0x44, 0, 0)) { + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + } + } + } else { + // clipperExchange.swap(address(srcToken), address(dstToken), inputAmount, outputAmount, goodUntil, recipient, signature, _INCH_TAG); + address clipper = address(clipperExchange); + bytes4 selector = clipperExchange.swap.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + mstore(ptr, selector) + mstore(add(ptr, 0x04), srcToken) + mstore(add(ptr, 0x24), dstToken) + mstore(add(ptr, 0x44), inputAmount) + mstore(add(ptr, 0x64), outputAmount) + mstore(add(ptr, 0x84), goodUntil) + mstore(add(ptr, 0xa4), recipient) + mstore(add(ptr, 0xc4), add(27, shr(_SIGNATURE_V_SHIFT, vs))) + mstore(add(ptr, 0xe4), r) + mstore(add(ptr, 0x104), and(vs, _SIGNATURE_S_MASK)) + mstore(add(ptr, 0x124), 0x140) + mstore(add(ptr, 0x163), _INCH_TAG_WITH_LENGTH_PREFIX) + if iszero(call(gas(), clipper, 0, ptr, 0x169, 0, 0)) { + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + } + } + + return outputAmount; + } +} + + +// File contracts/interfaces/IAggregationExecutor.sol + + +pragma solidity 0.8.17; + +/// @title Interface for making arbitrary calls during swap +interface IAggregationExecutor { + /// @notice propagates information about original msg.sender and executes arbitrary data + function execute(address msgSender) external payable; // 0x4b64e492 +} + + +// File @1inch/solidity-utils/contracts/interfaces/IERC20MetadataUppercase.sol@v2.1.1 + + +pragma solidity ^0.8.0; + + +interface IERC20MetadataUppercase { + function NAME() external view returns (string memory); // solhint-disable-line func-name-mixedcase + function SYMBOL() external view returns (string memory); // solhint-disable-line func-name-mixedcase +} + + +// File @1inch/solidity-utils/contracts/libraries/StringUtil.sol@v2.1.1 + + +pragma solidity ^0.8.0; + +/// @title Library with gas-efficient string operations +library StringUtil { + function toHex(uint256 value) internal pure returns (string memory) { + return toHex(abi.encodePacked(value)); + } + + function toHex(address value) internal pure returns (string memory) { + return toHex(abi.encodePacked(value)); + } + + function toHex(bytes memory data) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + function _toHex16(input) -> output { + output := or( + and(input, 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000), + shr(64, and(input, 0x0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000)) + ) + output := or( + and(output, 0xFFFFFFFF000000000000000000000000FFFFFFFF000000000000000000000000), + shr(32, and(output, 0x00000000FFFFFFFF000000000000000000000000FFFFFFFF0000000000000000)) + ) + output := or( + and(output, 0xFFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000), + shr(16, and(output, 0x0000FFFF000000000000FFFF000000000000FFFF000000000000FFFF00000000)) + ) + output := or( + and(output, 0xFF000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000), + shr(8, and(output, 0x00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000)) + ) + output := or( + shr(4, and(output, 0xF000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000)), + shr(8, and(output, 0x0F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F00)) + ) + output := add( + add(0x3030303030303030303030303030303030303030303030303030303030303030, output), + mul( + and( + shr(4, add(output, 0x0606060606060606060606060606060606060606060606060606060606060606)), + 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F + ), + 7 // Change 7 to 39 for lower case output + ) + ) + } + + result := mload(0x40) + let length := mload(data) + let resultLength := shl(1, length) + let toPtr := add(result, 0x22) // 32 bytes for length + 2 bytes for '0x' + mstore(0x40, add(toPtr, resultLength)) // move free memory pointer + mstore(add(result, 2), 0x3078) // 0x3078 is right aligned so we write to `result + 2` + // to store the last 2 bytes in the beginning of the string + mstore(result, add(resultLength, 2)) // extra 2 bytes for '0x' + + for { + let fromPtr := add(data, 0x20) + let endPtr := add(fromPtr, length) + } lt(fromPtr, endPtr) { + fromPtr := add(fromPtr, 0x20) + } { + let rawData := mload(fromPtr) + let hexData := _toHex16(rawData) + mstore(toPtr, hexData) + toPtr := add(toPtr, 0x20) + hexData := _toHex16(shl(128, rawData)) + mstore(toPtr, hexData) + toPtr := add(toPtr, 0x20) + } + } + } +} + + +// File @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol@v4.7.3 + +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + + +// File @1inch/solidity-utils/contracts/libraries/UniERC20.sol@v2.1.1 + + +pragma solidity ^0.8.0; + + + + + +library UniERC20 { + using SafeERC20 for IERC20; + + error InsufficientBalance(); + error ApproveCalledOnETH(); + error NotEnoughValue(); + error FromIsNotSender(); + error ToIsNotThis(); + error ETHTransferFailed(); + + uint256 private constant _RAW_CALL_GAS_LIMIT = 5000; + IERC20 private constant _ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); + IERC20 private constant _ZERO_ADDRESS = IERC20(address(0)); + + function isETH(IERC20 token) internal pure returns (bool) { + return (token == _ZERO_ADDRESS || token == _ETH_ADDRESS); + } + + function uniBalanceOf(IERC20 token, address account) internal view returns (uint256) { + if (isETH(token)) { + return account.balance; + } else { + return token.balanceOf(account); + } + } + + /// @dev note that this function does nothing in case of zero amount + function uniTransfer(IERC20 token, address payable to, uint256 amount) internal { + if (amount > 0) { + if (isETH(token)) { + if (address(this).balance < amount) revert InsufficientBalance(); + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = to.call{value: amount, gas: _RAW_CALL_GAS_LIMIT}(""); + if (!success) revert ETHTransferFailed(); + } else { + token.safeTransfer(to, amount); + } + } + } + + /// @dev note that this function does nothing in case of zero amount + function uniTransferFrom(IERC20 token, address payable from, address to, uint256 amount) internal { + if (amount > 0) { + if (isETH(token)) { + if (msg.value < amount) revert NotEnoughValue(); + if (from != msg.sender) revert FromIsNotSender(); + if (to != address(this)) revert ToIsNotThis(); + if (msg.value > amount) { + // Return remainder if exist + unchecked { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = from.call{value: msg.value - amount, gas: _RAW_CALL_GAS_LIMIT}(""); + if (!success) revert ETHTransferFailed(); + } + } + } else { + token.safeTransferFrom(from, to, amount); + } + } + } + + function uniSymbol(IERC20 token) internal view returns(string memory) { + return _uniDecode(token, IERC20Metadata.symbol.selector, IERC20MetadataUppercase.SYMBOL.selector); + } + + function uniName(IERC20 token) internal view returns(string memory) { + return _uniDecode(token, IERC20Metadata.name.selector, IERC20MetadataUppercase.NAME.selector); + } + + function uniApprove(IERC20 token, address to, uint256 amount) internal { + if (isETH(token)) revert ApproveCalledOnETH(); + + token.forceApprove(to, amount); + } + + /// 20K gas is provided to account for possible implementations of name/symbol + /// (token implementation might be behind proxy or store the value in storage) + function _uniDecode(IERC20 token, bytes4 lowerCaseSelector, bytes4 upperCaseSelector) private view returns(string memory result) { + if (isETH(token)) { + return "ETH"; + } + + (bool success, bytes memory data) = address(token).staticcall{ gas: 20000 }( + abi.encodeWithSelector(lowerCaseSelector) + ); + if (!success) { + (success, data) = address(token).staticcall{ gas: 20000 }( + abi.encodeWithSelector(upperCaseSelector) + ); + } + + if (success && data.length >= 0x40) { + (uint256 offset, uint256 len) = abi.decode(data, (uint256, uint256)); + if (offset == 0x20 && len > 0 && data.length == 0x40 + len) { + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + result := add(data, 0x20) + } + return result; + } + } + + if (success && data.length == 32) { + uint256 len = 0; + while (len < data.length && data[len] >= 0x20 && data[len] <= 0x7E) { + unchecked { + len++; + } + } + + if (len > 0) { + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + mstore(data, len) + } + return string(data); + } + } + + return StringUtil.toHex(address(token)); + } +} + + +// File contracts/routers/GenericRouter.sol + + +pragma solidity 0.8.17; + + + + + +contract GenericRouter is EthReceiver { + using UniERC20 for IERC20; + using SafeERC20 for IERC20; + + error ZeroMinReturn(); + error ZeroReturnAmount(); + + uint256 private constant _PARTIAL_FILL = 1 << 0; + uint256 private constant _REQUIRES_EXTRA_ETH = 1 << 1; + + struct SwapDescription { + IERC20 srcToken; + IERC20 dstToken; + address payable srcReceiver; + address payable dstReceiver; + uint256 amount; + uint256 minReturnAmount; + uint256 flags; + } + + /// @notice Performs a swap, delegating all calls encoded in `data` to `executor`. See tests for usage examples + /// @dev router keeps 1 wei of every token on the contract balance for gas optimisations reasons. This affects first swap of every token by leaving 1 wei on the contract. + /// @param executor Aggregation executor that executes calls described in `data` + /// @param desc Swap description + /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls. + /// @param data Encoded calls that `caller` should execute in between of swaps + /// @return returnAmount Resulting token amount + /// @return spentAmount Source token amount + function swap( + IAggregationExecutor executor, + SwapDescription calldata desc, + bytes calldata permit, + bytes calldata data + ) + external + payable + returns ( + uint256 returnAmount, + uint256 spentAmount + ) + { + if (desc.minReturnAmount == 0) revert ZeroMinReturn(); + + IERC20 srcToken = desc.srcToken; + IERC20 dstToken = desc.dstToken; + + bool srcETH = srcToken.isETH(); + if (desc.flags & _REQUIRES_EXTRA_ETH != 0) { + if (msg.value <= (srcETH ? desc.amount : 0)) revert RouterErrors.InvalidMsgValue(); + } else { + if (msg.value != (srcETH ? desc.amount : 0)) revert RouterErrors.InvalidMsgValue(); + } + + if (!srcETH) { + if (permit.length > 0) { + srcToken.safePermit(permit); + } + srcToken.safeTransferFrom(msg.sender, desc.srcReceiver, desc.amount); + } + + _execute(executor, msg.sender, desc.amount, data); + + spentAmount = desc.amount; + // we leave 1 wei on the router for gas optimisations reasons + returnAmount = dstToken.uniBalanceOf(address(this)); + if (returnAmount == 0) revert ZeroReturnAmount(); + unchecked { returnAmount--; } + + if (desc.flags & _PARTIAL_FILL != 0) { + uint256 unspentAmount = srcToken.uniBalanceOf(address(this)); + if (unspentAmount > 1) { + // we leave 1 wei on the router for gas optimisations reasons + unchecked { unspentAmount--; } + spentAmount -= unspentAmount; + srcToken.uniTransfer(payable(msg.sender), unspentAmount); + } + if (returnAmount * desc.amount < desc.minReturnAmount * spentAmount) revert RouterErrors.ReturnAmountIsNotEnough(); + } else { + if (returnAmount < desc.minReturnAmount) revert RouterErrors.ReturnAmountIsNotEnough(); + } + + address payable dstReceiver = (desc.dstReceiver == address(0)) ? payable(msg.sender) : desc.dstReceiver; + dstToken.uniTransfer(dstReceiver, returnAmount); + } + + function _execute( + IAggregationExecutor executor, + address srcTokenOwner, + uint256 inputAmount, + bytes calldata data + ) private { + bytes4 executeSelector = executor.execute.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + mstore(ptr, executeSelector) + mstore(add(ptr, 0x04), srcTokenOwner) + calldatacopy(add(ptr, 0x24), data.offset, data.length) + mstore(add(add(ptr, 0x24), data.length), inputAmount) + + if iszero(call(gas(), executor, callvalue(), ptr, add(0x44, data.length), 0, 0)) { + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + } + } +} + + +// File contracts/routers/UnoswapRouter.sol + + +pragma solidity 0.8.17; + + + + +contract UnoswapRouter is EthReceiver { + using SafeERC20 for IERC20; + + error ReservesCallFailed(); + error SwapAmountTooLarge(); + + bytes4 private constant _TRANSFER_FROM_CALL_SELECTOR = 0x23b872dd; + bytes4 private constant _WETH_DEPOSIT_CALL_SELECTOR = 0xd0e30db0; + bytes4 private constant _WETH_WITHDRAW_CALL_SELECTOR = 0x2e1a7d4d; + bytes4 private constant _ERC20_TRANSFER_CALL_SELECTOR = 0xa9059cbb; + uint256 private constant _ADDRESS_MASK = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff; + uint256 private constant _REVERSE_MASK = 0x8000000000000000000000000000000000000000000000000000000000000000; + uint256 private constant _WETH_MASK = 0x4000000000000000000000000000000000000000000000000000000000000000; + uint256 private constant _NUMERATOR_MASK = 0x0000000000000000ffffffff0000000000000000000000000000000000000000; + /// @dev WETH address is network-specific and needs to be changed before deployment. + /// It can not be moved to immutable as immutables are not supported in assembly + address private constant _WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + bytes4 private constant _UNISWAP_PAIR_RESERVES_CALL_SELECTOR = 0x0902f1ac; + bytes4 private constant _UNISWAP_PAIR_SWAP_CALL_SELECTOR = 0x022c0d9f; + uint256 private constant _DENOMINATOR = 1e9; + uint256 private constant _NUMERATOR_OFFSET = 160; + uint256 private constant _MAX_SWAP_AMOUNT = (1 << 112) - 1; // type(uint112).max; + + /// @notice Same as `unoswapTo` but calls permit first, + /// allowing to approve token spending and make a swap in one transaction. + /// @param recipient Address that will receive swapped funds + /// @param srcToken Source token + /// @param amount Amount of source tokens to swap + /// @param minReturn Minimal allowed returnAmount to make transaction commit + /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen + /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls. + /// See tests for examples + function unoswapToWithPermit( + address payable recipient, + IERC20 srcToken, + uint256 amount, + uint256 minReturn, + uint256[] calldata pools, + bytes calldata permit + ) external returns(uint256 returnAmount) { + srcToken.safePermit(permit); + return _unoswap(recipient, srcToken, amount, minReturn, pools); + } + + /// @notice Performs swap using Uniswap exchange. Wraps and unwraps ETH if required. + /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited + /// @param recipient Address that will receive swapped funds + /// @param srcToken Source token + /// @param amount Amount of source tokens to swap + /// @param minReturn Minimal allowed returnAmount to make transaction commit + /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen + function unoswapTo( + address payable recipient, + IERC20 srcToken, + uint256 amount, + uint256 minReturn, + uint256[] calldata pools + ) external payable returns(uint256 returnAmount) { + return _unoswap(recipient, srcToken, amount, minReturn, pools); + } + + /// @notice Performs swap using Uniswap exchange. Wraps and unwraps ETH if required. + /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited + /// @param srcToken Source token + /// @param amount Amount of source tokens to swap + /// @param minReturn Minimal allowed returnAmount to make transaction commit + /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen + function unoswap( + IERC20 srcToken, + uint256 amount, + uint256 minReturn, + uint256[] calldata pools + ) external payable returns(uint256 returnAmount) { + return _unoswap(payable(msg.sender), srcToken, amount, minReturn, pools); + } + + function _unoswap( + address payable recipient, + IERC20 srcToken, + uint256 amount, + uint256 minReturn, + uint256[] calldata pools + ) private returns(uint256 returnAmount) { + assembly { // solhint-disable-line no-inline-assembly + function reRevert() { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + + function validateERC20Transfer(status) { + if iszero(status) { + reRevert() + } + let success := or( + iszero(returndatasize()), // empty return data + and(gt(returndatasize(), 31), eq(mload(0), 1)) // true in return data + ) + if iszero(success) { + mstore(0, 0xf27f64e400000000000000000000000000000000000000000000000000000000) // ERC20TransferFailed() + revert(0, 4) + } + } + + function swap(emptyPtr, swapAmount, pair, reversed, numerator, to) -> ret { + mstore(emptyPtr, _UNISWAP_PAIR_RESERVES_CALL_SELECTOR) + if iszero(staticcall(gas(), pair, emptyPtr, 0x4, emptyPtr, 0x40)) { + reRevert() + } + if iszero(eq(returndatasize(), 0x60)) { + mstore(0, 0x85cd58dc00000000000000000000000000000000000000000000000000000000) // ReservesCallFailed() + revert(0, 4) + } + + let reserve0 := mload(emptyPtr) + let reserve1 := mload(add(emptyPtr, 0x20)) + if reversed { + let tmp := reserve0 + reserve0 := reserve1 + reserve1 := tmp + } + // this will not overflow as reserve0, reserve1 and ret fit to 112 bit and numerator and _DENOMINATOR fit to 32 bit + ret := mul(swapAmount, numerator) + ret := div(mul(ret, reserve1), add(ret, mul(reserve0, _DENOMINATOR))) + + mstore(emptyPtr, _UNISWAP_PAIR_SWAP_CALL_SELECTOR) + reversed := iszero(reversed) + mstore(add(emptyPtr, 0x04), mul(ret, iszero(reversed))) + mstore(add(emptyPtr, 0x24), mul(ret, reversed)) + mstore(add(emptyPtr, 0x44), to) + mstore(add(emptyPtr, 0x64), 0x80) + mstore(add(emptyPtr, 0x84), 0) + if iszero(call(gas(), pair, 0, emptyPtr, 0xa4, 0, 0)) { + reRevert() + } + } + + // make sure that input amount fits in 112 bit + if gt(amount, _MAX_SWAP_AMOUNT) { + mstore(0, 0xcf0b4d3a00000000000000000000000000000000000000000000000000000000) // SwapAmountTooLarge() + revert(0, 4) + } + + let emptyPtr := mload(0x40) + mstore(0x40, add(emptyPtr, 0xc0)) + + let poolsEndOffset := add(pools.offset, shl(5, pools.length)) + let rawPair := calldataload(pools.offset) + switch srcToken + case 0 { + if iszero(eq(amount, callvalue())) { + mstore(0, 0x1841b4e100000000000000000000000000000000000000000000000000000000) // InvalidMsgValue() + revert(0, 4) + } + + mstore(emptyPtr, _WETH_DEPOSIT_CALL_SELECTOR) + if iszero(call(gas(), _WETH, amount, emptyPtr, 0x4, 0, 0)) { + reRevert() + } + + mstore(emptyPtr, _ERC20_TRANSFER_CALL_SELECTOR) + mstore(add(emptyPtr, 0x4), and(rawPair, _ADDRESS_MASK)) + mstore(add(emptyPtr, 0x24), amount) + if iszero(call(gas(), _WETH, 0, emptyPtr, 0x44, 0, 0)) { + reRevert() + } + } + default { + if callvalue() { + mstore(0, 0x1841b4e100000000000000000000000000000000000000000000000000000000) // InvalidMsgValue() + revert(0, 4) + } + + mstore(emptyPtr, _TRANSFER_FROM_CALL_SELECTOR) + mstore(add(emptyPtr, 0x4), caller()) + mstore(add(emptyPtr, 0x24), and(rawPair, _ADDRESS_MASK)) + mstore(add(emptyPtr, 0x44), amount) + validateERC20Transfer( + call(gas(), srcToken, 0, emptyPtr, 0x64, 0, 0x20) + ) + } + + returnAmount := amount + + for {let i := add(pools.offset, 0x20)} lt(i, poolsEndOffset) {i := add(i, 0x20)} { + let nextRawPair := calldataload(i) + + returnAmount := swap( + emptyPtr, + returnAmount, + and(rawPair, _ADDRESS_MASK), + and(rawPair, _REVERSE_MASK), + shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)), + and(nextRawPair, _ADDRESS_MASK) + ) + + rawPair := nextRawPair + } + + switch and(rawPair, _WETH_MASK) + case 0 { + returnAmount := swap( + emptyPtr, + returnAmount, + and(rawPair, _ADDRESS_MASK), + and(rawPair, _REVERSE_MASK), + shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)), + recipient + ) + } + default { + returnAmount := swap( + emptyPtr, + returnAmount, + and(rawPair, _ADDRESS_MASK), + and(rawPair, _REVERSE_MASK), + shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)), + address() + ) + + mstore(emptyPtr, _WETH_WITHDRAW_CALL_SELECTOR) + mstore(add(emptyPtr, 0x04), returnAmount) + if iszero(call(gas(), _WETH, 0, emptyPtr, 0x24, 0, 0)) { + reRevert() + } + + if iszero(call(gas(), recipient, returnAmount, 0, 0, 0, 0)) { + reRevert() + } + } + } + if (returnAmount < minReturn) revert RouterErrors.ReturnAmountIsNotEnough(); + } +} + + +// File contracts/interfaces/IUniswapV3Pool.sol + +pragma solidity 0.8.17; + +interface IUniswapV3Pool { + /// @notice Swap token0 for token1, or token1 for token0 + /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback + /// @param recipient The address to receive the output of the swap + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this + /// value after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @param data Any data to be passed through to the callback + /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive + /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + + /// @notice The first of the two tokens of the pool, sorted by address + /// @return The token contract address + function token0() external view returns (address); + + /// @notice The second of the two tokens of the pool, sorted by address + /// @return The token contract address + function token1() external view returns (address); + + /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 + /// @return The fee + function fee() external view returns (uint24); +} + + +// File contracts/interfaces/IUniswapV3SwapCallback.sol + +pragma solidity 0.8.17; + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} + + +// File @openzeppelin/contracts/utils/Address.sol@v4.7.3 + +// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) + +pragma solidity ^0.8.1; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + + +// File @openzeppelin/contracts/utils/math/SafeCast.sol@v4.7.3 + +// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/SafeCast.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow + * checks. + * + * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can + * easily result in undesired exploitation or bugs, since developers usually + * assume that overflows raise errors. `SafeCast` restores this intuition by + * reverting the transaction when such an operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + * + * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing + * all math on `uint256` and `int256` and then downcasting. + */ +library SafeCast { + /** + * @dev Returns the downcasted uint248 from uint256, reverting on + * overflow (when the input is greater than largest uint248). + * + * Counterpart to Solidity's `uint248` operator. + * + * Requirements: + * + * - input must fit into 248 bits + * + * _Available since v4.7._ + */ + function toUint248(uint256 value) internal pure returns (uint248) { + require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits"); + return uint248(value); + } + + /** + * @dev Returns the downcasted uint240 from uint256, reverting on + * overflow (when the input is greater than largest uint240). + * + * Counterpart to Solidity's `uint240` operator. + * + * Requirements: + * + * - input must fit into 240 bits + * + * _Available since v4.7._ + */ + function toUint240(uint256 value) internal pure returns (uint240) { + require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits"); + return uint240(value); + } + + /** + * @dev Returns the downcasted uint232 from uint256, reverting on + * overflow (when the input is greater than largest uint232). + * + * Counterpart to Solidity's `uint232` operator. + * + * Requirements: + * + * - input must fit into 232 bits + * + * _Available since v4.7._ + */ + function toUint232(uint256 value) internal pure returns (uint232) { + require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits"); + return uint232(value); + } + + /** + * @dev Returns the downcasted uint224 from uint256, reverting on + * overflow (when the input is greater than largest uint224). + * + * Counterpart to Solidity's `uint224` operator. + * + * Requirements: + * + * - input must fit into 224 bits + * + * _Available since v4.2._ + */ + function toUint224(uint256 value) internal pure returns (uint224) { + require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits"); + return uint224(value); + } + + /** + * @dev Returns the downcasted uint216 from uint256, reverting on + * overflow (when the input is greater than largest uint216). + * + * Counterpart to Solidity's `uint216` operator. + * + * Requirements: + * + * - input must fit into 216 bits + * + * _Available since v4.7._ + */ + function toUint216(uint256 value) internal pure returns (uint216) { + require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits"); + return uint216(value); + } + + /** + * @dev Returns the downcasted uint208 from uint256, reverting on + * overflow (when the input is greater than largest uint208). + * + * Counterpart to Solidity's `uint208` operator. + * + * Requirements: + * + * - input must fit into 208 bits + * + * _Available since v4.7._ + */ + function toUint208(uint256 value) internal pure returns (uint208) { + require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits"); + return uint208(value); + } + + /** + * @dev Returns the downcasted uint200 from uint256, reverting on + * overflow (when the input is greater than largest uint200). + * + * Counterpart to Solidity's `uint200` operator. + * + * Requirements: + * + * - input must fit into 200 bits + * + * _Available since v4.7._ + */ + function toUint200(uint256 value) internal pure returns (uint200) { + require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits"); + return uint200(value); + } + + /** + * @dev Returns the downcasted uint192 from uint256, reverting on + * overflow (when the input is greater than largest uint192). + * + * Counterpart to Solidity's `uint192` operator. + * + * Requirements: + * + * - input must fit into 192 bits + * + * _Available since v4.7._ + */ + function toUint192(uint256 value) internal pure returns (uint192) { + require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits"); + return uint192(value); + } + + /** + * @dev Returns the downcasted uint184 from uint256, reverting on + * overflow (when the input is greater than largest uint184). + * + * Counterpart to Solidity's `uint184` operator. + * + * Requirements: + * + * - input must fit into 184 bits + * + * _Available since v4.7._ + */ + function toUint184(uint256 value) internal pure returns (uint184) { + require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits"); + return uint184(value); + } + + /** + * @dev Returns the downcasted uint176 from uint256, reverting on + * overflow (when the input is greater than largest uint176). + * + * Counterpart to Solidity's `uint176` operator. + * + * Requirements: + * + * - input must fit into 176 bits + * + * _Available since v4.7._ + */ + function toUint176(uint256 value) internal pure returns (uint176) { + require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits"); + return uint176(value); + } + + /** + * @dev Returns the downcasted uint168 from uint256, reverting on + * overflow (when the input is greater than largest uint168). + * + * Counterpart to Solidity's `uint168` operator. + * + * Requirements: + * + * - input must fit into 168 bits + * + * _Available since v4.7._ + */ + function toUint168(uint256 value) internal pure returns (uint168) { + require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits"); + return uint168(value); + } + + /** + * @dev Returns the downcasted uint160 from uint256, reverting on + * overflow (when the input is greater than largest uint160). + * + * Counterpart to Solidity's `uint160` operator. + * + * Requirements: + * + * - input must fit into 160 bits + * + * _Available since v4.7._ + */ + function toUint160(uint256 value) internal pure returns (uint160) { + require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits"); + return uint160(value); + } + + /** + * @dev Returns the downcasted uint152 from uint256, reverting on + * overflow (when the input is greater than largest uint152). + * + * Counterpart to Solidity's `uint152` operator. + * + * Requirements: + * + * - input must fit into 152 bits + * + * _Available since v4.7._ + */ + function toUint152(uint256 value) internal pure returns (uint152) { + require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits"); + return uint152(value); + } + + /** + * @dev Returns the downcasted uint144 from uint256, reverting on + * overflow (when the input is greater than largest uint144). + * + * Counterpart to Solidity's `uint144` operator. + * + * Requirements: + * + * - input must fit into 144 bits + * + * _Available since v4.7._ + */ + function toUint144(uint256 value) internal pure returns (uint144) { + require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits"); + return uint144(value); + } + + /** + * @dev Returns the downcasted uint136 from uint256, reverting on + * overflow (when the input is greater than largest uint136). + * + * Counterpart to Solidity's `uint136` operator. + * + * Requirements: + * + * - input must fit into 136 bits + * + * _Available since v4.7._ + */ + function toUint136(uint256 value) internal pure returns (uint136) { + require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits"); + return uint136(value); + } + + /** + * @dev Returns the downcasted uint128 from uint256, reverting on + * overflow (when the input is greater than largest uint128). + * + * Counterpart to Solidity's `uint128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + * + * _Available since v2.5._ + */ + function toUint128(uint256 value) internal pure returns (uint128) { + require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits"); + return uint128(value); + } + + /** + * @dev Returns the downcasted uint120 from uint256, reverting on + * overflow (when the input is greater than largest uint120). + * + * Counterpart to Solidity's `uint120` operator. + * + * Requirements: + * + * - input must fit into 120 bits + * + * _Available since v4.7._ + */ + function toUint120(uint256 value) internal pure returns (uint120) { + require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits"); + return uint120(value); + } + + /** + * @dev Returns the downcasted uint112 from uint256, reverting on + * overflow (when the input is greater than largest uint112). + * + * Counterpart to Solidity's `uint112` operator. + * + * Requirements: + * + * - input must fit into 112 bits + * + * _Available since v4.7._ + */ + function toUint112(uint256 value) internal pure returns (uint112) { + require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits"); + return uint112(value); + } + + /** + * @dev Returns the downcasted uint104 from uint256, reverting on + * overflow (when the input is greater than largest uint104). + * + * Counterpart to Solidity's `uint104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + * + * _Available since v4.7._ + */ + function toUint104(uint256 value) internal pure returns (uint104) { + require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits"); + return uint104(value); + } + + /** + * @dev Returns the downcasted uint96 from uint256, reverting on + * overflow (when the input is greater than largest uint96). + * + * Counterpart to Solidity's `uint96` operator. + * + * Requirements: + * + * - input must fit into 96 bits + * + * _Available since v4.2._ + */ + function toUint96(uint256 value) internal pure returns (uint96) { + require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits"); + return uint96(value); + } + + /** + * @dev Returns the downcasted uint88 from uint256, reverting on + * overflow (when the input is greater than largest uint88). + * + * Counterpart to Solidity's `uint88` operator. + * + * Requirements: + * + * - input must fit into 88 bits + * + * _Available since v4.7._ + */ + function toUint88(uint256 value) internal pure returns (uint88) { + require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); + return uint88(value); + } + + /** + * @dev Returns the downcasted uint80 from uint256, reverting on + * overflow (when the input is greater than largest uint80). + * + * Counterpart to Solidity's `uint80` operator. + * + * Requirements: + * + * - input must fit into 80 bits + * + * _Available since v4.7._ + */ + function toUint80(uint256 value) internal pure returns (uint80) { + require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits"); + return uint80(value); + } + + /** + * @dev Returns the downcasted uint72 from uint256, reverting on + * overflow (when the input is greater than largest uint72). + * + * Counterpart to Solidity's `uint72` operator. + * + * Requirements: + * + * - input must fit into 72 bits + * + * _Available since v4.7._ + */ + function toUint72(uint256 value) internal pure returns (uint72) { + require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits"); + return uint72(value); + } + + /** + * @dev Returns the downcasted uint64 from uint256, reverting on + * overflow (when the input is greater than largest uint64). + * + * Counterpart to Solidity's `uint64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + * + * _Available since v2.5._ + */ + function toUint64(uint256 value) internal pure returns (uint64) { + require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits"); + return uint64(value); + } + + /** + * @dev Returns the downcasted uint56 from uint256, reverting on + * overflow (when the input is greater than largest uint56). + * + * Counterpart to Solidity's `uint56` operator. + * + * Requirements: + * + * - input must fit into 56 bits + * + * _Available since v4.7._ + */ + function toUint56(uint256 value) internal pure returns (uint56) { + require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits"); + return uint56(value); + } + + /** + * @dev Returns the downcasted uint48 from uint256, reverting on + * overflow (when the input is greater than largest uint48). + * + * Counterpart to Solidity's `uint48` operator. + * + * Requirements: + * + * - input must fit into 48 bits + * + * _Available since v4.7._ + */ + function toUint48(uint256 value) internal pure returns (uint48) { + require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits"); + return uint48(value); + } + + /** + * @dev Returns the downcasted uint40 from uint256, reverting on + * overflow (when the input is greater than largest uint40). + * + * Counterpart to Solidity's `uint40` operator. + * + * Requirements: + * + * - input must fit into 40 bits + * + * _Available since v4.7._ + */ + function toUint40(uint256 value) internal pure returns (uint40) { + require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits"); + return uint40(value); + } + + /** + * @dev Returns the downcasted uint32 from uint256, reverting on + * overflow (when the input is greater than largest uint32). + * + * Counterpart to Solidity's `uint32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + * + * _Available since v2.5._ + */ + function toUint32(uint256 value) internal pure returns (uint32) { + require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); + return uint32(value); + } + + /** + * @dev Returns the downcasted uint24 from uint256, reverting on + * overflow (when the input is greater than largest uint24). + * + * Counterpart to Solidity's `uint24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + * + * _Available since v4.7._ + */ + function toUint24(uint256 value) internal pure returns (uint24) { + require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits"); + return uint24(value); + } + + /** + * @dev Returns the downcasted uint16 from uint256, reverting on + * overflow (when the input is greater than largest uint16). + * + * Counterpart to Solidity's `uint16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + * + * _Available since v2.5._ + */ + function toUint16(uint256 value) internal pure returns (uint16) { + require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits"); + return uint16(value); + } + + /** + * @dev Returns the downcasted uint8 from uint256, reverting on + * overflow (when the input is greater than largest uint8). + * + * Counterpart to Solidity's `uint8` operator. + * + * Requirements: + * + * - input must fit into 8 bits + * + * _Available since v2.5._ + */ + function toUint8(uint256 value) internal pure returns (uint8) { + require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits"); + return uint8(value); + } + + /** + * @dev Converts a signed int256 into an unsigned uint256. + * + * Requirements: + * + * - input must be greater than or equal to 0. + * + * _Available since v3.0._ + */ + function toUint256(int256 value) internal pure returns (uint256) { + require(value >= 0, "SafeCast: value must be positive"); + return uint256(value); + } + + /** + * @dev Returns the downcasted int248 from int256, reverting on + * overflow (when the input is less than smallest int248 or + * greater than largest int248). + * + * Counterpart to Solidity's `int248` operator. + * + * Requirements: + * + * - input must fit into 248 bits + * + * _Available since v4.7._ + */ + function toInt248(int256 value) internal pure returns (int248) { + require(value >= type(int248).min && value <= type(int248).max, "SafeCast: value doesn't fit in 248 bits"); + return int248(value); + } + + /** + * @dev Returns the downcasted int240 from int256, reverting on + * overflow (when the input is less than smallest int240 or + * greater than largest int240). + * + * Counterpart to Solidity's `int240` operator. + * + * Requirements: + * + * - input must fit into 240 bits + * + * _Available since v4.7._ + */ + function toInt240(int256 value) internal pure returns (int240) { + require(value >= type(int240).min && value <= type(int240).max, "SafeCast: value doesn't fit in 240 bits"); + return int240(value); + } + + /** + * @dev Returns the downcasted int232 from int256, reverting on + * overflow (when the input is less than smallest int232 or + * greater than largest int232). + * + * Counterpart to Solidity's `int232` operator. + * + * Requirements: + * + * - input must fit into 232 bits + * + * _Available since v4.7._ + */ + function toInt232(int256 value) internal pure returns (int232) { + require(value >= type(int232).min && value <= type(int232).max, "SafeCast: value doesn't fit in 232 bits"); + return int232(value); + } + + /** + * @dev Returns the downcasted int224 from int256, reverting on + * overflow (when the input is less than smallest int224 or + * greater than largest int224). + * + * Counterpart to Solidity's `int224` operator. + * + * Requirements: + * + * - input must fit into 224 bits + * + * _Available since v4.7._ + */ + function toInt224(int256 value) internal pure returns (int224) { + require(value >= type(int224).min && value <= type(int224).max, "SafeCast: value doesn't fit in 224 bits"); + return int224(value); + } + + /** + * @dev Returns the downcasted int216 from int256, reverting on + * overflow (when the input is less than smallest int216 or + * greater than largest int216). + * + * Counterpart to Solidity's `int216` operator. + * + * Requirements: + * + * - input must fit into 216 bits + * + * _Available since v4.7._ + */ + function toInt216(int256 value) internal pure returns (int216) { + require(value >= type(int216).min && value <= type(int216).max, "SafeCast: value doesn't fit in 216 bits"); + return int216(value); + } + + /** + * @dev Returns the downcasted int208 from int256, reverting on + * overflow (when the input is less than smallest int208 or + * greater than largest int208). + * + * Counterpart to Solidity's `int208` operator. + * + * Requirements: + * + * - input must fit into 208 bits + * + * _Available since v4.7._ + */ + function toInt208(int256 value) internal pure returns (int208) { + require(value >= type(int208).min && value <= type(int208).max, "SafeCast: value doesn't fit in 208 bits"); + return int208(value); + } + + /** + * @dev Returns the downcasted int200 from int256, reverting on + * overflow (when the input is less than smallest int200 or + * greater than largest int200). + * + * Counterpart to Solidity's `int200` operator. + * + * Requirements: + * + * - input must fit into 200 bits + * + * _Available since v4.7._ + */ + function toInt200(int256 value) internal pure returns (int200) { + require(value >= type(int200).min && value <= type(int200).max, "SafeCast: value doesn't fit in 200 bits"); + return int200(value); + } + + /** + * @dev Returns the downcasted int192 from int256, reverting on + * overflow (when the input is less than smallest int192 or + * greater than largest int192). + * + * Counterpart to Solidity's `int192` operator. + * + * Requirements: + * + * - input must fit into 192 bits + * + * _Available since v4.7._ + */ + function toInt192(int256 value) internal pure returns (int192) { + require(value >= type(int192).min && value <= type(int192).max, "SafeCast: value doesn't fit in 192 bits"); + return int192(value); + } + + /** + * @dev Returns the downcasted int184 from int256, reverting on + * overflow (when the input is less than smallest int184 or + * greater than largest int184). + * + * Counterpart to Solidity's `int184` operator. + * + * Requirements: + * + * - input must fit into 184 bits + * + * _Available since v4.7._ + */ + function toInt184(int256 value) internal pure returns (int184) { + require(value >= type(int184).min && value <= type(int184).max, "SafeCast: value doesn't fit in 184 bits"); + return int184(value); + } + + /** + * @dev Returns the downcasted int176 from int256, reverting on + * overflow (when the input is less than smallest int176 or + * greater than largest int176). + * + * Counterpart to Solidity's `int176` operator. + * + * Requirements: + * + * - input must fit into 176 bits + * + * _Available since v4.7._ + */ + function toInt176(int256 value) internal pure returns (int176) { + require(value >= type(int176).min && value <= type(int176).max, "SafeCast: value doesn't fit in 176 bits"); + return int176(value); + } + + /** + * @dev Returns the downcasted int168 from int256, reverting on + * overflow (when the input is less than smallest int168 or + * greater than largest int168). + * + * Counterpart to Solidity's `int168` operator. + * + * Requirements: + * + * - input must fit into 168 bits + * + * _Available since v4.7._ + */ + function toInt168(int256 value) internal pure returns (int168) { + require(value >= type(int168).min && value <= type(int168).max, "SafeCast: value doesn't fit in 168 bits"); + return int168(value); + } + + /** + * @dev Returns the downcasted int160 from int256, reverting on + * overflow (when the input is less than smallest int160 or + * greater than largest int160). + * + * Counterpart to Solidity's `int160` operator. + * + * Requirements: + * + * - input must fit into 160 bits + * + * _Available since v4.7._ + */ + function toInt160(int256 value) internal pure returns (int160) { + require(value >= type(int160).min && value <= type(int160).max, "SafeCast: value doesn't fit in 160 bits"); + return int160(value); + } + + /** + * @dev Returns the downcasted int152 from int256, reverting on + * overflow (when the input is less than smallest int152 or + * greater than largest int152). + * + * Counterpart to Solidity's `int152` operator. + * + * Requirements: + * + * - input must fit into 152 bits + * + * _Available since v4.7._ + */ + function toInt152(int256 value) internal pure returns (int152) { + require(value >= type(int152).min && value <= type(int152).max, "SafeCast: value doesn't fit in 152 bits"); + return int152(value); + } + + /** + * @dev Returns the downcasted int144 from int256, reverting on + * overflow (when the input is less than smallest int144 or + * greater than largest int144). + * + * Counterpart to Solidity's `int144` operator. + * + * Requirements: + * + * - input must fit into 144 bits + * + * _Available since v4.7._ + */ + function toInt144(int256 value) internal pure returns (int144) { + require(value >= type(int144).min && value <= type(int144).max, "SafeCast: value doesn't fit in 144 bits"); + return int144(value); + } + + /** + * @dev Returns the downcasted int136 from int256, reverting on + * overflow (when the input is less than smallest int136 or + * greater than largest int136). + * + * Counterpart to Solidity's `int136` operator. + * + * Requirements: + * + * - input must fit into 136 bits + * + * _Available since v4.7._ + */ + function toInt136(int256 value) internal pure returns (int136) { + require(value >= type(int136).min && value <= type(int136).max, "SafeCast: value doesn't fit in 136 bits"); + return int136(value); + } + + /** + * @dev Returns the downcasted int128 from int256, reverting on + * overflow (when the input is less than smallest int128 or + * greater than largest int128). + * + * Counterpart to Solidity's `int128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + * + * _Available since v3.1._ + */ + function toInt128(int256 value) internal pure returns (int128) { + require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits"); + return int128(value); + } + + /** + * @dev Returns the downcasted int120 from int256, reverting on + * overflow (when the input is less than smallest int120 or + * greater than largest int120). + * + * Counterpart to Solidity's `int120` operator. + * + * Requirements: + * + * - input must fit into 120 bits + * + * _Available since v4.7._ + */ + function toInt120(int256 value) internal pure returns (int120) { + require(value >= type(int120).min && value <= type(int120).max, "SafeCast: value doesn't fit in 120 bits"); + return int120(value); + } + + /** + * @dev Returns the downcasted int112 from int256, reverting on + * overflow (when the input is less than smallest int112 or + * greater than largest int112). + * + * Counterpart to Solidity's `int112` operator. + * + * Requirements: + * + * - input must fit into 112 bits + * + * _Available since v4.7._ + */ + function toInt112(int256 value) internal pure returns (int112) { + require(value >= type(int112).min && value <= type(int112).max, "SafeCast: value doesn't fit in 112 bits"); + return int112(value); + } + + /** + * @dev Returns the downcasted int104 from int256, reverting on + * overflow (when the input is less than smallest int104 or + * greater than largest int104). + * + * Counterpart to Solidity's `int104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + * + * _Available since v4.7._ + */ + function toInt104(int256 value) internal pure returns (int104) { + require(value >= type(int104).min && value <= type(int104).max, "SafeCast: value doesn't fit in 104 bits"); + return int104(value); + } + + /** + * @dev Returns the downcasted int96 from int256, reverting on + * overflow (when the input is less than smallest int96 or + * greater than largest int96). + * + * Counterpart to Solidity's `int96` operator. + * + * Requirements: + * + * - input must fit into 96 bits + * + * _Available since v4.7._ + */ + function toInt96(int256 value) internal pure returns (int96) { + require(value >= type(int96).min && value <= type(int96).max, "SafeCast: value doesn't fit in 96 bits"); + return int96(value); + } + + /** + * @dev Returns the downcasted int88 from int256, reverting on + * overflow (when the input is less than smallest int88 or + * greater than largest int88). + * + * Counterpart to Solidity's `int88` operator. + * + * Requirements: + * + * - input must fit into 88 bits + * + * _Available since v4.7._ + */ + function toInt88(int256 value) internal pure returns (int88) { + require(value >= type(int88).min && value <= type(int88).max, "SafeCast: value doesn't fit in 88 bits"); + return int88(value); + } + + /** + * @dev Returns the downcasted int80 from int256, reverting on + * overflow (when the input is less than smallest int80 or + * greater than largest int80). + * + * Counterpart to Solidity's `int80` operator. + * + * Requirements: + * + * - input must fit into 80 bits + * + * _Available since v4.7._ + */ + function toInt80(int256 value) internal pure returns (int80) { + require(value >= type(int80).min && value <= type(int80).max, "SafeCast: value doesn't fit in 80 bits"); + return int80(value); + } + + /** + * @dev Returns the downcasted int72 from int256, reverting on + * overflow (when the input is less than smallest int72 or + * greater than largest int72). + * + * Counterpart to Solidity's `int72` operator. + * + * Requirements: + * + * - input must fit into 72 bits + * + * _Available since v4.7._ + */ + function toInt72(int256 value) internal pure returns (int72) { + require(value >= type(int72).min && value <= type(int72).max, "SafeCast: value doesn't fit in 72 bits"); + return int72(value); + } + + /** + * @dev Returns the downcasted int64 from int256, reverting on + * overflow (when the input is less than smallest int64 or + * greater than largest int64). + * + * Counterpart to Solidity's `int64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + * + * _Available since v3.1._ + */ + function toInt64(int256 value) internal pure returns (int64) { + require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits"); + return int64(value); + } + + /** + * @dev Returns the downcasted int56 from int256, reverting on + * overflow (when the input is less than smallest int56 or + * greater than largest int56). + * + * Counterpart to Solidity's `int56` operator. + * + * Requirements: + * + * - input must fit into 56 bits + * + * _Available since v4.7._ + */ + function toInt56(int256 value) internal pure returns (int56) { + require(value >= type(int56).min && value <= type(int56).max, "SafeCast: value doesn't fit in 56 bits"); + return int56(value); + } + + /** + * @dev Returns the downcasted int48 from int256, reverting on + * overflow (when the input is less than smallest int48 or + * greater than largest int48). + * + * Counterpart to Solidity's `int48` operator. + * + * Requirements: + * + * - input must fit into 48 bits + * + * _Available since v4.7._ + */ + function toInt48(int256 value) internal pure returns (int48) { + require(value >= type(int48).min && value <= type(int48).max, "SafeCast: value doesn't fit in 48 bits"); + return int48(value); + } + + /** + * @dev Returns the downcasted int40 from int256, reverting on + * overflow (when the input is less than smallest int40 or + * greater than largest int40). + * + * Counterpart to Solidity's `int40` operator. + * + * Requirements: + * + * - input must fit into 40 bits + * + * _Available since v4.7._ + */ + function toInt40(int256 value) internal pure returns (int40) { + require(value >= type(int40).min && value <= type(int40).max, "SafeCast: value doesn't fit in 40 bits"); + return int40(value); + } + + /** + * @dev Returns the downcasted int32 from int256, reverting on + * overflow (when the input is less than smallest int32 or + * greater than largest int32). + * + * Counterpart to Solidity's `int32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + * + * _Available since v3.1._ + */ + function toInt32(int256 value) internal pure returns (int32) { + require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits"); + return int32(value); + } + + /** + * @dev Returns the downcasted int24 from int256, reverting on + * overflow (when the input is less than smallest int24 or + * greater than largest int24). + * + * Counterpart to Solidity's `int24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + * + * _Available since v4.7._ + */ + function toInt24(int256 value) internal pure returns (int24) { + require(value >= type(int24).min && value <= type(int24).max, "SafeCast: value doesn't fit in 24 bits"); + return int24(value); + } + + /** + * @dev Returns the downcasted int16 from int256, reverting on + * overflow (when the input is less than smallest int16 or + * greater than largest int16). + * + * Counterpart to Solidity's `int16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + * + * _Available since v3.1._ + */ + function toInt16(int256 value) internal pure returns (int16) { + require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits"); + return int16(value); + } + + /** + * @dev Returns the downcasted int8 from int256, reverting on + * overflow (when the input is less than smallest int8 or + * greater than largest int8). + * + * Counterpart to Solidity's `int8` operator. + * + * Requirements: + * + * - input must fit into 8 bits + * + * _Available since v3.1._ + */ + function toInt8(int256 value) internal pure returns (int8) { + require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits"); + return int8(value); + } + + /** + * @dev Converts an unsigned uint256 into a signed int256. + * + * Requirements: + * + * - input must be less than or equal to maxInt256. + * + * _Available since v3.0._ + */ + function toInt256(uint256 value) internal pure returns (int256) { + // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive + require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256"); + return int256(value); + } +} + + +// File contracts/routers/UnoswapV3Router.sol + + +pragma solidity 0.8.17; + + + + + + + + + +contract UnoswapV3Router is EthReceiver, IUniswapV3SwapCallback { + using Address for address payable; + using SafeERC20 for IERC20; + + error EmptyPools(); + error BadPool(); + + uint256 private constant _ONE_FOR_ZERO_MASK = 1 << 255; + uint256 private constant _WETH_UNWRAP_MASK = 1 << 253; + bytes32 private constant _POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; + bytes32 private constant _FF_FACTORY = 0xff1F98431c8aD98523631AE4a59f267346ea31F9840000000000000000000000; + // concatenation of token0(), token1() fee(), transfer() and transferFrom() selectors + bytes32 private constant _SELECTORS = 0x0dfe1681d21220a7ddca3f43a9059cbb23b872dd000000000000000000000000; + uint256 private constant _ADDRESS_MASK = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff; + /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) + uint160 private constant _MIN_SQRT_RATIO = 4295128739 + 1; + /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) + uint160 private constant _MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342 - 1; + IWETH private immutable _WETH; // solhint-disable-line var-name-mixedcase + + constructor(IWETH weth) { + _WETH = weth; + } + + /// @notice Same as `uniswapV3SwapTo` but calls permit first, + /// allowing to approve token spending and make a swap in one transaction. + /// @param recipient Address that will receive swap funds + /// @param srcToken Source token + /// @param amount Amount of source tokens to swap + /// @param minReturn Minimal allowed returnAmount to make transaction commit + /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen + /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls. + /// See tests for examples + function uniswapV3SwapToWithPermit( + address payable recipient, + IERC20 srcToken, + uint256 amount, + uint256 minReturn, + uint256[] calldata pools, + bytes calldata permit + ) external returns(uint256 returnAmount) { + srcToken.safePermit(permit); + return _uniswapV3Swap(recipient, amount, minReturn, pools); + } + + /// @notice Same as `uniswapV3SwapTo` but uses `msg.sender` as recipient + /// @param amount Amount of source tokens to swap + /// @param minReturn Minimal allowed returnAmount to make transaction commit + /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen + function uniswapV3Swap( + uint256 amount, + uint256 minReturn, + uint256[] calldata pools + ) external payable returns(uint256 returnAmount) { + return _uniswapV3Swap(payable(msg.sender), amount, minReturn, pools); + } + + /// @notice Performs swap using Uniswap V3 exchange. Wraps and unwraps ETH if required. + /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited + /// @param recipient Address that will receive swap funds + /// @param amount Amount of source tokens to swap + /// @param minReturn Minimal allowed returnAmount to make transaction commit + /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen + function uniswapV3SwapTo( + address payable recipient, + uint256 amount, + uint256 minReturn, + uint256[] calldata pools + ) external payable returns(uint256 returnAmount) { + return _uniswapV3Swap(recipient, amount, minReturn, pools); + } + + function _uniswapV3Swap( + address payable recipient, + uint256 amount, + uint256 minReturn, + uint256[] calldata pools + ) private returns(uint256 returnAmount) { + unchecked { + uint256 len = pools.length; + if (len == 0) revert EmptyPools(); + uint256 lastIndex = len - 1; + returnAmount = amount; + bool wrapWeth = msg.value > 0; + bool unwrapWeth = pools[lastIndex] & _WETH_UNWRAP_MASK > 0; + if (wrapWeth) { + if (msg.value != amount) revert RouterErrors.InvalidMsgValue(); + _WETH.deposit{value: amount}(); + } + if (len > 1) { + returnAmount = _makeSwap(address(this), wrapWeth ? address(this) : msg.sender, pools[0], returnAmount); + + for (uint256 i = 1; i < lastIndex; i++) { + returnAmount = _makeSwap(address(this), address(this), pools[i], returnAmount); + } + returnAmount = _makeSwap(unwrapWeth ? address(this) : recipient, address(this), pools[lastIndex], returnAmount); + } else { + returnAmount = _makeSwap(unwrapWeth ? address(this) : recipient, wrapWeth ? address(this) : msg.sender, pools[0], returnAmount); + } + + if (returnAmount < minReturn) revert RouterErrors.ReturnAmountIsNotEnough(); + + if (unwrapWeth) { + _WETH.withdraw(returnAmount); + recipient.sendValue(returnAmount); + } + } + } + + /// @inheritdoc IUniswapV3SwapCallback + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata /* data */ + ) external override { + assembly { // solhint-disable-line no-inline-assembly + function reRevert() { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + + function validateERC20Transfer(status) { + if iszero(status) { + reRevert() + } + let success := or( + iszero(returndatasize()), // empty return data + and(gt(returndatasize(), 31), eq(mload(0), 1)) // true in return data + ) + if iszero(success) { + mstore(0, 0xf27f64e400000000000000000000000000000000000000000000000000000000) // ERC20TransferFailed() + revert(0, 4) + } + } + + let emptyPtr := mload(0x40) + let resultPtr := add(emptyPtr, 0x15) // 0x15 = _FF_FACTORY size + + mstore(emptyPtr, _SELECTORS) + if iszero(staticcall(gas(), caller(), emptyPtr, 0x4, resultPtr, 0x20)) { + reRevert() + } + if iszero(staticcall(gas(), caller(), add(emptyPtr, 0x4), 0x4, add(resultPtr, 0x20), 0x20)) { + reRevert() + } + if iszero(staticcall(gas(), caller(), add(emptyPtr, 0x8), 0x4, add(resultPtr, 0x40), 0x20)) { + reRevert() + } + + let token + let amount + switch sgt(amount0Delta, 0) + case 1 { + token := mload(resultPtr) + amount := amount0Delta + } + default { + token := mload(add(resultPtr, 0x20)) + amount := amount1Delta + } + + mstore(emptyPtr, _FF_FACTORY) + mstore(resultPtr, keccak256(resultPtr, 0x60)) // Compute the inner hash in-place + mstore(add(resultPtr, 0x20), _POOL_INIT_CODE_HASH) + let pool := and(keccak256(emptyPtr, 0x55), _ADDRESS_MASK) + if xor(pool, caller()) { + mstore(0, 0xb2c0272200000000000000000000000000000000000000000000000000000000) // BadPool() + revert(0, 4) + } + + let payer := calldataload(0x84) + mstore(emptyPtr, _SELECTORS) + switch eq(payer, address()) + case 1 { + // token.safeTransfer(msg.sender,amount) + mstore(add(emptyPtr, 0x10), caller()) + mstore(add(emptyPtr, 0x30), amount) + validateERC20Transfer( + call(gas(), token, 0, add(emptyPtr, 0x0c), 0x44, 0, 0x20) + ) + } + default { + // token.safeTransferFrom(payer, msg.sender, amount); + mstore(add(emptyPtr, 0x14), payer) + mstore(add(emptyPtr, 0x34), caller()) + mstore(add(emptyPtr, 0x54), amount) + validateERC20Transfer( + call(gas(), token, 0, add(emptyPtr, 0x10), 0x64, 0, 0x20) + ) + } + } + } + + function _makeSwap(address recipient, address payer, uint256 pool, uint256 amount) private returns (uint256) { + bool zeroForOne = pool & _ONE_FOR_ZERO_MASK == 0; + if (zeroForOne) { + (, int256 amount1) = IUniswapV3Pool(address(uint160(pool))).swap( + recipient, + zeroForOne, + SafeCast.toInt256(amount), + _MIN_SQRT_RATIO, + abi.encode(payer) + ); + return SafeCast.toUint256(-amount1); + } else { + (int256 amount0,) = IUniswapV3Pool(address(uint160(pool))).swap( + recipient, + zeroForOne, + SafeCast.toInt256(amount), + _MAX_SQRT_RATIO, + abi.encode(payer) + ); + return SafeCast.toUint256(-amount0); + } + } +} + + +// File @1inch/solidity-utils/contracts/OnlyWethReceiver.sol@v2.1.1 + + +pragma solidity ^0.8.0; + +abstract contract OnlyWethReceiver is EthReceiver { + address private immutable _WETH; // solhint-disable-line var-name-mixedcase + + constructor(address weth) { + _WETH = address(weth); + } + + function _receive() internal virtual override { + if (msg.sender != _WETH) revert EthDepositRejected(); + } +} + + +// File @openzeppelin/contracts/interfaces/IERC1271.sol@v4.7.3 + +// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC1271 standard signature validation method for + * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. + * + * _Available since v4.1._ + */ +interface IERC1271 { + /** + * @dev Should return whether the signature provided is valid for the provided data + * @param hash Hash of the data to be signed + * @param signature Signature byte array associated with _data + */ + function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); +} + + +// File @1inch/solidity-utils/contracts/libraries/ECDSA.sol@v2.1.1 + + +pragma solidity ^0.8.0; + +library ECDSA { + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + uint256 private constant _S_BOUNDARY = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 + 1; + uint256 private constant _COMPACT_S_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + uint256 private constant _COMPACT_V_SHIFT = 255; + + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns(address signer) { + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + if lt(s, _S_BOUNDARY) { + let ptr := mload(0x40) + + mstore(ptr, hash) + mstore(add(ptr, 0x20), v) + mstore(add(ptr, 0x40), r) + mstore(add(ptr, 0x60), s) + mstore(0, 0) + pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20)) + signer := mload(0) + } + } + } + + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns(address signer) { + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let s := and(vs, _COMPACT_S_MASK) + if lt(s, _S_BOUNDARY) { + let ptr := mload(0x40) + + mstore(ptr, hash) + mstore(add(ptr, 0x20), add(27, shr(_COMPACT_V_SHIFT, vs))) + mstore(add(ptr, 0x40), r) + mstore(add(ptr, 0x60), s) + mstore(0, 0) + pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20)) + signer := mload(0) + } + } + } + + /// WARNING!!! + /// There is a known signature malleability issue with two representations of signatures! + /// Even though this function is able to verify both standard 65-byte and compact 64-byte EIP-2098 signatures + /// one should never use raw signatures for any kind of invalidation logic in their code. + /// As the standard and compact representations are interchangeable any invalidation logic that relies on + /// signature uniqueness will get rekt. + /// More info: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-4h98-2769-gh6h + function recover(bytes32 hash, bytes calldata signature) internal view returns(address signer) { + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + // memory[ptr:ptr+0x80] = (hash, v, r, s) + switch signature.length + case 65 { + // memory[ptr+0x20:ptr+0x80] = (v, r, s) + mstore(add(ptr, 0x20), byte(0, calldataload(add(signature.offset, 0x40)))) + calldatacopy(add(ptr, 0x40), signature.offset, 0x40) + } + case 64 { + // memory[ptr+0x20:ptr+0x80] = (v, r, s) + let vs := calldataload(add(signature.offset, 0x20)) + mstore(add(ptr, 0x20), add(27, shr(_COMPACT_V_SHIFT, vs))) + calldatacopy(add(ptr, 0x40), signature.offset, 0x20) + mstore(add(ptr, 0x60), and(vs, _COMPACT_S_MASK)) + } + default { + ptr := 0 + } + + if ptr { + if lt(mload(add(ptr, 0x60)), _S_BOUNDARY) { + // memory[ptr:ptr+0x20] = (hash) + mstore(ptr, hash) + + mstore(0, 0) + pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20)) + signer := mload(0) + } + } + } + } + + function recoverOrIsValidSignature(address signer, bytes32 hash, bytes calldata signature) internal view returns(bool success) { + if (signer == address(0)) return false; + if ((signature.length == 64 || signature.length == 65) && recover(hash, signature) == signer) { + return true; + } + return isValidSignature(signer, hash, signature); + } + + function recoverOrIsValidSignature(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns(bool success) { + if (signer == address(0)) return false; + if (recover(hash, v, r, s) == signer) { + return true; + } + return isValidSignature(signer, hash, v, r, s); + } + + function recoverOrIsValidSignature(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) { + if (signer == address(0)) return false; + if (recover(hash, r, vs) == signer) { + return true; + } + return isValidSignature(signer, hash, r, vs); + } + + function recoverOrIsValidSignature65(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) { + if (signer == address(0)) return false; + if (recover(hash, r, vs) == signer) { + return true; + } + return isValidSignature65(signer, hash, r, vs); + } + + function isValidSignature(address signer, bytes32 hash, bytes calldata signature) internal view returns(bool success) { + // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature)); + // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector; + bytes4 selector = IERC1271.isValidSignature.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + mstore(ptr, selector) + mstore(add(ptr, 0x04), hash) + mstore(add(ptr, 0x24), 0x40) + mstore(add(ptr, 0x44), signature.length) + calldatacopy(add(ptr, 0x64), signature.offset, signature.length) + if staticcall(gas(), signer, ptr, add(0x64, signature.length), 0, 0x20) { + success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20)) + } + } + } + + function isValidSignature(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns(bool success) { + bytes4 selector = IERC1271.isValidSignature.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + mstore(ptr, selector) + mstore(add(ptr, 0x04), hash) + mstore(add(ptr, 0x24), 0x40) + mstore(add(ptr, 0x44), 65) + mstore(add(ptr, 0x64), r) + mstore(add(ptr, 0x84), s) + mstore8(add(ptr, 0xa4), v) + if staticcall(gas(), signer, ptr, 0xa5, 0, 0x20) { + success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20)) + } + } + } + + function isValidSignature(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) { + // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, abi.encodePacked(r, vs))); + // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector; + bytes4 selector = IERC1271.isValidSignature.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + mstore(ptr, selector) + mstore(add(ptr, 0x04), hash) + mstore(add(ptr, 0x24), 0x40) + mstore(add(ptr, 0x44), 64) + mstore(add(ptr, 0x64), r) + mstore(add(ptr, 0x84), vs) + if staticcall(gas(), signer, ptr, 0xa4, 0, 0x20) { + success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20)) + } + } + } + + function isValidSignature65(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) { + // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, abi.encodePacked(r, vs & ~uint256(1 << 255), uint8(vs >> 255)))); + // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector; + bytes4 selector = IERC1271.isValidSignature.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + mstore(ptr, selector) + mstore(add(ptr, 0x04), hash) + mstore(add(ptr, 0x24), 0x40) + mstore(add(ptr, 0x44), 65) + mstore(add(ptr, 0x64), r) + mstore(add(ptr, 0x84), and(vs, _COMPACT_S_MASK)) + mstore8(add(ptr, 0xa4), add(27, shr(_COMPACT_V_SHIFT, vs))) + if staticcall(gas(), signer, ptr, 0xa5, 0, 0x20) { + success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20)) + } + } + } + + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 res) { + // 32 is the length in bytes of hash, enforced by the type signature above + // return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + mstore(0, 0x19457468657265756d205369676e6564204d6573736167653a0a333200000000) // "\x19Ethereum Signed Message:\n32" + mstore(28, hash) + res := keccak256(0, 60) + } + } + + function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 res) { + // return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + mstore(ptr, 0x1901000000000000000000000000000000000000000000000000000000000000) // "\x19\x01" + mstore(add(ptr, 0x02), domainSeparator) + mstore(add(ptr, 0x22), structHash) + res := keccak256(ptr, 66) + } + } +} + + +// File @1inch/limit-order-protocol/contracts/OrderRFQLib.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + +library OrderRFQLib { + struct OrderRFQ { + uint256 info; // lowest 64 bits is the order id, next 64 bits is the expiration timestamp + address makerAsset; + address takerAsset; + address maker; + address allowedSender; // equals to Zero address on public orders + uint256 makingAmount; + uint256 takingAmount; + } + + bytes32 constant internal _LIMIT_ORDER_RFQ_TYPEHASH = keccak256( + "OrderRFQ(" + "uint256 info," + "address makerAsset," + "address takerAsset," + "address maker," + "address allowedSender," + "uint256 makingAmount," + "uint256 takingAmount" + ")" + ); + + function hash(OrderRFQ memory order, bytes32 domainSeparator) internal pure returns(bytes32 result) { + bytes32 typehash = _LIMIT_ORDER_RFQ_TYPEHASH; + bytes32 orderHash; + // this assembly is memory unsafe :( + assembly { // solhint-disable-line no-inline-assembly + let ptr := sub(order, 0x20) + + // keccak256(abi.encode(_LIMIT_ORDER_RFQ_TYPEHASH, order)); + let tmp := mload(ptr) + mstore(ptr, typehash) + orderHash := keccak256(ptr, 0x100) + mstore(ptr, tmp) + } + return ECDSA.toTypedDataHash(domainSeparator, orderHash); + } +} + + +// File @openzeppelin/contracts/utils/Strings.sol@v4.7.3 + +// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol) + +pragma solidity ^0.8.0; + +/** + * @dev String operations. + */ +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + uint8 private constant _ADDRESS_LENGTH = 20; + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } + + /** + * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); + } +} + + +// File @openzeppelin/contracts/utils/cryptography/draft-EIP712.sol@v4.7.3 + +// OpenZeppelin Contracts v4.4.1 (utils/cryptography/draft-EIP712.sol) + +pragma solidity ^0.8.0; + +/** + * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. + * + * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, + * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding + * they need in their contracts using a combination of `abi.encode` and `keccak256`. + * + * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding + * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA + * ({_hashTypedDataV4}). + * + * The implementation of the domain separator was designed to be as efficient as possible while still properly updating + * the chain id to protect against replay attacks on an eventual fork of the chain. + * + * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method + * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. + * + * _Available since v3.4._ + */ +abstract contract EIP712 { + /* solhint-disable var-name-mixedcase */ + // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to + // invalidate the cached domain separator if the chain id changes. + bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; + uint256 private immutable _CACHED_CHAIN_ID; + address private immutable _CACHED_THIS; + + bytes32 private immutable _HASHED_NAME; + bytes32 private immutable _HASHED_VERSION; + bytes32 private immutable _TYPE_HASH; + + /* solhint-enable var-name-mixedcase */ + + /** + * @dev Initializes the domain separator and parameter caches. + * + * The meaning of `name` and `version` is specified in + * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: + * + * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. + * - `version`: the current major version of the signing domain. + * + * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart + * contract upgrade]. + */ + constructor(string memory name, string memory version) { + bytes32 hashedName = keccak256(bytes(name)); + bytes32 hashedVersion = keccak256(bytes(version)); + bytes32 typeHash = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ); + _HASHED_NAME = hashedName; + _HASHED_VERSION = hashedVersion; + _CACHED_CHAIN_ID = block.chainid; + _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion); + _CACHED_THIS = address(this); + _TYPE_HASH = typeHash; + } + + /** + * @dev Returns the domain separator for the current chain. + */ + function _domainSeparatorV4() internal view returns (bytes32) { + if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) { + return _CACHED_DOMAIN_SEPARATOR; + } else { + return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION); + } + } + + function _buildDomainSeparator( + bytes32 typeHash, + bytes32 nameHash, + bytes32 versionHash + ) private view returns (bytes32) { + return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this))); + } + + /** + * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this + * function returns the hash of the fully encoded EIP712 message for this domain. + * + * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: + * + * ```solidity + * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + * keccak256("Mail(address to,string contents)"), + * mailTo, + * keccak256(bytes(mailContents)) + * ))); + * address signer = ECDSA.recover(digest, signature); + * ``` + */ + function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { + return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); + } +} + + +// File @1inch/limit-order-protocol/contracts/libraries/Errors.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + +library Errors { + error InvalidMsgValue(); + error ETHTransferFailed(); +} + + +// File @1inch/limit-order-protocol/contracts/helpers/AmountCalculator.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + +/// @title A helper contract for calculations related to order amounts +library AmountCalculator { + /// @notice Calculates maker amount + /// @return Result Floored maker amount + function getMakingAmount(uint256 orderMakerAmount, uint256 orderTakerAmount, uint256 swapTakerAmount) internal pure returns(uint256) { + return swapTakerAmount * orderMakerAmount / orderTakerAmount; + } + + /// @notice Calculates taker amount + /// @return Result Ceiled taker amount + function getTakingAmount(uint256 orderMakerAmount, uint256 orderTakerAmount, uint256 swapMakerAmount) internal pure returns(uint256) { + return (swapMakerAmount * orderTakerAmount + orderMakerAmount - 1) / orderMakerAmount; + } +} + + +// File @1inch/limit-order-protocol/contracts/OrderRFQMixin.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + + + + + + + +/// @title RFQ Limit Order mixin +abstract contract OrderRFQMixin is EIP712, OnlyWethReceiver { + using SafeERC20 for IERC20; + using OrderRFQLib for OrderRFQLib.OrderRFQ; + + error RFQZeroTargetIsForbidden(); + error RFQPrivateOrder(); + error RFQBadSignature(); + error OrderExpired(); + error MakingAmountExceeded(); + error TakingAmountExceeded(); + error RFQSwapWithZeroAmount(); + error InvalidatedOrder(); + + /** + * @notice Emitted when RFQ gets filled + * @param orderHash Hash of the order + * @param makingAmount Amount of the maker asset that was transferred from maker to taker + */ + event OrderFilledRFQ( + bytes32 orderHash, + uint256 makingAmount + ); + + uint256 private constant _RAW_CALL_GAS_LIMIT = 5000; + uint256 private constant _MAKER_AMOUNT_FLAG = 1 << 255; + uint256 private constant _SIGNER_SMART_CONTRACT_HINT = 1 << 254; + uint256 private constant _IS_VALID_SIGNATURE_65_BYTES = 1 << 253; + uint256 private constant _UNWRAP_WETH_FLAG = 1 << 252; + uint256 private constant _AMOUNT_MASK = ~( + _MAKER_AMOUNT_FLAG | + _SIGNER_SMART_CONTRACT_HINT | + _IS_VALID_SIGNATURE_65_BYTES | + _UNWRAP_WETH_FLAG + ); + + IWETH private immutable _WETH; // solhint-disable-line var-name-mixedcase + mapping(address => mapping(uint256 => uint256)) private _invalidator; + + constructor(IWETH weth) OnlyWethReceiver(address(weth)) { + _WETH = weth; + } + + /** + * @notice Returns bitmask for double-spend invalidators based on lowest byte of order.info and filled quotes + * @param maker Maker address + * @param slot Slot number to return bitmask for + * @return result Each bit represents whether corresponding was already invalidated + */ + function invalidatorForOrderRFQ(address maker, uint256 slot) external view returns(uint256 /* result */) { + return _invalidator[maker][slot]; + } + + /** + * @notice Cancels order's quote + * @param orderInfo Order info (only order id in lowest 64 bits is used) + */ + function cancelOrderRFQ(uint256 orderInfo) external { + _invalidateOrder(msg.sender, orderInfo, 0); + } + + /// @notice Cancels multiple order's quotes + function cancelOrderRFQ(uint256 orderInfo, uint256 additionalMask) external { + _invalidateOrder(msg.sender, orderInfo, additionalMask); + } + + /** + * @notice Fills order's quote, fully or partially (whichever is possible) + * @param order Order quote to fill + * @param signature Signature to confirm quote ownership + * @param flagsAndAmount Fill configuration flags with amount packed in one slot + * @return filledMakingAmount Actual amount transferred from maker to taker + * @return filledTakingAmount Actual amount transferred from taker to maker + * @return orderHash Hash of the filled order + */ + function fillOrderRFQ( + OrderRFQLib.OrderRFQ memory order, + bytes calldata signature, + uint256 flagsAndAmount + ) external payable returns(uint256 /* filledMakingAmount */, uint256 /* filledTakingAmount */, bytes32 /* orderHash */) { + return fillOrderRFQTo(order, signature, flagsAndAmount, msg.sender); + } + + /** + * @notice Fills order's quote, fully or partially, with compact signature + * @param order Order quote to fill + * @param r R component of signature + * @param vs VS component of signature + * @param flagsAndAmount Fill configuration flags with amount packed in one slot + * - Bits 0-252 contain the amount to fill + * - Bit 253 is used to indicate whether signature is 64-bit (0) or 65-bit (1) + * - Bit 254 is used to indicate whether smart contract (1) signed the order or not (0) + * - Bit 255 is used to indicate whether maker (1) or taker amount (0) is given in the amount parameter + * @return filledMakingAmount Actual amount transferred from maker to taker + * @return filledTakingAmount Actual amount transferred from taker to maker + * @return orderHash Hash of the filled order + */ + function fillOrderRFQCompact( + OrderRFQLib.OrderRFQ memory order, + bytes32 r, + bytes32 vs, + uint256 flagsAndAmount + ) external payable returns(uint256 filledMakingAmount, uint256 filledTakingAmount, bytes32 orderHash) { + orderHash = order.hash(_domainSeparatorV4()); + if (flagsAndAmount & _SIGNER_SMART_CONTRACT_HINT != 0) { + if (flagsAndAmount & _IS_VALID_SIGNATURE_65_BYTES != 0) { + if (!ECDSA.isValidSignature65(order.maker, orderHash, r, vs)) revert RFQBadSignature(); + } else { + if (!ECDSA.isValidSignature(order.maker, orderHash, r, vs)) revert RFQBadSignature(); + } + } else { + if(!ECDSA.recoverOrIsValidSignature(order.maker, orderHash, r, vs)) revert RFQBadSignature(); + } + + (filledMakingAmount, filledTakingAmount) = _fillOrderRFQTo(order, flagsAndAmount, msg.sender); + emit OrderFilledRFQ(orderHash, filledMakingAmount); + } + + /** + * @notice Same as `fillOrderRFQTo` but calls permit first. + * It allows to approve token spending and make a swap in one transaction. + * Also allows to specify funds destination instead of `msg.sender` + * @param order Order quote to fill + * @param signature Signature to confirm quote ownership + * @param flagsAndAmount Fill configuration flags with amount packed in one slot + * @param target Address that will receive swap funds + * @param permit Should consist of abiencoded token address and encoded `IERC20Permit.permit` call. + * @return filledMakingAmount Actual amount transferred from maker to taker + * @return filledTakingAmount Actual amount transferred from taker to maker + * @return orderHash Hash of the filled order + * @dev See tests for examples + */ + function fillOrderRFQToWithPermit( + OrderRFQLib.OrderRFQ memory order, + bytes calldata signature, + uint256 flagsAndAmount, + address target, + bytes calldata permit + ) external returns(uint256 /* filledMakingAmount */, uint256 /* filledTakingAmount */, bytes32 /* orderHash */) { + IERC20(order.takerAsset).safePermit(permit); + return fillOrderRFQTo(order, signature, flagsAndAmount, target); + } + + /** + * @notice Same as `fillOrderRFQ` but allows to specify funds destination instead of `msg.sender` + * @param order Order quote to fill + * @param signature Signature to confirm quote ownership + * @param flagsAndAmount Fill configuration flags with amount packed in one slot + * @param target Address that will receive swap funds + * @return filledMakingAmount Actual amount transferred from maker to taker + * @return filledTakingAmount Actual amount transferred from taker to maker + * @return orderHash Hash of the filled order + */ + function fillOrderRFQTo( + OrderRFQLib.OrderRFQ memory order, + bytes calldata signature, + uint256 flagsAndAmount, + address target + ) public payable returns(uint256 filledMakingAmount, uint256 filledTakingAmount, bytes32 orderHash) { + orderHash = order.hash(_domainSeparatorV4()); + if (flagsAndAmount & _SIGNER_SMART_CONTRACT_HINT != 0) { + if (flagsAndAmount & _IS_VALID_SIGNATURE_65_BYTES != 0 && signature.length != 65) revert RFQBadSignature(); + if (!ECDSA.isValidSignature(order.maker, orderHash, signature)) revert RFQBadSignature(); + } else { + if(!ECDSA.recoverOrIsValidSignature(order.maker, orderHash, signature)) revert RFQBadSignature(); + } + (filledMakingAmount, filledTakingAmount) = _fillOrderRFQTo(order, flagsAndAmount, target); + emit OrderFilledRFQ(orderHash, filledMakingAmount); + } + + function _fillOrderRFQTo( + OrderRFQLib.OrderRFQ memory order, + uint256 flagsAndAmount, + address target + ) private returns(uint256 makingAmount, uint256 takingAmount) { + if (target == address(0)) revert RFQZeroTargetIsForbidden(); + + address maker = order.maker; + + // Validate order + if (order.allowedSender != address(0) && order.allowedSender != msg.sender) revert RFQPrivateOrder(); + + { // Stack too deep + uint256 info = order.info; + // Check time expiration + uint256 expiration = uint128(info) >> 64; + if (expiration != 0 && block.timestamp > expiration) revert OrderExpired(); // solhint-disable-line not-rely-on-time + _invalidateOrder(maker, info, 0); + } + + { // Stack too deep + uint256 orderMakingAmount = order.makingAmount; + uint256 orderTakingAmount = order.takingAmount; + uint256 amount = flagsAndAmount & _AMOUNT_MASK; + // Compute partial fill if needed + if (amount == 0) { + // zero amount means whole order + makingAmount = orderMakingAmount; + takingAmount = orderTakingAmount; + } + else if (flagsAndAmount & _MAKER_AMOUNT_FLAG != 0) { + if (amount > orderMakingAmount) revert MakingAmountExceeded(); + makingAmount = amount; + takingAmount = AmountCalculator.getTakingAmount(orderMakingAmount, orderTakingAmount, makingAmount); + } + else { + if (amount > orderTakingAmount) revert TakingAmountExceeded(); + takingAmount = amount; + makingAmount = AmountCalculator.getMakingAmount(orderMakingAmount, orderTakingAmount, takingAmount); + } + } + + if (makingAmount == 0 || takingAmount == 0) revert RFQSwapWithZeroAmount(); + + // Maker => Taker + if (order.makerAsset == address(_WETH) && flagsAndAmount & _UNWRAP_WETH_FLAG != 0) { + _WETH.transferFrom(maker, address(this), makingAmount); + _WETH.withdraw(makingAmount); + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = target.call{value: makingAmount, gas: _RAW_CALL_GAS_LIMIT}(""); + if (!success) revert Errors.ETHTransferFailed(); + } else { + IERC20(order.makerAsset).safeTransferFrom(maker, target, makingAmount); + } + + // Taker => Maker + if (order.takerAsset == address(_WETH) && msg.value > 0) { + if (msg.value != takingAmount) revert Errors.InvalidMsgValue(); + _WETH.deposit{ value: takingAmount }(); + _WETH.transfer(maker, takingAmount); + } else { + if (msg.value != 0) revert Errors.InvalidMsgValue(); + IERC20(order.takerAsset).safeTransferFrom(msg.sender, maker, takingAmount); + } + } + + function _invalidateOrder(address maker, uint256 orderInfo, uint256 additionalMask) private { + uint256 invalidatorSlot = uint64(orderInfo) >> 8; + uint256 invalidatorBits = (1 << uint8(orderInfo)) | additionalMask; + mapping(uint256 => uint256) storage invalidatorStorage = _invalidator[maker]; + uint256 invalidator = invalidatorStorage[invalidatorSlot]; + if (invalidator & invalidatorBits == invalidatorBits) revert InvalidatedOrder(); + invalidatorStorage[invalidatorSlot] = invalidator | invalidatorBits; + } +} + + +// File @1inch/limit-order-protocol/contracts/OrderLib.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + +library OrderLib { + struct Order { + uint256 salt; + address makerAsset; + address takerAsset; + address maker; + address receiver; + address allowedSender; // equals to Zero address on public orders + uint256 makingAmount; + uint256 takingAmount; + uint256 offsets; + // bytes makerAssetData; + // bytes takerAssetData; + // bytes getMakingAmount; // this.staticcall(abi.encodePacked(bytes, swapTakerAmount)) => (swapMakerAmount) + // bytes getTakingAmount; // this.staticcall(abi.encodePacked(bytes, swapMakerAmount)) => (swapTakerAmount) + // bytes predicate; // this.staticcall(bytes) => (bool) + // bytes permit; // On first fill: permit.1.call(abi.encodePacked(permit.selector, permit.2)) + // bytes preInteraction; + // bytes postInteraction; + bytes interactions; // concat(makerAssetData, takerAssetData, getMakingAmount, getTakingAmount, predicate, permit, preIntercation, postInteraction) + } + + bytes32 constant internal _LIMIT_ORDER_TYPEHASH = keccak256( + "Order(" + "uint256 salt," + "address makerAsset," + "address takerAsset," + "address maker," + "address receiver," + "address allowedSender," + "uint256 makingAmount," + "uint256 takingAmount," + "uint256 offsets," + "bytes interactions" + ")" + ); + + enum DynamicField { + MakerAssetData, + TakerAssetData, + GetMakingAmount, + GetTakingAmount, + Predicate, + Permit, + PreInteraction, + PostInteraction + } + + function getterIsFrozen(bytes calldata getter) internal pure returns(bool) { + return getter.length == 1 && getter[0] == "x"; + } + + function _get(Order calldata order, DynamicField field) private pure returns(bytes calldata) { + uint256 bitShift = uint256(field) << 5; // field * 32 + return order.interactions[ + uint32((order.offsets << 32) >> bitShift): + uint32(order.offsets >> bitShift) + ]; + } + + function makerAssetData(Order calldata order) internal pure returns(bytes calldata) { + return _get(order, DynamicField.MakerAssetData); + } + + function takerAssetData(Order calldata order) internal pure returns(bytes calldata) { + return _get(order, DynamicField.TakerAssetData); + } + + function getMakingAmount(Order calldata order) internal pure returns(bytes calldata) { + return _get(order, DynamicField.GetMakingAmount); + } + + function getTakingAmount(Order calldata order) internal pure returns(bytes calldata) { + return _get(order, DynamicField.GetTakingAmount); + } + + function predicate(Order calldata order) internal pure returns(bytes calldata) { + return _get(order, DynamicField.Predicate); + } + + function permit(Order calldata order) internal pure returns(bytes calldata) { + return _get(order, DynamicField.Permit); + } + + function preInteraction(Order calldata order) internal pure returns(bytes calldata) { + return _get(order, DynamicField.PreInteraction); + } + + function postInteraction(Order calldata order) internal pure returns(bytes calldata) { + return _get(order, DynamicField.PostInteraction); + } + + function hash(Order calldata order, bytes32 domainSeparator) internal pure returns(bytes32 result) { + bytes calldata interactions = order.interactions; + bytes32 typehash = _LIMIT_ORDER_TYPEHASH; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + + // keccak256(abi.encode(_LIMIT_ORDER_TYPEHASH, orderWithoutInteractions, keccak256(order.interactions))); + calldatacopy(ptr, interactions.offset, interactions.length) + mstore(add(ptr, 0x140), keccak256(ptr, interactions.length)) + calldatacopy(add(ptr, 0x20), order, 0x120) + mstore(ptr, typehash) + result := keccak256(ptr, 0x160) + } + result = ECDSA.toTypedDataHash(domainSeparator, result); + } +} + + +// File @1inch/limit-order-protocol/contracts/libraries/ArgumentsDecoder.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + +/// @title Library with gas efficient alternatives to `abi.decode` +library ArgumentsDecoder { + error IncorrectDataLength(); + + function decodeUint256(bytes calldata data, uint256 offset) internal pure returns(uint256 value) { + unchecked { if (data.length < offset + 32) revert IncorrectDataLength(); } + // no memory ops inside so this insertion is automatically memory safe + assembly { // solhint-disable-line no-inline-assembly + value := calldataload(add(data.offset, offset)) + } + } + + function decodeSelector(bytes calldata data) internal pure returns(bytes4 value) { + if (data.length < 4) revert IncorrectDataLength(); + // no memory ops inside so this insertion is automatically memory safe + assembly { // solhint-disable-line no-inline-assembly + value := calldataload(data.offset) + } + } + + function decodeTailCalldata(bytes calldata data, uint256 tailOffset) internal pure returns(bytes calldata args) { + if (data.length < tailOffset) revert IncorrectDataLength(); + // no memory ops inside so this insertion is automatically memory safe + assembly { // solhint-disable-line no-inline-assembly + args.offset := add(data.offset, tailOffset) + args.length := sub(data.length, tailOffset) + } + } + + function decodeTargetAndCalldata(bytes calldata data) internal pure returns(address target, bytes calldata args) { + if (data.length < 20) revert IncorrectDataLength(); + // no memory ops inside so this insertion is automatically memory safe + assembly { // solhint-disable-line no-inline-assembly + target := shr(96, calldataload(data.offset)) + args.offset := add(data.offset, 20) + args.length := sub(data.length, 20) + } + } +} + + +// File @1inch/limit-order-protocol/contracts/helpers/NonceManager.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + +/// @title A helper contract for managing nonce of tx sender +contract NonceManager { + error AdvanceNonceFailed(); + event NonceIncreased(address indexed maker, uint256 newNonce); + + mapping(address => uint256) public nonce; + + /// @notice Advances nonce by one + function increaseNonce() external { + advanceNonce(1); + } + + /// @notice Advances nonce by specified amount + function advanceNonce(uint8 amount) public { + if (amount == 0) revert AdvanceNonceFailed(); + uint256 newNonce = nonce[msg.sender] + amount; + nonce[msg.sender] = newNonce; + emit NonceIncreased(msg.sender, newNonce); + } + + /// @notice Checks if `makerAddress` has specified `makerNonce` + /// @return Result True if `makerAddress` has specified nonce. Otherwise, false + function nonceEquals(address makerAddress, uint256 makerNonce) public view returns(bool) { + return nonce[makerAddress] == makerNonce; + } +} + + +// File @1inch/limit-order-protocol/contracts/helpers/PredicateHelper.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + + +/// @title A helper contract for executing boolean functions on arbitrary target call results +contract PredicateHelper is NonceManager { + using ArgumentsDecoder for bytes; + + error ArbitraryStaticCallFailed(); + + /// @notice Calls every target with corresponding data + /// @return Result True if call to any target returned True. Otherwise, false + function or(uint256 offsets, bytes calldata data) public view returns(bool) { + uint256 current; + uint256 previous; + for (uint256 i = 0; (current = uint32(offsets >> i)) != 0; i += 32) { + (bool success, uint256 res) = _selfStaticCall(data[previous:current]); + if (success && res == 1) { + return true; + } + previous = current; + } + return false; + } + + /// @notice Calls every target with corresponding data + /// @return Result True if calls to all targets returned True. Otherwise, false + function and(uint256 offsets, bytes calldata data) public view returns(bool) { + uint256 current; + uint256 previous; + for (uint256 i = 0; (current = uint32(offsets >> i)) != 0; i += 32) { + (bool success, uint256 res) = _selfStaticCall(data[previous:current]); + if (!success || res != 1) { + return false; + } + previous = current; + } + return true; + } + + /// @notice Calls target with specified data and tests if it's equal to the value + /// @param value Value to test + /// @return Result True if call to target returns the same value as `value`. Otherwise, false + function eq(uint256 value, bytes calldata data) public view returns(bool) { + (bool success, uint256 res) = _selfStaticCall(data); + return success && res == value; + } + + /// @notice Calls target with specified data and tests if it's lower than value + /// @param value Value to test + /// @return Result True if call to target returns value which is lower than `value`. Otherwise, false + function lt(uint256 value, bytes calldata data) public view returns(bool) { + (bool success, uint256 res) = _selfStaticCall(data); + return success && res < value; + } + + /// @notice Calls target with specified data and tests if it's bigger than value + /// @param value Value to test + /// @return Result True if call to target returns value which is bigger than `value`. Otherwise, false + function gt(uint256 value, bytes calldata data) public view returns(bool) { + (bool success, uint256 res) = _selfStaticCall(data); + return success && res > value; + } + + /// @notice Checks passed time against block timestamp + /// @return Result True if current block timestamp is lower than `time`. Otherwise, false + function timestampBelow(uint256 time) public view returns(bool) { + return block.timestamp < time; // solhint-disable-line not-rely-on-time + } + + /// @notice Performs an arbitrary call to target with data + /// @return Result Bytes transmuted to uint256 + function arbitraryStaticCall(address target, bytes calldata data) public view returns(uint256) { + (bool success, uint256 res) = _staticcallForUint(target, data); + if (!success) revert ArbitraryStaticCallFailed(); + return res; + } + + function timestampBelowAndNonceEquals(uint256 timeNonceAccount) public view returns(bool) { + uint256 _time = uint48(timeNonceAccount >> 208); + uint256 _nonce = uint48(timeNonceAccount >> 160); + address _account = address(uint160(timeNonceAccount)); + return timestampBelow(_time) && nonceEquals(_account, _nonce); + } + + function _selfStaticCall(bytes calldata data) internal view returns(bool, uint256) { + uint256 selector = uint32(data.decodeSelector()); + uint256 arg = data.decodeUint256(4); + + // special case for the most often used predicate + if (selector == uint32(this.timestampBelowAndNonceEquals.selector)) { // 0x2cc2878d + return (true, timestampBelowAndNonceEquals(arg) ? 1 : 0); + } + + if (selector < uint32(this.arbitraryStaticCall.selector)) { // 0xbf15fcd8 + if (selector < uint32(this.eq.selector)) { // 0x6fe7b0ba + if (selector == uint32(this.gt.selector)) { // 0x4f38e2b8 + return (true, gt(arg, data.decodeTailCalldata(100)) ? 1 : 0); + } else if (selector == uint32(this.timestampBelow.selector)) { // 0x63592c2b + return (true, timestampBelow(arg) ? 1 : 0); + } + } else { + if (selector == uint32(this.eq.selector)) { // 0x6fe7b0ba + return (true, eq(arg, data.decodeTailCalldata(100)) ? 1 : 0); + } else if (selector == uint32(this.or.selector)) { // 0x74261145 + return (true, or(arg, data.decodeTailCalldata(100)) ? 1 : 0); + } + } + } else { + if (selector < uint32(this.lt.selector)) { // 0xca4ece22 + if (selector == uint32(this.arbitraryStaticCall.selector)) { // 0xbf15fcd8 + return (true, arbitraryStaticCall(address(uint160(arg)), data.decodeTailCalldata(100))); + } else if (selector == uint32(this.and.selector)) { // 0xbfa75143 + return (true, and(arg, data.decodeTailCalldata(100)) ? 1 : 0); + } + } else { + if (selector == uint32(this.lt.selector)) { // 0xca4ece22 + return (true, lt(arg, data.decodeTailCalldata(100)) ? 1 : 0); + } else if (selector == uint32(this.nonceEquals.selector)) { // 0xcf6fc6e3 + return (true, nonceEquals(address(uint160(arg)), data.decodeUint256(0x24)) ? 1 : 0); + } + } + } + + return _staticcallForUint(address(this), data); + } + + function _staticcallForUint(address target, bytes calldata input) private view returns(bool success, uint256 res) { + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let data := mload(0x40) + + calldatacopy(data, input.offset, input.length) + success := staticcall(gas(), target, data, input.length, 0x0, 0x20) + success := and(success, eq(returndatasize(), 32)) + if success { + res := mload(0) + } + } + } +} + + +// File @1inch/limit-order-protocol/contracts/interfaces/IOrderMixin.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + +interface IOrderMixin { + /** + * @notice Returns unfilled amount for order. Throws if order does not exist + * @param orderHash Order's hash. Can be obtained by the `hashOrder` function + * @return amount Unfilled amount + */ + function remaining(bytes32 orderHash) external view returns(uint256 amount); + + /** + * @notice Returns unfilled amount for order + * @param orderHash Order's hash. Can be obtained by the `hashOrder` function + * @return rawAmount Unfilled amount of order plus one if order exists. Otherwise 0 + */ + function remainingRaw(bytes32 orderHash) external view returns(uint256 rawAmount); + + /** + * @notice Same as `remainingRaw` but for multiple orders + * @param orderHashes Array of hashes + * @return rawAmounts Array of amounts for each order plus one if order exists or 0 otherwise + */ + function remainingsRaw(bytes32[] memory orderHashes) external view returns(uint256[] memory rawAmounts); + + /** + * @notice Checks order predicate + * @param order Order to check predicate for + * @return result Predicate evaluation result. True if predicate allows to fill the order, false otherwise + */ + function checkPredicate(OrderLib.Order calldata order) external view returns(bool result); + + /** + * @notice Returns order hash according to EIP712 standard + * @param order Order to get hash for + * @return orderHash Hash of the order + */ + function hashOrder(OrderLib.Order calldata order) external view returns(bytes32); + + /** + * @notice Delegates execution to custom implementation. Could be used to validate if `transferFrom` works properly + * @dev The function always reverts and returns the simulation results in revert data. + * @param target Addresses that will be delegated + * @param data Data that will be passed to delegatee + */ + function simulate(address target, bytes calldata data) external; + + /** + * @notice Cancels order. + * @dev Order is cancelled by setting remaining amount to _ORDER_FILLED value + * @param order Order quote to cancel + * @return orderRemaining Unfilled amount of order before cancellation + * @return orderHash Hash of the filled order + */ + function cancelOrder(OrderLib.Order calldata order) external returns(uint256 orderRemaining, bytes32 orderHash); + + /** + * @notice Fills an order. If one doesn't exist (first fill) it will be created using order.makerAssetData + * @param order Order quote to fill + * @param signature Signature to confirm quote ownership + * @param interaction A call data for InteractiveNotificationReceiver. Taker may execute interaction after getting maker assets and before sending taker assets. + * @param makingAmount Making amount + * @param takingAmount Taking amount + * @param skipPermitAndThresholdAmount Specifies maximum allowed takingAmount when takingAmount is zero, otherwise specifies minimum allowed makingAmount. Top-most bit specifies whether taker wants to skip maker's permit. + * @return actualMakingAmount Actual amount transferred from maker to taker + * @return actualTakingAmount Actual amount transferred from taker to maker + * @return orderHash Hash of the filled order + */ + function fillOrder( + OrderLib.Order calldata order, + bytes calldata signature, + bytes calldata interaction, + uint256 makingAmount, + uint256 takingAmount, + uint256 skipPermitAndThresholdAmount + ) external payable returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash); + + /** + * @notice Same as `fillOrderTo` but calls permit first, + * allowing to approve token spending and make a swap in one transaction. + * Also allows to specify funds destination instead of `msg.sender` + * @dev See tests for examples + * @param order Order quote to fill + * @param signature Signature to confirm quote ownership + * @param interaction A call data for InteractiveNotificationReceiver. Taker may execute interaction after getting maker assets and before sending taker assets. + * @param makingAmount Making amount + * @param takingAmount Taking amount + * @param skipPermitAndThresholdAmount Specifies maximum allowed takingAmount when takingAmount is zero, otherwise specifies minimum allowed makingAmount. Top-most bit specifies whether taker wants to skip maker's permit. + * @param target Address that will receive swap funds + * @param permit Should consist of abiencoded token address and encoded `IERC20Permit.permit` call. + * @return actualMakingAmount Actual amount transferred from maker to taker + * @return actualTakingAmount Actual amount transferred from taker to maker + * @return orderHash Hash of the filled order + */ + function fillOrderToWithPermit( + OrderLib.Order calldata order, + bytes calldata signature, + bytes calldata interaction, + uint256 makingAmount, + uint256 takingAmount, + uint256 skipPermitAndThresholdAmount, + address target, + bytes calldata permit + ) external returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash); + + /** + * @notice Same as `fillOrder` but allows to specify funds destination instead of `msg.sender` + * @param order_ Order quote to fill + * @param signature Signature to confirm quote ownership + * @param interaction A call data for InteractiveNotificationReceiver. Taker may execute interaction after getting maker assets and before sending taker assets. + * @param makingAmount Making amount + * @param takingAmount Taking amount + * @param skipPermitAndThresholdAmount Specifies maximum allowed takingAmount when takingAmount is zero, otherwise specifies minimum allowed makingAmount. Top-most bit specifies whether taker wants to skip maker's permit. + * @param target Address that will receive swap funds + * @return actualMakingAmount Actual amount transferred from maker to taker + * @return actualTakingAmount Actual amount transferred from taker to maker + * @return orderHash Hash of the filled order + */ + function fillOrderTo( + OrderLib.Order calldata order_, + bytes calldata signature, + bytes calldata interaction, + uint256 makingAmount, + uint256 takingAmount, + uint256 skipPermitAndThresholdAmount, + address target + ) external payable returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash); +} + + +// File @1inch/limit-order-protocol/contracts/interfaces/NotificationReceiver.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + +/// @title Interface for interactor which acts between `maker => taker` and `taker => maker` transfers. +interface PreInteractionNotificationReceiver { + function fillOrderPreInteraction( + bytes32 orderHash, + address maker, + address taker, + uint256 makingAmount, + uint256 takingAmount, + uint256 remainingAmount, + bytes memory interactiveData + ) external; +} + +interface PostInteractionNotificationReceiver { + /// @notice Callback method that gets called after taker transferred funds to maker but before + /// the opposite transfer happened + function fillOrderPostInteraction( + bytes32 orderHash, + address maker, + address taker, + uint256 makingAmount, + uint256 takingAmount, + uint256 remainingAmount, + bytes memory interactiveData + ) external; +} + +interface InteractionNotificationReceiver { + function fillOrderInteraction( + address taker, + uint256 makingAmount, + uint256 takingAmount, + bytes memory interactiveData + ) external returns(uint256 offeredTakingAmount); +} + + +// File @1inch/limit-order-protocol/contracts/OrderMixin.sol@v0.3.0-prerelease + + +pragma solidity 0.8.17; + + + + +/// @title Regular Limit Order mixin +abstract contract OrderMixin is IOrderMixin, EIP712, PredicateHelper { + using SafeERC20 for IERC20; + using ArgumentsDecoder for bytes; + using OrderLib for OrderLib.Order; + + error UnknownOrder(); + error AccessDenied(); + error AlreadyFilled(); + error PermitLengthTooLow(); + error ZeroTargetIsForbidden(); + error RemainingAmountIsZero(); + error PrivateOrder(); + error BadSignature(); + error ReentrancyDetected(); + error PredicateIsNotTrue(); + error OnlyOneAmountShouldBeZero(); + error TakingAmountTooHigh(); + error MakingAmountTooLow(); + error SwapWithZeroAmount(); + error TransferFromMakerToTakerFailed(); + error TransferFromTakerToMakerFailed(); + error WrongAmount(); + error WrongGetter(); + error GetAmountCallFailed(); + error TakingAmountIncreased(); + error SimulationResults(bool success, bytes res); + + /// @notice Emitted every time order gets filled, including partial fills + event OrderFilled( + address indexed maker, + bytes32 orderHash, + uint256 remaining + ); + + /// @notice Emitted when order gets cancelled + event OrderCanceled( + address indexed maker, + bytes32 orderHash, + uint256 remainingRaw + ); + + uint256 constant private _ORDER_DOES_NOT_EXIST = 0; + uint256 constant private _ORDER_FILLED = 1; + uint256 constant private _SKIP_PERMIT_FLAG = 1 << 255; + uint256 constant private _THRESHOLD_MASK = ~_SKIP_PERMIT_FLAG; + + IWETH private immutable _WETH; // solhint-disable-line var-name-mixedcase + /// @notice Stores unfilled amounts for each order plus one. + /// Therefore 0 means order doesn't exist and 1 means order was filled + mapping(bytes32 => uint256) private _remaining; + + constructor(IWETH weth) { + _WETH = weth; + } + + /** + * @notice See {IOrderMixin-remaining}. + */ + function remaining(bytes32 orderHash) external view returns(uint256 /* amount */) { + uint256 amount = _remaining[orderHash]; + if (amount == _ORDER_DOES_NOT_EXIST) revert UnknownOrder(); + unchecked { return amount - 1; } + } + + /** + * @notice See {IOrderMixin-remainingRaw}. + */ + function remainingRaw(bytes32 orderHash) external view returns(uint256 /* rawAmount */) { + return _remaining[orderHash]; + } + + /** + * @notice See {IOrderMixin-remainingsRaw}. + */ + function remainingsRaw(bytes32[] memory orderHashes) external view returns(uint256[] memory /* rawAmounts */) { + uint256[] memory results = new uint256[](orderHashes.length); + for (uint256 i = 0; i < orderHashes.length; i++) { + results[i] = _remaining[orderHashes[i]]; + } + return results; + } + + /** + * @notice See {IOrderMixin-simulate}. + */ + function simulate(address target, bytes calldata data) external { + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory result) = target.delegatecall(data); + revert SimulationResults(success, result); + } + + /** + * @notice See {IOrderMixin-cancelOrder}. + */ + function cancelOrder(OrderLib.Order calldata order) external returns(uint256 orderRemaining, bytes32 orderHash) { + if (order.maker != msg.sender) revert AccessDenied(); + + orderHash = hashOrder(order); + orderRemaining = _remaining[orderHash]; + if (orderRemaining == _ORDER_FILLED) revert AlreadyFilled(); + emit OrderCanceled(msg.sender, orderHash, orderRemaining); + _remaining[orderHash] = _ORDER_FILLED; + } + + /** + * @notice See {IOrderMixin-fillOrder}. + */ + function fillOrder( + OrderLib.Order calldata order, + bytes calldata signature, + bytes calldata interaction, + uint256 makingAmount, + uint256 takingAmount, + uint256 skipPermitAndThresholdAmount + ) external payable returns(uint256 /* actualMakingAmount */, uint256 /* actualTakingAmount */, bytes32 /* orderHash */) { + return fillOrderTo(order, signature, interaction, makingAmount, takingAmount, skipPermitAndThresholdAmount, msg.sender); + } + + /** + * @notice See {IOrderMixin-fillOrderToWithPermit}. + */ + function fillOrderToWithPermit( + OrderLib.Order calldata order, + bytes calldata signature, + bytes calldata interaction, + uint256 makingAmount, + uint256 takingAmount, + uint256 skipPermitAndThresholdAmount, + address target, + bytes calldata permit + ) external returns(uint256 /* actualMakingAmount */, uint256 /* actualTakingAmount */, bytes32 /* orderHash */) { + if (permit.length < 20) revert PermitLengthTooLow(); + { // Stack too deep + (address token, bytes calldata permitData) = permit.decodeTargetAndCalldata(); + IERC20(token).safePermit(permitData); + } + return fillOrderTo(order, signature, interaction, makingAmount, takingAmount, skipPermitAndThresholdAmount, target); + } + + /** + * @notice See {IOrderMixin-fillOrderTo}. + */ + function fillOrderTo( + OrderLib.Order calldata order_, + bytes calldata signature, + bytes calldata interaction, + uint256 makingAmount, + uint256 takingAmount, + uint256 skipPermitAndThresholdAmount, + address target + ) public payable returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash) { + if (target == address(0)) revert ZeroTargetIsForbidden(); + orderHash = hashOrder(order_); + + OrderLib.Order calldata order = order_; // Helps with "Stack too deep" + actualMakingAmount = makingAmount; + actualTakingAmount = takingAmount; + + uint256 remainingMakingAmount = _remaining[orderHash]; + if (remainingMakingAmount == _ORDER_FILLED) revert RemainingAmountIsZero(); + if (order.allowedSender != address(0) && order.allowedSender != msg.sender) revert PrivateOrder(); + if (remainingMakingAmount == _ORDER_DOES_NOT_EXIST) { + // First fill: validate order and permit maker asset + if (!ECDSA.recoverOrIsValidSignature(order.maker, orderHash, signature)) revert BadSignature(); + remainingMakingAmount = order.makingAmount; + + bytes calldata permit = order.permit(); + if (skipPermitAndThresholdAmount & _SKIP_PERMIT_FLAG == 0 && permit.length >= 20) { + // proceed only if taker is willing to execute permit and its length is enough to store address + (address token, bytes calldata permitCalldata) = permit.decodeTargetAndCalldata(); + IERC20(token).safePermit(permitCalldata); + if (_remaining[orderHash] != _ORDER_DOES_NOT_EXIST) revert ReentrancyDetected(); + } + } else { + unchecked { remainingMakingAmount -= 1; } + } + + // Check if order is valid + if (order.predicate().length > 0) { + if (!checkPredicate(order)) revert PredicateIsNotTrue(); + } + + // Compute maker and taker assets amount + if ((actualTakingAmount == 0) == (actualMakingAmount == 0)) { + revert OnlyOneAmountShouldBeZero(); + } else if (actualTakingAmount == 0) { + if (actualMakingAmount > remainingMakingAmount) { + actualMakingAmount = remainingMakingAmount; + } + actualTakingAmount = _getTakingAmount(order.getTakingAmount(), order.makingAmount, actualMakingAmount, order.takingAmount, remainingMakingAmount, orderHash); + uint256 thresholdAmount = skipPermitAndThresholdAmount & _THRESHOLD_MASK; + // check that actual rate is not worse than what was expected + // actualTakingAmount / actualMakingAmount <= thresholdAmount / makingAmount + if (actualTakingAmount * makingAmount > thresholdAmount * actualMakingAmount) revert TakingAmountTooHigh(); + } else { + actualMakingAmount = _getMakingAmount(order.getMakingAmount(), order.takingAmount, actualTakingAmount, order.makingAmount, remainingMakingAmount, orderHash); + if (actualMakingAmount > remainingMakingAmount) { + actualMakingAmount = remainingMakingAmount; + actualTakingAmount = _getTakingAmount(order.getTakingAmount(), order.makingAmount, actualMakingAmount, order.takingAmount, remainingMakingAmount, orderHash); + if (actualTakingAmount > takingAmount) revert TakingAmountIncreased(); + } + uint256 thresholdAmount = skipPermitAndThresholdAmount & _THRESHOLD_MASK; + // check that actual rate is not worse than what was expected + // actualMakingAmount / actualTakingAmount >= thresholdAmount / takingAmount + if (actualMakingAmount * takingAmount < thresholdAmount * actualTakingAmount) revert MakingAmountTooLow(); + } + + if (actualMakingAmount == 0 || actualTakingAmount == 0) revert SwapWithZeroAmount(); + + // Update remaining amount in storage + unchecked { + remainingMakingAmount = remainingMakingAmount - actualMakingAmount; + _remaining[orderHash] = remainingMakingAmount + 1; + } + emit OrderFilled(order_.maker, orderHash, remainingMakingAmount); + + // Maker can handle funds interactively + if (order.preInteraction().length >= 20) { + // proceed only if interaction length is enough to store address + (address interactionTarget, bytes calldata interactionData) = order.preInteraction().decodeTargetAndCalldata(); + PreInteractionNotificationReceiver(interactionTarget).fillOrderPreInteraction( + orderHash, order.maker, msg.sender, actualMakingAmount, actualTakingAmount, remainingMakingAmount, interactionData + ); + } + + // Maker => Taker + if (!_callTransferFrom( + order.makerAsset, + order.maker, + target, + actualMakingAmount, + order.makerAssetData() + )) revert TransferFromMakerToTakerFailed(); + + if (interaction.length >= 20) { + // proceed only if interaction length is enough to store address + (address interactionTarget, bytes calldata interactionData) = interaction.decodeTargetAndCalldata(); + uint256 offeredTakingAmount = InteractionNotificationReceiver(interactionTarget).fillOrderInteraction( + msg.sender, actualMakingAmount, actualTakingAmount, interactionData + ); + + if (offeredTakingAmount > actualTakingAmount && + !OrderLib.getterIsFrozen(order.getMakingAmount()) && + !OrderLib.getterIsFrozen(order.getTakingAmount())) + { + actualTakingAmount = offeredTakingAmount; + } + } + + // Taker => Maker + if (order.takerAsset == address(_WETH) && msg.value > 0) { + if (msg.value < actualTakingAmount) revert Errors.InvalidMsgValue(); + if (msg.value > actualTakingAmount) { + unchecked { + (bool success, ) = msg.sender.call{value: msg.value - actualTakingAmount}(""); // solhint-disable-line avoid-low-level-calls + if (!success) revert Errors.ETHTransferFailed(); + } + } + _WETH.deposit{ value: actualTakingAmount }(); + _WETH.transfer(order.receiver == address(0) ? order.maker : order.receiver, actualTakingAmount); + } else { + if (msg.value != 0) revert Errors.InvalidMsgValue(); + if (!_callTransferFrom( + order.takerAsset, + msg.sender, + order.receiver == address(0) ? order.maker : order.receiver, + actualTakingAmount, + order.takerAssetData() + )) revert TransferFromTakerToMakerFailed(); + } + + // Maker can handle funds interactively + if (order.postInteraction().length >= 20) { + // proceed only if interaction length is enough to store address + (address interactionTarget, bytes calldata interactionData) = order.postInteraction().decodeTargetAndCalldata(); + PostInteractionNotificationReceiver(interactionTarget).fillOrderPostInteraction( + orderHash, order.maker, msg.sender, actualMakingAmount, actualTakingAmount, remainingMakingAmount, interactionData + ); + } + } + + /** + * @notice See {IOrderMixin-checkPredicate}. + */ + function checkPredicate(OrderLib.Order calldata order) public view returns(bool) { + (bool success, uint256 res) = _selfStaticCall(order.predicate()); + return success && res == 1; + } + + /** + * @notice See {IOrderMixin-hashOrder}. + */ + function hashOrder(OrderLib.Order calldata order) public view returns(bytes32) { + return order.hash(_domainSeparatorV4()); + } + + function _callTransferFrom(address asset, address from, address to, uint256 amount, bytes calldata input) private returns(bool success) { + bytes4 selector = IERC20.transferFrom.selector; + /// @solidity memory-safe-assembly + assembly { // solhint-disable-line no-inline-assembly + let data := mload(0x40) + + mstore(data, selector) + mstore(add(data, 0x04), from) + mstore(add(data, 0x24), to) + mstore(add(data, 0x44), amount) + calldatacopy(add(data, 0x64), input.offset, input.length) + let status := call(gas(), asset, 0, data, add(0x64, input.length), 0x0, 0x20) + success := and(status, or(iszero(returndatasize()), and(gt(returndatasize(), 31), eq(mload(0), 1)))) + } + } + + function _getMakingAmount( + bytes calldata getter, + uint256 orderTakingAmount, + uint256 requestedTakingAmount, + uint256 orderMakingAmount, + uint256 remainingMakingAmount, + bytes32 orderHash + ) private view returns(uint256) { + if (getter.length == 0) { + // Linear proportion + return AmountCalculator.getMakingAmount(orderMakingAmount, orderTakingAmount, requestedTakingAmount); + } + return _callGetter(getter, orderTakingAmount, requestedTakingAmount, orderMakingAmount, remainingMakingAmount, orderHash); + } + + function _getTakingAmount( + bytes calldata getter, + uint256 orderMakingAmount, + uint256 requestedMakingAmount, + uint256 orderTakingAmount, + uint256 remainingMakingAmount, + bytes32 orderHash + ) private view returns(uint256) { + if (getter.length == 0) { + // Linear proportion + return AmountCalculator.getTakingAmount(orderMakingAmount, orderTakingAmount, requestedMakingAmount); + } + return _callGetter(getter, orderMakingAmount, requestedMakingAmount, orderTakingAmount, remainingMakingAmount, orderHash); + } + + function _callGetter( + bytes calldata getter, + uint256 orderExpectedAmount, + uint256 requestedAmount, + uint256 orderResultAmount, + uint256 remainingMakingAmount, + bytes32 orderHash + ) private view returns(uint256) { + if (getter.length == 1) { + if (OrderLib.getterIsFrozen(getter)) { + // On "x" getter calldata only exact amount is allowed + if (requestedAmount != orderExpectedAmount) revert WrongAmount(); + return orderResultAmount; + } else { + revert WrongGetter(); + } + } else { + (address target, bytes calldata data) = getter.decodeTargetAndCalldata(); + (bool success, bytes memory result) = target.staticcall(abi.encodePacked(data, requestedAmount, remainingMakingAmount, orderHash)); + if (!success || result.length != 32) revert GetAmountCallFailed(); + return abi.decode(result, (uint256)); + } + } +} + + +// File contracts/AggregationRouterV5.sol + + +pragma solidity 0.8.17; + + + + + +/// @notice Main contract incorporates a number of routers to perform swaps and limit orders protocol to fill limit orders +contract AggregationRouterV5 is EIP712("1inch Aggregation Router", "5"), Ownable, + ClipperRouter, GenericRouter, UnoswapRouter, UnoswapV3Router, OrderMixin, OrderRFQMixin +{ + using UniERC20 for IERC20; + + error ZeroAddress(); + + /** + * @dev Sets the wrapped eth token and clipper exhange interface + * Both values are immutable: they can only be set once during + * construction. + */ + constructor(IWETH weth) + UnoswapV3Router(weth) + ClipperRouter(weth) + OrderMixin(weth) + OrderRFQMixin(weth) + { + if (address(weth) == address(0)) revert ZeroAddress(); + } + + /** + * @notice Retrieves funds accidently sent directly to the contract address + * @param token ERC20 token to retrieve + * @param amount amount to retrieve + */ + function rescueFunds(IERC20 token, uint256 amount) external onlyOwner { + token.uniTransfer(payable(msg.sender), amount); + } + + /** + * @notice Destroys the contract and sends eth to sender. Use with caution. + * The only case when the use of the method is justified is if there is an exploit found. + * And the damage from the exploit is greater than from just an urgent contract change. + */ + function destroy() external onlyOwner { + selfdestruct(payable(msg.sender)); + } + + function _receive() internal override(EthReceiver, OnlyWethReceiver) { + EthReceiver._receive(); + } +} \ No newline at end of file diff --git a/packages/foundry/src/vault/strategy/Pool2SingleAssetCompounder.sol b/packages/foundry/src/vault/strategy/Pool2SingleAssetCompounder.sol index cd529a72..70f72020 100644 --- a/packages/foundry/src/vault/strategy/Pool2SingleAssetCompounder.sol +++ b/packages/foundry/src/vault/strategy/Pool2SingleAssetCompounder.sol @@ -65,3 +65,22 @@ contract Pool2SingleAssetCompounder is StrategyBase { IAdapter(address(this)).strategyDeposit(ERC20(asset).balanceOf(address(this)), 0); } } + + +// asset -> asset +// asset -> curve pool2 +// asset -> curve pool3 +// asset -> curve custom pool -- how many? +// asset -> uniV2 +// asset -> uniV3 +// asset -> velo? +// asset -> balancer -- how many? +// split asset to mitigate slippage + + +// Use 1Inch Router +// 1. Get Spot price +// 2. Calc Min +// 3. Trade using router +// 4. Potentially LP +// Trade to usdc first? and than trade all together to asset? Or just each reward on their own \ No newline at end of file From 7e32e59de23ef9a1d5bd4b7e3b649e915c81f2f7 Mon Sep 17 00:00:00 2001 From: RedVeil Date: Tue, 14 Feb 2023 10:42:22 +0100 Subject: [PATCH 02/21] wip - UniV2 TradeModule --- .../src/vault/strategy/trade/UniV2TradeModule.sol | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol diff --git a/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol b/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol new file mode 100644 index 00000000..9216474f --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import { IUniswapRouterV2 } from "../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../interfaces/vault/IWithRewards.sol"; +import { StrategyBase } from "./StrategyBase.sol"; + +contract UniV2TradeModule { + constructor(){} + + +} \ No newline at end of file From 38d8609104094a5da02ebe3cd8b88ab012c3e411 Mon Sep 17 00:00:00 2001 From: RedVeil Date: Tue, 14 Feb 2023 21:46:40 +0100 Subject: [PATCH 03/21] wip - Compounder Modules --- .../external/uni/IUniswapV2Pair.sol | 98 +++++++++++++++++++ .../strategy/trade/CompounderStrategy.sol | 71 ++++++++++++++ .../vault/strategy/trade/Pool2Depositor.sol | 59 +++++++++++ .../vault/strategy/trade/UniV2TradeModule.sol | 42 ++++++-- 4 files changed, 261 insertions(+), 9 deletions(-) create mode 100644 packages/foundry/src/interfaces/external/uni/IUniswapV2Pair.sol create mode 100644 packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol create mode 100644 packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol diff --git a/packages/foundry/src/interfaces/external/uni/IUniswapV2Pair.sol b/packages/foundry/src/interfaces/external/uni/IUniswapV2Pair.sol new file mode 100644 index 00000000..492ce59b --- /dev/null +++ b/packages/foundry/src/interfaces/external/uni/IUniswapV2Pair.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.15; + +interface IUniswapV2Pair { + event Approval(address indexed owner, address indexed spender, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); + + function name() external pure returns (string memory); + + function symbol() external pure returns (string memory); + + function decimals() external pure returns (uint8); + + function totalSupply() external view returns (uint256); + + function balanceOf(address owner) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 value) external returns (bool); + + function transfer(address to, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + event Mint(address indexed sender, uint256 amount0, uint256 amount1); + event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); + event Swap( + address indexed sender, + uint256 amount0In, + uint256 amount1In, + uint256 amount0Out, + uint256 amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint256); + + function factory() external view returns (address); + + function token0() external view returns (address); + + function token1() external view returns (address); + + function getReserves() + external + view + returns ( + uint112 reserve0, + uint112 reserve1, + uint32 blockTimestampLast + ); + + function price0CumulativeLast() external view returns (uint256); + + function price1CumulativeLast() external view returns (uint256); + + function kLast() external view returns (uint256); + + function mint(address to) external returns (uint256 liquidity); + + function burn(address to) external returns (uint256 amount0, uint256 amount1); + + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; + + function skim(address to) external; + + function sync() external; + + function initialize(address, address) external; +} diff --git a/packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol b/packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol new file mode 100644 index 00000000..9b973a27 --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 +pragma solidity ^0.8.15; + +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import { IUniswapRouterV2 } from "../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../interfaces/vault/IWithRewards.sol"; +import { StrategyBase } from "./StrategyBase.sol"; + +interface ITradeModule { + function getAmountOut( + uint256 amountIn, + address[] calldata tradePath, + address router + ) external view returns (uint256); + + function trade( + uint256 amountIn, + uint256 amountOut, + address[] calldata tradePath, + address router + ) external returns (uint256); +} + +struct Trade { + address tradeModule; + address router; + uint256 minTradeAmount; + uint256 slippage; + address[] tradePath; +} + +contract Pool2SingleAssetCompounder is StrategyBase { + error NoValidTradePath(); + + /// @notice claim all token rewards and trade them for the underlying asset + function harvest() public override { + (address tradeModule, address router, uint256 minTradeAmount, uint256 slippage, address[] tradePath) = abi.decode( + IAdapter(address(this)).strategyConfig(), + ((address, address, uint256, uint256, address[])) + ); + + for (uint256 i = 0; i < len; i++) { + uint256 bal = ERC20(tradePath[0]).balanceOf(address(this)); + if (bal > minTradeAmount) { + // TODO amountOut currently doesnt account for oracle manipulation before the trade. It assumes a fair price + uint256 amountOut = ITradeModule(tradeModule).getAmountOut(bal, tradePath, router); + ITradeModule(tradeModule).trade(bal, (amountOut * (1e18 - slippage)) / 1e18, tradePath, router); + } + } + IAdapter(address(this)).strategyDeposit(ERC20(asset).balanceOf(address(this)), 0); + } +} + +// asset -> asset +// asset -> curve pool2 +// asset -> curve pool3 +// asset -> curve custom pool -- how many? +// asset -> uniV2 +// asset -> uniV3 +// asset -> velo? +// asset -> balancer -- how many? +// split asset to mitigate slippage + +// Use 1Inch Router +// 1. Get Spot price +// 2. Calc Min +// 3. Trade using router +// 4. Potentially LP +// Trade to usdc first? and than trade all together to asset? Or just each reward on their own diff --git a/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol b/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol new file mode 100644 index 00000000..d09dfea3 --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IUniswapV2Pair } from "../../../interfaces/external/uni/IUniswapV2Pair.sol"; + +contract UniV2TradeModule { + function getAmountOut( + uint256 amountIn, + address[] calldata tradePath, + address router + ) external view returns (uint256) { + uint256 len = tradePath.length; + uint256[] memory amountsOut = IUniswapRouterV2(router).getAmountsOut(amountIn, tradePath); + return amountsOut[len - 1]; + } + + function deposit( + address asset, + uint256 amountIn, + uint256 amountOut, + address[] calldata tradePath, + address router + ) external returns (uint256) { + IERC20(tradePath[0]).transferFrom(msg.sender, address(this), amountIn); + + // A - only one token + // B - both token but unbalanced + // C - both token but balanced + + // 1. Check pair assets + // 2. Check pair balance and ratio + // 3. Compare balances and ratio + // 4. Trade larger balance for smaller balance to achieve ratio ---- this will change the ratio + // 5. Deposit to pool + + address token0 = IUniswapV2Pair(asset).token0(); + address token1 = IUniswapV2Pair(asset).token1(); + + uint256 bal0 = IERC20(token0).balanceOf(msg.sender); + + // TODO how do i need to set the deadline? + IUniswapRouterV2(router).addLiquidity( + tokenA, + tokenB, + amountADesired, + amountBDesired, + amountAMin, + amountBMin, + msg.sender, + block.timestamp + 10 + ); + + uint256 len = tradePath.length; + IERC20(tradePath[len - 1]).transfer(msg.sender, amountsOut[len - 1]); + return amountsOut[len - 1]; + } +} diff --git a/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol b/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol index 9216474f..d2beda0d 100644 --- a/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol +++ b/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol @@ -2,14 +2,38 @@ // Docgen-SOLC: 0.8.15 pragma solidity ^0.8.15; -import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; -import { IUniswapRouterV2 } from "../../interfaces/external/uni/IUniswapRouterV2.sol"; -import { IAdapter } from "../../interfaces/vault/IAdapter.sol"; -import { IWithRewards } from "../../interfaces/vault/IWithRewards.sol"; -import { StrategyBase } from "./StrategyBase.sol"; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; -contract UniV2TradeModule { - constructor(){} +contract UniV2TradeModule { + function getAmountOut( + uint256 amountIn, + address[] calldata tradePath, + address router + ) external view returns (uint256) { + uint256 len = tradePath.length; + uint256[] memory amountsOut = IUniswapRouterV2(router).getAmountsOut(amountIn, tradePath); + return amountsOut[len - 1]; + } - -} \ No newline at end of file + function trade( + uint256 amountIn, + uint256 amountOut, + address[] calldata tradePath, + address router + ) external returns (uint256) { + IERC20(tradePath[0]).transferFrom(msg.sender, address(this), amountIn); + + // TODO how do i need to set the deadline? + uint256[] memory amountsOut = IUniswapRouterV2(router).swapExactTokensForTokens( + amount, + amountOut, + tradePath, + msg.sender, + block.timestamp + 10 + ); + + uint256 len = tradePath.length; + IERC20(tradePath[len - 1]).transfer(msg.sender, amountsOut[len - 1]); + return amountsOut[len - 1]; + } +} From 3cf1f54a35547b6943dfcfbe15a61ceca658433a Mon Sep 17 00:00:00 2001 From: RedVeil Date: Wed, 15 Feb 2023 13:23:00 +0100 Subject: [PATCH 04/21] wip - SimplePool2Depositor --- .../vault/strategy/trade/Pool2Depositor.sol | 3 ++ .../strategy/trade/SimplePool2Depositor.sol | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol diff --git a/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol b/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol index d09dfea3..b30d1f8e 100644 --- a/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol +++ b/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol @@ -39,6 +39,9 @@ contract UniV2TradeModule { address token1 = IUniswapV2Pair(asset).token1(); uint256 bal0 = IERC20(token0).balanceOf(msg.sender); + uint256 bal1 = IERC20(token1).balanceOf(msg.sender); + + (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) = IUniswapV2Pair(asset).getReserves(); // TODO how do i need to set the deadline? IUniswapRouterV2(router).addLiquidity( diff --git a/packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol b/packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol new file mode 100644 index 00000000..b668cdf2 --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IUniswapV2Pair } from "../../../interfaces/external/uni/IUniswapV2Pair.sol"; + +contract SimplePool2Depositor { + function addLiquidity() internal { + uint256 nativeHalf = IERC20(native).balanceOf(address(this)).div(2); + + if (lpToken0 != native) { + uint256 amountOut0 = ITradeModule(tradeModule).getAmountOut(nativeHalf, nativeToLp0Route, router); + ITradeModule(tradeModule).trade(nativeHalf, (amountOut * (1e18 - slippage)) / 1e18, nativeToLp0Route, router); + } + + if (lpToken1 != native) { + uint256 amountOut1 = ITradeModule(tradeModule).getAmountOut(nativeHalf, nativeToLp1Route, router); + ITradeModule(tradeModule).trade(nativeHalf, (amountOut * (1e18 - slippage)) / 1e18, nativeToLp1Route, router); + } + + uint256 lp0Bal = IERC20(lpToken0).balanceOf(address(this)); + uint256 lp1Bal = IERC20(lpToken1).balanceOf(address(this)); + IUniswapRouterETH(unirouter).addLiquidity( + lpToken0, + lpToken1, + lp0Bal, + lp1Bal, + (amountOut * (1e18 - slippage)) / 1e18, + (amountOut * (1e18 - slippage)) / 1e18, + address(this), + now + ); + } +} From 52ea16be6ead0332740cf0ceb9f07dfdd93126a1 Mon Sep 17 00:00:00 2001 From: RedVeil Date: Wed, 15 Feb 2023 15:38:44 +0100 Subject: [PATCH 05/21] trade and deposit module builds --- packages/foundry/src/vault/strategy/1inch.sol | 4692 ----------------- .../vault/strategy/trade/1InchTradeModule.sol | 116 + .../strategy/trade/CompounderStrategy.sol | 65 +- .../src/vault/strategy/trade/ITradeModule.sol | 26 + .../vault/strategy/trade/Pool2Depositor.sol | 45 +- .../strategy/trade/SimplePool2Depositor.sol | 60 +- .../vault/strategy/trade/UniV2TradeModule.sol | 6 +- .../benGov/AddBeneficiariesToRegistry.s.sol | 102 - .../script/benGov/AddNominationProposal.s.sol | 62 - .../script/benGov/AddTakedownProposal.s.sol | 56 - .../BeneficiaryGovernanceDeployment.s.sol | 100 - .../CreateFinalizedYearlyElection.s.sol | 55 - .../benGov/CreateOpenQuarterlyElection.s.sol | 56 - .../CreateRegistrationMonthlyElection.s.sol | 46 - .../RemoveBeneficiariesFromRegistry.s.sol | 28 - packages/hardhat/script/benGov/readme.MD | 15 - .../AbstractSimulateBatchSlippage.t.sol | 97 - .../SimulateThreeXBatchSlippage.t.sol | 22 - .../test/forge/fork/ButterFeeConverter.t.sol | 260 - .../hardhat/test/forge/fork/FeeEscrow.t.sol | 164 - 20 files changed, 226 insertions(+), 5847 deletions(-) delete mode 100644 packages/foundry/src/vault/strategy/1inch.sol create mode 100644 packages/foundry/src/vault/strategy/trade/1InchTradeModule.sol create mode 100644 packages/foundry/src/vault/strategy/trade/ITradeModule.sol delete mode 100644 packages/hardhat/script/benGov/AddBeneficiariesToRegistry.s.sol delete mode 100644 packages/hardhat/script/benGov/AddNominationProposal.s.sol delete mode 100644 packages/hardhat/script/benGov/AddTakedownProposal.s.sol delete mode 100644 packages/hardhat/script/benGov/BeneficiaryGovernanceDeployment.s.sol delete mode 100644 packages/hardhat/script/benGov/CreateFinalizedYearlyElection.s.sol delete mode 100644 packages/hardhat/script/benGov/CreateOpenQuarterlyElection.s.sol delete mode 100644 packages/hardhat/script/benGov/CreateRegistrationMonthlyElection.s.sol delete mode 100644 packages/hardhat/script/benGov/RemoveBeneficiariesFromRegistry.s.sol delete mode 100644 packages/hardhat/script/benGov/readme.MD delete mode 100644 packages/hardhat/test/forge/batchSlippageSimulation/AbstractSimulateBatchSlippage.t.sol delete mode 100644 packages/hardhat/test/forge/batchSlippageSimulation/SimulateThreeXBatchSlippage.t.sol delete mode 100644 packages/hardhat/test/forge/fork/ButterFeeConverter.t.sol delete mode 100644 packages/hardhat/test/forge/fork/FeeEscrow.t.sol diff --git a/packages/foundry/src/vault/strategy/1inch.sol b/packages/foundry/src/vault/strategy/1inch.sol deleted file mode 100644 index bc163570..00000000 --- a/packages/foundry/src/vault/strategy/1inch.sol +++ /dev/null @@ -1,4692 +0,0 @@ -/** - *Submitted for verification at Etherscan.io on 2022-11-04 -*/ - -/* - ,▄▓▓██▌ ,╓▄▄▓▓▓▓▓▓▓▓▄▄▄,, - ,▓██▓███▓▄▓███▓╬╬╬╬╬╬╬╬╬╬╬╬╬▓███▓▄, - ▄█ ▓██╬╣███████╬▓▀╬╬▓▓▓████████████▓█████▄, - ▓██▌ ▓██╬╣██████╬▓▌ ██████████████████████▌╙╙▀ⁿ - ▐████████╬▓████▓▓█╨ ▄ ╟█████████▓▓╬╬╬╬╬▓▓█████▓▄ - └▀▓▓▄╓ ╟█▓╣█████▓██████▀ ╓█▌ ███████▓▓▓▓▓╬╬╬╬╬╬╬╬╬╬╬╬▓██▓▄ - └▀████▓▄╥ ▐██╬╬██████████╙ Æ▀─ ▓███▀╚╠╬╩▀▀███████▓▓╬╬╬╬╬╬╬╬╬██▄ - └▀██▓▀▀█████▓╬▓██████▀ ▄█████▒╠" └╙▓██████▓╬╬╬╬╬╬╬╬██▄ - └▀██▄,└╙▀▀████▌└╙ ^"▀╙╙╙"╙██ @▄ ╙▀███████╬╬╬╬╬╬╬██µ - └▀██▓▄, ██▌ ╒ ╙█▓ ]▓█▓╔ ▀███████▓╬╬╬╬╬▓█▌ - ▀█████ ▓ ╟█▌ ]╠██▓░▒╓ ▀████████╬╬╬╬╣█▌ - ▐████ ╓█▀█▌ ,██▌ ╚Å███▓▒▒╠╓ ╙█████████╬╬╬╣█▌ - └████ ▓█░░▓█ ▀▀▀ φ▒╫████▒▒▒▒╠╓ █████████▓╬╬▓█µ - ╘███µ ▌▄█▓▄▓▀` ,▀ ,╔╠░▓██████▌╠▒▒▒φ ██████████╬╬██ - ▐████µ╙▓▀` ,▀╙,╔╔φφφ╠░▄▓███████▌░▓╙▒▒▒╠ └██╬███████╬▓█⌐ - ╫██ ▓▌ ▌φ▒▒░▓██████████████▌▒░▓╚▒▒▒╠ ▓██╬▓██████╣█▌ - ██▌ ▌╔▒▒▄████████████████▒▒▒░▌╠▒▒▒≥▐██▓╬╬███████▌ - ██▌ ,╓φ╠▓«▒▒▓████▀ ▀█████████▌▒▒▒╟░▒▒▒▒▐███╬╬╣████▓█▌ - ▐██ ╠▒▄▓▓███▓████└ ▀████████▌▒▒░▌╚▒▒▒▐███▓╬╬████ ╙▌ - ███ ) ╠▒░░░▒░╬████▀ └████████░▒▒░╬∩▒▒▓████╬╬╣███ - ▓██ ╠╠▒▒▐█▀▀▌`░╫██ ███████▒▒▒▒░▒▒½█████╬╬╣███ - ███ ,█▄ ╠▒▒▒╫▌,▄▀,▒╫██ ╟██████▒▒▒░╣⌠▒▓█████╬╬╣██▌ - ╘██µ ██` ╠▒▒░██╬φ╠▄▓██` ██████░░▌φ╠░▓█████▓╬╬▓██ - ╟██ .φ╠▒░▄█▀░░▄██▀└ █████▌▒╣φ▒░▓██████╬╬╣██ - ▀██▄▄▄╓▄███████▀ ▐█████░▓φ▒▄███████▓╬╣██ - ╙▀▀▀██▀└ ████▓▄▀φ▄▓████████╬▓█▀ - ▓███╬╩╔╣██████████▓██└ - ╓████▀▄▓████████▀████▀ - ,▓███████████████─]██╙ - ,▄▓██████████████▀└ ╙ - ,╓▄▓███████████████▀╙ - `"▀▀▀████████▀▀▀▀`▄███▀▀└ - └└ - - - - 11\ 11\ 11\ 11\ 11\ 11\ 11\ - 1111 | \__| 11 | 111\ 11 | 11 | 11 | - \_11 | 11\ 1111111\ 1111111\ 1111111\ 1111\ 11 | 111111\ 111111\ 11\ 11\ 11\ 111111\ 111111\ 11 | 11\ - 11 | 11 |11 __11\ 11 _____|11 __11\ 11 11\11 |11 __11\\_11 _| 11 | 11 | 11 |11 __11\ 11 __11\ 11 | 11 | - 11 | 11 |11 | 11 |11 / 11 | 11 | 11 \1111 |11111111 | 11 | 11 | 11 | 11 |11 / 11 |11 | \__|111111 / - 11 | 11 |11 | 11 |11 | 11 | 11 | 11 |\111 |11 ____| 11 |11\ 11 | 11 | 11 |11 | 11 |11 | 11 _11< - 111111\ 11 |11 | 11 |\1111111\ 11 | 11 | 11 | \11 |\1111111\ \1111 |\11111\1111 |\111111 |11 | 11 | \11\ - \______|\__|\__| \__| \_______|\__| \__| \__| \__| \_______| \____/ \_____\____/ \______/ \__| \__| \__| - - - - 111111\ 11\ 11\ - 11 __11\ 11 | \__| - 11 / 11 | 111111\ 111111\ 111111\ 111111\ 111111\ 111111\ 111111\ 11\ 111111\ 1111111\ - 11111111 |11 __11\ 11 __11\ 11 __11\ 11 __11\ 11 __11\ \____11\\_11 _| 11 |11 __11\ 11 __11\ - 11 __11 |11 / 11 |11 / 11 |11 | \__|11111111 |11 / 11 | 1111111 | 11 | 11 |11 / 11 |11 | 11 | - 11 | 11 |11 | 11 |11 | 11 |11 | 11 ____|11 | 11 |11 __11 | 11 |11\ 11 |11 | 11 |11 | 11 | - 11 | 11 |\1111111 |\1111111 |11 | \1111111\ \1111111 |\1111111 | \1111 |11 |\111111 |11 | 11 | - \__| \__| \____11 | \____11 |\__| \_______| \____11 | \_______| \____/ \__| \______/ \__| \__| - 11\ 11 |11\ 11 | 11\ 11 | - \111111 |\111111 | \111111 | - \______/ \______/ \______/ - 1111111\ 11\ - 11 __11\ 11 | - 11 | 11 | 111111\ 11\ 11\ 111111\ 111111\ 111111\ - 1111111 |11 __11\ 11 | 11 |\_11 _| 11 __11\ 11 __11\ - 11 __11< 11 / 11 |11 | 11 | 11 | 11111111 |11 | \__| - 11 | 11 |11 | 11 |11 | 11 | 11 |11\ 11 ____|11 | - 11 | 11 |\111111 |\111111 | \1111 |\1111111\ 11 | - \__| \__| \______/ \______/ \____/ \_______|\__| -*/ - -// SPDX-License-Identifier: MIT - -// File contracts/interfaces/IClipperExchangeInterface.sol - - -pragma solidity 0.8.17; - -/// @title Clipper interface subset used in swaps -interface IClipperExchangeInterface { - struct Signature { - uint8 v; - bytes32 r; - bytes32 s; - } - - function sellEthForToken(address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external payable; - function sellTokenForEth(address inputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external; - function swap(address inputToken, address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external; -} - - -// File contracts/helpers/RouterErrors.sol - - -pragma solidity 0.8.17; - -library RouterErrors { - error ReturnAmountIsNotEnough(); - error InvalidMsgValue(); - error ERC20TransferFailed(); -} - - -// File @1inch/solidity-utils/contracts/EthReceiver.sol@v2.1.1 - - -pragma solidity ^0.8.0; - -abstract contract EthReceiver { - error EthDepositRejected(); - - receive() external payable { - _receive(); - } - - function _receive() internal virtual { - // solhint-disable-next-line avoid-tx-origin - if (msg.sender == tx.origin) revert EthDepositRejected(); - } -} - - -// File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.7.3 - -// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Interface of the ERC20 standard as defined in the EIP. - */ -interface IERC20 { - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); - - /** - * @dev Returns the amount of tokens in existence. - */ - function totalSupply() external view returns (uint256); - - /** - * @dev Returns the amount of tokens owned by `account`. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * @dev Moves `amount` tokens from the caller's account to `to`. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transfer(address to, uint256 amount) external returns (bool); - - /** - * @dev Returns the remaining number of tokens that `spender` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - * - * This value changes when {approve} or {transferFrom} are called. - */ - function allowance(address owner, address spender) external view returns (uint256); - - /** - * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * IMPORTANT: Beware that changing an allowance with this method brings the risk - * that someone may use both the old and the new allowance by unfortunate - * transaction ordering. One possible solution to mitigate this race - * condition is to first reduce the spender's allowance to 0 and set the - * desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * - * Emits an {Approval} event. - */ - function approve(address spender, uint256 amount) external returns (bool); - - /** - * @dev Moves `amount` tokens from `from` to `to` using the - * allowance mechanism. `amount` is then deducted from the caller's - * allowance. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool); -} - - -// File @1inch/solidity-utils/contracts/interfaces/IDaiLikePermit.sol@v2.1.1 - - -pragma solidity ^0.8.0; - - -interface IDaiLikePermit { - function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external; -} - - -// File @1inch/solidity-utils/contracts/libraries/RevertReasonForwarder.sol@v2.1.1 - - -pragma solidity ^0.8.0; - -library RevertReasonForwarder { - function reRevert() internal pure { - // bubble up revert reason from latest external call - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - returndatacopy(ptr, 0, returndatasize()) - revert(ptr, returndatasize()) - } - } -} - - -// File @openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol@v4.7.3 - -// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in - * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. - * - * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by - * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't - * need to send a transaction, and thus is not required to hold Ether at all. - */ -interface IERC20Permit { - /** - * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * - * IMPORTANT: The same issues {IERC20-approve} has related to transaction - * ordering also apply here. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - * - * For more information on the signature format, see the - * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP - * section]. - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @dev Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - */ - function nonces(address owner) external view returns (uint256); - - /** - * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. - */ - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view returns (bytes32); -} - - -// File @1inch/solidity-utils/contracts/libraries/SafeERC20.sol@v2.1.1 - - -pragma solidity ^0.8.0; - - - - -library SafeERC20 { - error SafeTransferFailed(); - error SafeTransferFromFailed(); - error ForceApproveFailed(); - error SafeIncreaseAllowanceFailed(); - error SafeDecreaseAllowanceFailed(); - error SafePermitBadLength(); - - // Ensures method do not revert or return boolean `true`, admits call to non-smart-contract - function safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal { - bytes4 selector = token.transferFrom.selector; - bool success; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let data := mload(0x40) - - mstore(data, selector) - mstore(add(data, 0x04), from) - mstore(add(data, 0x24), to) - mstore(add(data, 0x44), amount) - success := call(gas(), token, 0, data, 100, 0x0, 0x20) - if success { - switch returndatasize() - case 0 { success := gt(extcodesize(token), 0) } - default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) } - } - } - if (!success) revert SafeTransferFromFailed(); - } - - // Ensures method do not revert or return boolean `true`, admits call to non-smart-contract - function safeTransfer(IERC20 token, address to, uint256 value) internal { - if (!_makeCall(token, token.transfer.selector, to, value)) { - revert SafeTransferFailed(); - } - } - - // If `approve(from, to, amount)` fails, try to `approve(from, to, 0)` before retry - function forceApprove(IERC20 token, address spender, uint256 value) internal { - if (!_makeCall(token, token.approve.selector, spender, value)) { - if (!_makeCall(token, token.approve.selector, spender, 0) || - !_makeCall(token, token.approve.selector, spender, value)) - { - revert ForceApproveFailed(); - } - } - } - - function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { - uint256 allowance = token.allowance(address(this), spender); - if (value > type(uint256).max - allowance) revert SafeIncreaseAllowanceFailed(); - forceApprove(token, spender, allowance + value); - } - - function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { - uint256 allowance = token.allowance(address(this), spender); - if (value > allowance) revert SafeDecreaseAllowanceFailed(); - forceApprove(token, spender, allowance - value); - } - - function safePermit(IERC20 token, bytes calldata permit) internal { - bool success; - if (permit.length == 32 * 7) { - success = _makeCalldataCall(token, IERC20Permit.permit.selector, permit); - } else if (permit.length == 32 * 8) { - success = _makeCalldataCall(token, IDaiLikePermit.permit.selector, permit); - } else { - revert SafePermitBadLength(); - } - if (!success) RevertReasonForwarder.reRevert(); - } - - function _makeCall(IERC20 token, bytes4 selector, address to, uint256 amount) private returns(bool success) { - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let data := mload(0x40) - - mstore(data, selector) - mstore(add(data, 0x04), to) - mstore(add(data, 0x24), amount) - success := call(gas(), token, 0, data, 0x44, 0x0, 0x20) - if success { - switch returndatasize() - case 0 { success := gt(extcodesize(token), 0) } - default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) } - } - } - } - - function _makeCalldataCall(IERC20 token, bytes4 selector, bytes calldata args) private returns(bool success) { - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let len := add(4, args.length) - let data := mload(0x40) - - mstore(data, selector) - calldatacopy(add(data, 0x04), args.offset, args.length) - success := call(gas(), token, 0, data, len, 0x0, 0x20) - if success { - switch returndatasize() - case 0 { success := gt(extcodesize(token), 0) } - default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) } - } - } - } -} - - -// File @1inch/solidity-utils/contracts/interfaces/IWETH.sol@v2.1.1 - - -pragma solidity ^0.8.0; - -interface IWETH is IERC20 { - function deposit() external payable; - function withdraw(uint256 amount) external; -} - - -// File contracts/routers/ClipperRouter.sol - - -pragma solidity 0.8.17; - - - - - - -/// @title Clipper router that allows to use `ClipperExchangeInterface` for swaps -contract ClipperRouter is EthReceiver { - using SafeERC20 for IERC20; - - uint256 private constant _SIGNATURE_S_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; - uint256 private constant _SIGNATURE_V_SHIFT = 255; - bytes6 private constant _INCH_TAG_WITH_LENGTH_PREFIX = "\x051INCH"; - IERC20 private constant _ETH = IERC20(address(0)); - IWETH private immutable _WETH; // solhint-disable-line var-name-mixedcase - - constructor(IWETH weth) { - _WETH = weth; - } - - /// @notice Same as `clipperSwapTo` but calls permit first, - /// allowing to approve token spending and make a swap in one transaction. - /// @dev See tests for examples - /// @param recipient Address that will receive swap funds - /// @param srcToken Source token - /// @param dstToken Destination token - /// @param inputAmount Amount of source tokens to swap - /// @param outputAmount Amount of destination tokens to receive - /// @param goodUntil Timestamp until the swap will be valid - /// @param r Clipper order signature (r part) - /// @param vs Clipper order signature (vs part) - /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls. - /// @return returnAmount Amount of destination tokens received - function clipperSwapToWithPermit( - IClipperExchangeInterface clipperExchange, - address payable recipient, - IERC20 srcToken, - IERC20 dstToken, - uint256 inputAmount, - uint256 outputAmount, - uint256 goodUntil, - bytes32 r, - bytes32 vs, - bytes calldata permit - ) external returns(uint256 returnAmount) { - srcToken.safePermit(permit); - return clipperSwapTo(clipperExchange, recipient, srcToken, dstToken, inputAmount, outputAmount, goodUntil, r, vs); - } - - /// @notice Same as `clipperSwapTo` but uses `msg.sender` as recipient - /// @param srcToken Source token - /// @param dstToken Destination token - /// @param inputAmount Amount of source tokens to swap - /// @param outputAmount Amount of destination tokens to receive - /// @param goodUntil Timestamp until the swap will be valid - /// @param r Clipper order signature (r part) - /// @param vs Clipper order signature (vs part) - /// @return returnAmount Amount of destination tokens received - function clipperSwap( - IClipperExchangeInterface clipperExchange, - IERC20 srcToken, - IERC20 dstToken, - uint256 inputAmount, - uint256 outputAmount, - uint256 goodUntil, - bytes32 r, - bytes32 vs - ) external payable returns(uint256 returnAmount) { - return clipperSwapTo(clipperExchange, payable(msg.sender), srcToken, dstToken, inputAmount, outputAmount, goodUntil, r, vs); - } - - /// @notice Performs swap using Clipper exchange. Wraps and unwraps ETH if required. - /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited - /// @param recipient Address that will receive swap funds - /// @param srcToken Source token - /// @param dstToken Destination token - /// @param inputAmount Amount of source tokens to swap - /// @param outputAmount Amount of destination tokens to receive - /// @param goodUntil Timestamp until the swap will be valid - /// @param r Clipper order signature (r part) - /// @param vs Clipper order signature (vs part) - /// @return returnAmount Amount of destination tokens received - function clipperSwapTo( - IClipperExchangeInterface clipperExchange, - address payable recipient, - IERC20 srcToken, - IERC20 dstToken, - uint256 inputAmount, - uint256 outputAmount, - uint256 goodUntil, - bytes32 r, - bytes32 vs - ) public payable returns(uint256 returnAmount) { - bool srcETH = srcToken == _ETH; - if (srcETH) { - if (msg.value != inputAmount) revert RouterErrors.InvalidMsgValue(); - } else if (srcToken == _WETH) { - srcETH = true; - if (msg.value != 0) revert RouterErrors.InvalidMsgValue(); - // _WETH.transferFrom(msg.sender, address(this), inputAmount); - // _WETH.withdraw(inputAmount); - address weth = address(_WETH); - bytes4 transferFromSelector = _WETH.transferFrom.selector; - bytes4 withdrawSelector = _WETH.withdraw.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - mstore(ptr, transferFromSelector) - mstore(add(ptr, 0x04), caller()) - mstore(add(ptr, 0x24), address()) - mstore(add(ptr, 0x44), inputAmount) - if iszero(call(gas(), weth, 0, ptr, 0x64, 0, 0)) { - returndatacopy(ptr, 0, returndatasize()) - revert(ptr, returndatasize()) - } - - mstore(ptr, withdrawSelector) - mstore(add(ptr, 0x04), inputAmount) - if iszero(call(gas(), weth, 0, ptr, 0x24, 0, 0)) { - returndatacopy(ptr, 0, returndatasize()) - revert(ptr, returndatasize()) - } - } - } else { - if (msg.value != 0) revert RouterErrors.InvalidMsgValue(); - srcToken.safeTransferFrom(msg.sender, address(clipperExchange), inputAmount); - } - - if (srcETH) { - // clipperExchange.sellEthForToken{value: inputAmount}(address(dstToken), inputAmount, outputAmount, goodUntil, recipient, signature, _INCH_TAG); - address clipper = address(clipperExchange); - bytes4 selector = clipperExchange.sellEthForToken.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - mstore(ptr, selector) - mstore(add(ptr, 0x04), dstToken) - mstore(add(ptr, 0x24), inputAmount) - mstore(add(ptr, 0x44), outputAmount) - mstore(add(ptr, 0x64), goodUntil) - mstore(add(ptr, 0x84), recipient) - mstore(add(ptr, 0xa4), add(27, shr(_SIGNATURE_V_SHIFT, vs))) - mstore(add(ptr, 0xc4), r) - mstore(add(ptr, 0xe4), and(vs, _SIGNATURE_S_MASK)) - mstore(add(ptr, 0x104), 0x120) - mstore(add(ptr, 0x143), _INCH_TAG_WITH_LENGTH_PREFIX) - if iszero(call(gas(), clipper, inputAmount, ptr, 0x149, 0, 0)) { - returndatacopy(ptr, 0, returndatasize()) - revert(ptr, returndatasize()) - } - } - } else if (dstToken == _ETH || dstToken == _WETH) { - // clipperExchange.sellTokenForEth(address(srcToken), inputAmount, outputAmount, goodUntil, recipient, signature, _INCH_TAG); - address clipper = address(clipperExchange); - bytes4 selector = clipperExchange.sellTokenForEth.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - mstore(ptr, selector) - mstore(add(ptr, 0x04), srcToken) - mstore(add(ptr, 0x24), inputAmount) - mstore(add(ptr, 0x44), outputAmount) - mstore(add(ptr, 0x64), goodUntil) - switch iszero(dstToken) - case 1 { - mstore(add(ptr, 0x84), recipient) - } - default { - mstore(add(ptr, 0x84), address()) - } - mstore(add(ptr, 0xa4), add(27, shr(_SIGNATURE_V_SHIFT, vs))) - mstore(add(ptr, 0xc4), r) - mstore(add(ptr, 0xe4), and(vs, _SIGNATURE_S_MASK)) - mstore(add(ptr, 0x104), 0x120) - mstore(add(ptr, 0x143), _INCH_TAG_WITH_LENGTH_PREFIX) - if iszero(call(gas(), clipper, 0, ptr, 0x149, 0, 0)) { - returndatacopy(ptr, 0, returndatasize()) - revert(ptr, returndatasize()) - } - } - - if (dstToken == _WETH) { - // _WETH.deposit{value: outputAmount}(); - // _WETH.transfer(recipient, outputAmount); - address weth = address(_WETH); - bytes4 depositSelector = _WETH.deposit.selector; - bytes4 transferSelector = _WETH.transfer.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - mstore(ptr, depositSelector) - if iszero(call(gas(), weth, outputAmount, ptr, 0x04, 0, 0)) { - returndatacopy(ptr, 0, returndatasize()) - revert(ptr, returndatasize()) - } - - mstore(ptr, transferSelector) - mstore(add(ptr, 0x04), recipient) - mstore(add(ptr, 0x24), outputAmount) - if iszero(call(gas(), weth, 0, ptr, 0x44, 0, 0)) { - returndatacopy(ptr, 0, returndatasize()) - revert(ptr, returndatasize()) - } - } - } - } else { - // clipperExchange.swap(address(srcToken), address(dstToken), inputAmount, outputAmount, goodUntil, recipient, signature, _INCH_TAG); - address clipper = address(clipperExchange); - bytes4 selector = clipperExchange.swap.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - mstore(ptr, selector) - mstore(add(ptr, 0x04), srcToken) - mstore(add(ptr, 0x24), dstToken) - mstore(add(ptr, 0x44), inputAmount) - mstore(add(ptr, 0x64), outputAmount) - mstore(add(ptr, 0x84), goodUntil) - mstore(add(ptr, 0xa4), recipient) - mstore(add(ptr, 0xc4), add(27, shr(_SIGNATURE_V_SHIFT, vs))) - mstore(add(ptr, 0xe4), r) - mstore(add(ptr, 0x104), and(vs, _SIGNATURE_S_MASK)) - mstore(add(ptr, 0x124), 0x140) - mstore(add(ptr, 0x163), _INCH_TAG_WITH_LENGTH_PREFIX) - if iszero(call(gas(), clipper, 0, ptr, 0x169, 0, 0)) { - returndatacopy(ptr, 0, returndatasize()) - revert(ptr, returndatasize()) - } - } - } - - return outputAmount; - } -} - - -// File contracts/interfaces/IAggregationExecutor.sol - - -pragma solidity 0.8.17; - -/// @title Interface for making arbitrary calls during swap -interface IAggregationExecutor { - /// @notice propagates information about original msg.sender and executes arbitrary data - function execute(address msgSender) external payable; // 0x4b64e492 -} - - -// File @1inch/solidity-utils/contracts/interfaces/IERC20MetadataUppercase.sol@v2.1.1 - - -pragma solidity ^0.8.0; - - -interface IERC20MetadataUppercase { - function NAME() external view returns (string memory); // solhint-disable-line func-name-mixedcase - function SYMBOL() external view returns (string memory); // solhint-disable-line func-name-mixedcase -} - - -// File @1inch/solidity-utils/contracts/libraries/StringUtil.sol@v2.1.1 - - -pragma solidity ^0.8.0; - -/// @title Library with gas-efficient string operations -library StringUtil { - function toHex(uint256 value) internal pure returns (string memory) { - return toHex(abi.encodePacked(value)); - } - - function toHex(address value) internal pure returns (string memory) { - return toHex(abi.encodePacked(value)); - } - - function toHex(bytes memory data) internal pure returns (string memory result) { - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - function _toHex16(input) -> output { - output := or( - and(input, 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000), - shr(64, and(input, 0x0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000)) - ) - output := or( - and(output, 0xFFFFFFFF000000000000000000000000FFFFFFFF000000000000000000000000), - shr(32, and(output, 0x00000000FFFFFFFF000000000000000000000000FFFFFFFF0000000000000000)) - ) - output := or( - and(output, 0xFFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000), - shr(16, and(output, 0x0000FFFF000000000000FFFF000000000000FFFF000000000000FFFF00000000)) - ) - output := or( - and(output, 0xFF000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000), - shr(8, and(output, 0x00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000)) - ) - output := or( - shr(4, and(output, 0xF000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000)), - shr(8, and(output, 0x0F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F00)) - ) - output := add( - add(0x3030303030303030303030303030303030303030303030303030303030303030, output), - mul( - and( - shr(4, add(output, 0x0606060606060606060606060606060606060606060606060606060606060606)), - 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F - ), - 7 // Change 7 to 39 for lower case output - ) - ) - } - - result := mload(0x40) - let length := mload(data) - let resultLength := shl(1, length) - let toPtr := add(result, 0x22) // 32 bytes for length + 2 bytes for '0x' - mstore(0x40, add(toPtr, resultLength)) // move free memory pointer - mstore(add(result, 2), 0x3078) // 0x3078 is right aligned so we write to `result + 2` - // to store the last 2 bytes in the beginning of the string - mstore(result, add(resultLength, 2)) // extra 2 bytes for '0x' - - for { - let fromPtr := add(data, 0x20) - let endPtr := add(fromPtr, length) - } lt(fromPtr, endPtr) { - fromPtr := add(fromPtr, 0x20) - } { - let rawData := mload(fromPtr) - let hexData := _toHex16(rawData) - mstore(toPtr, hexData) - toPtr := add(toPtr, 0x20) - hexData := _toHex16(shl(128, rawData)) - mstore(toPtr, hexData) - toPtr := add(toPtr, 0x20) - } - } - } -} - - -// File @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol@v4.7.3 - -// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Interface for the optional metadata functions from the ERC20 standard. - * - * _Available since v4.1._ - */ -interface IERC20Metadata is IERC20 { - /** - * @dev Returns the name of the token. - */ - function name() external view returns (string memory); - - /** - * @dev Returns the symbol of the token. - */ - function symbol() external view returns (string memory); - - /** - * @dev Returns the decimals places of the token. - */ - function decimals() external view returns (uint8); -} - - -// File @1inch/solidity-utils/contracts/libraries/UniERC20.sol@v2.1.1 - - -pragma solidity ^0.8.0; - - - - - -library UniERC20 { - using SafeERC20 for IERC20; - - error InsufficientBalance(); - error ApproveCalledOnETH(); - error NotEnoughValue(); - error FromIsNotSender(); - error ToIsNotThis(); - error ETHTransferFailed(); - - uint256 private constant _RAW_CALL_GAS_LIMIT = 5000; - IERC20 private constant _ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); - IERC20 private constant _ZERO_ADDRESS = IERC20(address(0)); - - function isETH(IERC20 token) internal pure returns (bool) { - return (token == _ZERO_ADDRESS || token == _ETH_ADDRESS); - } - - function uniBalanceOf(IERC20 token, address account) internal view returns (uint256) { - if (isETH(token)) { - return account.balance; - } else { - return token.balanceOf(account); - } - } - - /// @dev note that this function does nothing in case of zero amount - function uniTransfer(IERC20 token, address payable to, uint256 amount) internal { - if (amount > 0) { - if (isETH(token)) { - if (address(this).balance < amount) revert InsufficientBalance(); - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = to.call{value: amount, gas: _RAW_CALL_GAS_LIMIT}(""); - if (!success) revert ETHTransferFailed(); - } else { - token.safeTransfer(to, amount); - } - } - } - - /// @dev note that this function does nothing in case of zero amount - function uniTransferFrom(IERC20 token, address payable from, address to, uint256 amount) internal { - if (amount > 0) { - if (isETH(token)) { - if (msg.value < amount) revert NotEnoughValue(); - if (from != msg.sender) revert FromIsNotSender(); - if (to != address(this)) revert ToIsNotThis(); - if (msg.value > amount) { - // Return remainder if exist - unchecked { - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = from.call{value: msg.value - amount, gas: _RAW_CALL_GAS_LIMIT}(""); - if (!success) revert ETHTransferFailed(); - } - } - } else { - token.safeTransferFrom(from, to, amount); - } - } - } - - function uniSymbol(IERC20 token) internal view returns(string memory) { - return _uniDecode(token, IERC20Metadata.symbol.selector, IERC20MetadataUppercase.SYMBOL.selector); - } - - function uniName(IERC20 token) internal view returns(string memory) { - return _uniDecode(token, IERC20Metadata.name.selector, IERC20MetadataUppercase.NAME.selector); - } - - function uniApprove(IERC20 token, address to, uint256 amount) internal { - if (isETH(token)) revert ApproveCalledOnETH(); - - token.forceApprove(to, amount); - } - - /// 20K gas is provided to account for possible implementations of name/symbol - /// (token implementation might be behind proxy or store the value in storage) - function _uniDecode(IERC20 token, bytes4 lowerCaseSelector, bytes4 upperCaseSelector) private view returns(string memory result) { - if (isETH(token)) { - return "ETH"; - } - - (bool success, bytes memory data) = address(token).staticcall{ gas: 20000 }( - abi.encodeWithSelector(lowerCaseSelector) - ); - if (!success) { - (success, data) = address(token).staticcall{ gas: 20000 }( - abi.encodeWithSelector(upperCaseSelector) - ); - } - - if (success && data.length >= 0x40) { - (uint256 offset, uint256 len) = abi.decode(data, (uint256, uint256)); - if (offset == 0x20 && len > 0 && data.length == 0x40 + len) { - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - result := add(data, 0x20) - } - return result; - } - } - - if (success && data.length == 32) { - uint256 len = 0; - while (len < data.length && data[len] >= 0x20 && data[len] <= 0x7E) { - unchecked { - len++; - } - } - - if (len > 0) { - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - mstore(data, len) - } - return string(data); - } - } - - return StringUtil.toHex(address(token)); - } -} - - -// File contracts/routers/GenericRouter.sol - - -pragma solidity 0.8.17; - - - - - -contract GenericRouter is EthReceiver { - using UniERC20 for IERC20; - using SafeERC20 for IERC20; - - error ZeroMinReturn(); - error ZeroReturnAmount(); - - uint256 private constant _PARTIAL_FILL = 1 << 0; - uint256 private constant _REQUIRES_EXTRA_ETH = 1 << 1; - - struct SwapDescription { - IERC20 srcToken; - IERC20 dstToken; - address payable srcReceiver; - address payable dstReceiver; - uint256 amount; - uint256 minReturnAmount; - uint256 flags; - } - - /// @notice Performs a swap, delegating all calls encoded in `data` to `executor`. See tests for usage examples - /// @dev router keeps 1 wei of every token on the contract balance for gas optimisations reasons. This affects first swap of every token by leaving 1 wei on the contract. - /// @param executor Aggregation executor that executes calls described in `data` - /// @param desc Swap description - /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls. - /// @param data Encoded calls that `caller` should execute in between of swaps - /// @return returnAmount Resulting token amount - /// @return spentAmount Source token amount - function swap( - IAggregationExecutor executor, - SwapDescription calldata desc, - bytes calldata permit, - bytes calldata data - ) - external - payable - returns ( - uint256 returnAmount, - uint256 spentAmount - ) - { - if (desc.minReturnAmount == 0) revert ZeroMinReturn(); - - IERC20 srcToken = desc.srcToken; - IERC20 dstToken = desc.dstToken; - - bool srcETH = srcToken.isETH(); - if (desc.flags & _REQUIRES_EXTRA_ETH != 0) { - if (msg.value <= (srcETH ? desc.amount : 0)) revert RouterErrors.InvalidMsgValue(); - } else { - if (msg.value != (srcETH ? desc.amount : 0)) revert RouterErrors.InvalidMsgValue(); - } - - if (!srcETH) { - if (permit.length > 0) { - srcToken.safePermit(permit); - } - srcToken.safeTransferFrom(msg.sender, desc.srcReceiver, desc.amount); - } - - _execute(executor, msg.sender, desc.amount, data); - - spentAmount = desc.amount; - // we leave 1 wei on the router for gas optimisations reasons - returnAmount = dstToken.uniBalanceOf(address(this)); - if (returnAmount == 0) revert ZeroReturnAmount(); - unchecked { returnAmount--; } - - if (desc.flags & _PARTIAL_FILL != 0) { - uint256 unspentAmount = srcToken.uniBalanceOf(address(this)); - if (unspentAmount > 1) { - // we leave 1 wei on the router for gas optimisations reasons - unchecked { unspentAmount--; } - spentAmount -= unspentAmount; - srcToken.uniTransfer(payable(msg.sender), unspentAmount); - } - if (returnAmount * desc.amount < desc.minReturnAmount * spentAmount) revert RouterErrors.ReturnAmountIsNotEnough(); - } else { - if (returnAmount < desc.minReturnAmount) revert RouterErrors.ReturnAmountIsNotEnough(); - } - - address payable dstReceiver = (desc.dstReceiver == address(0)) ? payable(msg.sender) : desc.dstReceiver; - dstToken.uniTransfer(dstReceiver, returnAmount); - } - - function _execute( - IAggregationExecutor executor, - address srcTokenOwner, - uint256 inputAmount, - bytes calldata data - ) private { - bytes4 executeSelector = executor.execute.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - mstore(ptr, executeSelector) - mstore(add(ptr, 0x04), srcTokenOwner) - calldatacopy(add(ptr, 0x24), data.offset, data.length) - mstore(add(add(ptr, 0x24), data.length), inputAmount) - - if iszero(call(gas(), executor, callvalue(), ptr, add(0x44, data.length), 0, 0)) { - returndatacopy(ptr, 0, returndatasize()) - revert(ptr, returndatasize()) - } - } - } -} - - -// File contracts/routers/UnoswapRouter.sol - - -pragma solidity 0.8.17; - - - - -contract UnoswapRouter is EthReceiver { - using SafeERC20 for IERC20; - - error ReservesCallFailed(); - error SwapAmountTooLarge(); - - bytes4 private constant _TRANSFER_FROM_CALL_SELECTOR = 0x23b872dd; - bytes4 private constant _WETH_DEPOSIT_CALL_SELECTOR = 0xd0e30db0; - bytes4 private constant _WETH_WITHDRAW_CALL_SELECTOR = 0x2e1a7d4d; - bytes4 private constant _ERC20_TRANSFER_CALL_SELECTOR = 0xa9059cbb; - uint256 private constant _ADDRESS_MASK = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff; - uint256 private constant _REVERSE_MASK = 0x8000000000000000000000000000000000000000000000000000000000000000; - uint256 private constant _WETH_MASK = 0x4000000000000000000000000000000000000000000000000000000000000000; - uint256 private constant _NUMERATOR_MASK = 0x0000000000000000ffffffff0000000000000000000000000000000000000000; - /// @dev WETH address is network-specific and needs to be changed before deployment. - /// It can not be moved to immutable as immutables are not supported in assembly - address private constant _WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - bytes4 private constant _UNISWAP_PAIR_RESERVES_CALL_SELECTOR = 0x0902f1ac; - bytes4 private constant _UNISWAP_PAIR_SWAP_CALL_SELECTOR = 0x022c0d9f; - uint256 private constant _DENOMINATOR = 1e9; - uint256 private constant _NUMERATOR_OFFSET = 160; - uint256 private constant _MAX_SWAP_AMOUNT = (1 << 112) - 1; // type(uint112).max; - - /// @notice Same as `unoswapTo` but calls permit first, - /// allowing to approve token spending and make a swap in one transaction. - /// @param recipient Address that will receive swapped funds - /// @param srcToken Source token - /// @param amount Amount of source tokens to swap - /// @param minReturn Minimal allowed returnAmount to make transaction commit - /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen - /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls. - /// See tests for examples - function unoswapToWithPermit( - address payable recipient, - IERC20 srcToken, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools, - bytes calldata permit - ) external returns(uint256 returnAmount) { - srcToken.safePermit(permit); - return _unoswap(recipient, srcToken, amount, minReturn, pools); - } - - /// @notice Performs swap using Uniswap exchange. Wraps and unwraps ETH if required. - /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited - /// @param recipient Address that will receive swapped funds - /// @param srcToken Source token - /// @param amount Amount of source tokens to swap - /// @param minReturn Minimal allowed returnAmount to make transaction commit - /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen - function unoswapTo( - address payable recipient, - IERC20 srcToken, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools - ) external payable returns(uint256 returnAmount) { - return _unoswap(recipient, srcToken, amount, minReturn, pools); - } - - /// @notice Performs swap using Uniswap exchange. Wraps and unwraps ETH if required. - /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited - /// @param srcToken Source token - /// @param amount Amount of source tokens to swap - /// @param minReturn Minimal allowed returnAmount to make transaction commit - /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen - function unoswap( - IERC20 srcToken, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools - ) external payable returns(uint256 returnAmount) { - return _unoswap(payable(msg.sender), srcToken, amount, minReturn, pools); - } - - function _unoswap( - address payable recipient, - IERC20 srcToken, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools - ) private returns(uint256 returnAmount) { - assembly { // solhint-disable-line no-inline-assembly - function reRevert() { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } - - function validateERC20Transfer(status) { - if iszero(status) { - reRevert() - } - let success := or( - iszero(returndatasize()), // empty return data - and(gt(returndatasize(), 31), eq(mload(0), 1)) // true in return data - ) - if iszero(success) { - mstore(0, 0xf27f64e400000000000000000000000000000000000000000000000000000000) // ERC20TransferFailed() - revert(0, 4) - } - } - - function swap(emptyPtr, swapAmount, pair, reversed, numerator, to) -> ret { - mstore(emptyPtr, _UNISWAP_PAIR_RESERVES_CALL_SELECTOR) - if iszero(staticcall(gas(), pair, emptyPtr, 0x4, emptyPtr, 0x40)) { - reRevert() - } - if iszero(eq(returndatasize(), 0x60)) { - mstore(0, 0x85cd58dc00000000000000000000000000000000000000000000000000000000) // ReservesCallFailed() - revert(0, 4) - } - - let reserve0 := mload(emptyPtr) - let reserve1 := mload(add(emptyPtr, 0x20)) - if reversed { - let tmp := reserve0 - reserve0 := reserve1 - reserve1 := tmp - } - // this will not overflow as reserve0, reserve1 and ret fit to 112 bit and numerator and _DENOMINATOR fit to 32 bit - ret := mul(swapAmount, numerator) - ret := div(mul(ret, reserve1), add(ret, mul(reserve0, _DENOMINATOR))) - - mstore(emptyPtr, _UNISWAP_PAIR_SWAP_CALL_SELECTOR) - reversed := iszero(reversed) - mstore(add(emptyPtr, 0x04), mul(ret, iszero(reversed))) - mstore(add(emptyPtr, 0x24), mul(ret, reversed)) - mstore(add(emptyPtr, 0x44), to) - mstore(add(emptyPtr, 0x64), 0x80) - mstore(add(emptyPtr, 0x84), 0) - if iszero(call(gas(), pair, 0, emptyPtr, 0xa4, 0, 0)) { - reRevert() - } - } - - // make sure that input amount fits in 112 bit - if gt(amount, _MAX_SWAP_AMOUNT) { - mstore(0, 0xcf0b4d3a00000000000000000000000000000000000000000000000000000000) // SwapAmountTooLarge() - revert(0, 4) - } - - let emptyPtr := mload(0x40) - mstore(0x40, add(emptyPtr, 0xc0)) - - let poolsEndOffset := add(pools.offset, shl(5, pools.length)) - let rawPair := calldataload(pools.offset) - switch srcToken - case 0 { - if iszero(eq(amount, callvalue())) { - mstore(0, 0x1841b4e100000000000000000000000000000000000000000000000000000000) // InvalidMsgValue() - revert(0, 4) - } - - mstore(emptyPtr, _WETH_DEPOSIT_CALL_SELECTOR) - if iszero(call(gas(), _WETH, amount, emptyPtr, 0x4, 0, 0)) { - reRevert() - } - - mstore(emptyPtr, _ERC20_TRANSFER_CALL_SELECTOR) - mstore(add(emptyPtr, 0x4), and(rawPair, _ADDRESS_MASK)) - mstore(add(emptyPtr, 0x24), amount) - if iszero(call(gas(), _WETH, 0, emptyPtr, 0x44, 0, 0)) { - reRevert() - } - } - default { - if callvalue() { - mstore(0, 0x1841b4e100000000000000000000000000000000000000000000000000000000) // InvalidMsgValue() - revert(0, 4) - } - - mstore(emptyPtr, _TRANSFER_FROM_CALL_SELECTOR) - mstore(add(emptyPtr, 0x4), caller()) - mstore(add(emptyPtr, 0x24), and(rawPair, _ADDRESS_MASK)) - mstore(add(emptyPtr, 0x44), amount) - validateERC20Transfer( - call(gas(), srcToken, 0, emptyPtr, 0x64, 0, 0x20) - ) - } - - returnAmount := amount - - for {let i := add(pools.offset, 0x20)} lt(i, poolsEndOffset) {i := add(i, 0x20)} { - let nextRawPair := calldataload(i) - - returnAmount := swap( - emptyPtr, - returnAmount, - and(rawPair, _ADDRESS_MASK), - and(rawPair, _REVERSE_MASK), - shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)), - and(nextRawPair, _ADDRESS_MASK) - ) - - rawPair := nextRawPair - } - - switch and(rawPair, _WETH_MASK) - case 0 { - returnAmount := swap( - emptyPtr, - returnAmount, - and(rawPair, _ADDRESS_MASK), - and(rawPair, _REVERSE_MASK), - shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)), - recipient - ) - } - default { - returnAmount := swap( - emptyPtr, - returnAmount, - and(rawPair, _ADDRESS_MASK), - and(rawPair, _REVERSE_MASK), - shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)), - address() - ) - - mstore(emptyPtr, _WETH_WITHDRAW_CALL_SELECTOR) - mstore(add(emptyPtr, 0x04), returnAmount) - if iszero(call(gas(), _WETH, 0, emptyPtr, 0x24, 0, 0)) { - reRevert() - } - - if iszero(call(gas(), recipient, returnAmount, 0, 0, 0, 0)) { - reRevert() - } - } - } - if (returnAmount < minReturn) revert RouterErrors.ReturnAmountIsNotEnough(); - } -} - - -// File contracts/interfaces/IUniswapV3Pool.sol - -pragma solidity 0.8.17; - -interface IUniswapV3Pool { - /// @notice Swap token0 for token1, or token1 for token0 - /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback - /// @param recipient The address to receive the output of the swap - /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 - /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) - /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this - /// value after the swap. If one for zero, the price cannot be greater than this value after the swap - /// @param data Any data to be passed through to the callback - /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive - /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive - function swap( - address recipient, - bool zeroForOne, - int256 amountSpecified, - uint160 sqrtPriceLimitX96, - bytes calldata data - ) external returns (int256 amount0, int256 amount1); - - /// @notice The first of the two tokens of the pool, sorted by address - /// @return The token contract address - function token0() external view returns (address); - - /// @notice The second of the two tokens of the pool, sorted by address - /// @return The token contract address - function token1() external view returns (address); - - /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 - /// @return The fee - function fee() external view returns (uint24); -} - - -// File contracts/interfaces/IUniswapV3SwapCallback.sol - -pragma solidity 0.8.17; - -/// @title Callback for IUniswapV3PoolActions#swap -/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface -interface IUniswapV3SwapCallback { - /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. - /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call - function uniswapV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external; -} - - -// File @openzeppelin/contracts/utils/Address.sol@v4.7.3 - -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) - -pragma solidity ^0.8.1; - -/** - * @dev Collection of functions related to the address type - */ -library Address { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - * - * [IMPORTANT] - * ==== - * You shouldn't rely on `isContract` to protect against flash loan attacks! - * - * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets - * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract - * constructor. - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize/address.code.length, which returns 0 - // for contracts in construction, since the code is only stored at the end - // of the constructor execution. - - return account.code.length > 0; - } - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - require(address(this).balance >= amount, "Address: insufficient balance"); - - (bool success, ) = recipient.call{value: amount}(""); - require(success, "Address: unable to send value, recipient may have reverted"); - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain `call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason, it is bubbled up by this - * function (like regular Solidity function calls). - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCall(target, data, "Address: low-level call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); - } - - /** - * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value, - string memory errorMessage - ) internal returns (bytes memory) { - require(address(this).balance >= value, "Address: insufficient balance for call"); - require(isContract(target), "Address: call to non-contract"); - - (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, "Address: low-level static call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall( - address target, - bytes memory data, - string memory errorMessage - ) internal view returns (bytes memory) { - require(isContract(target), "Address: static call to non-contract"); - - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, "Address: low-level delegate call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - require(isContract(target), "Address: delegate call to non-contract"); - - (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason using the provided one. - * - * _Available since v4.3._ - */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { - if (success) { - return returndata; - } else { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } - } -} - - -// File @openzeppelin/contracts/utils/math/SafeCast.sol@v4.7.3 - -// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/SafeCast.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow - * checks. - * - * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can - * easily result in undesired exploitation or bugs, since developers usually - * assume that overflows raise errors. `SafeCast` restores this intuition by - * reverting the transaction when such an operation overflows. - * - * Using this library instead of the unchecked operations eliminates an entire - * class of bugs, so it's recommended to use it always. - * - * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing - * all math on `uint256` and `int256` and then downcasting. - */ -library SafeCast { - /** - * @dev Returns the downcasted uint248 from uint256, reverting on - * overflow (when the input is greater than largest uint248). - * - * Counterpart to Solidity's `uint248` operator. - * - * Requirements: - * - * - input must fit into 248 bits - * - * _Available since v4.7._ - */ - function toUint248(uint256 value) internal pure returns (uint248) { - require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits"); - return uint248(value); - } - - /** - * @dev Returns the downcasted uint240 from uint256, reverting on - * overflow (when the input is greater than largest uint240). - * - * Counterpart to Solidity's `uint240` operator. - * - * Requirements: - * - * - input must fit into 240 bits - * - * _Available since v4.7._ - */ - function toUint240(uint256 value) internal pure returns (uint240) { - require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits"); - return uint240(value); - } - - /** - * @dev Returns the downcasted uint232 from uint256, reverting on - * overflow (when the input is greater than largest uint232). - * - * Counterpart to Solidity's `uint232` operator. - * - * Requirements: - * - * - input must fit into 232 bits - * - * _Available since v4.7._ - */ - function toUint232(uint256 value) internal pure returns (uint232) { - require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits"); - return uint232(value); - } - - /** - * @dev Returns the downcasted uint224 from uint256, reverting on - * overflow (when the input is greater than largest uint224). - * - * Counterpart to Solidity's `uint224` operator. - * - * Requirements: - * - * - input must fit into 224 bits - * - * _Available since v4.2._ - */ - function toUint224(uint256 value) internal pure returns (uint224) { - require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits"); - return uint224(value); - } - - /** - * @dev Returns the downcasted uint216 from uint256, reverting on - * overflow (when the input is greater than largest uint216). - * - * Counterpart to Solidity's `uint216` operator. - * - * Requirements: - * - * - input must fit into 216 bits - * - * _Available since v4.7._ - */ - function toUint216(uint256 value) internal pure returns (uint216) { - require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits"); - return uint216(value); - } - - /** - * @dev Returns the downcasted uint208 from uint256, reverting on - * overflow (when the input is greater than largest uint208). - * - * Counterpart to Solidity's `uint208` operator. - * - * Requirements: - * - * - input must fit into 208 bits - * - * _Available since v4.7._ - */ - function toUint208(uint256 value) internal pure returns (uint208) { - require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits"); - return uint208(value); - } - - /** - * @dev Returns the downcasted uint200 from uint256, reverting on - * overflow (when the input is greater than largest uint200). - * - * Counterpart to Solidity's `uint200` operator. - * - * Requirements: - * - * - input must fit into 200 bits - * - * _Available since v4.7._ - */ - function toUint200(uint256 value) internal pure returns (uint200) { - require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits"); - return uint200(value); - } - - /** - * @dev Returns the downcasted uint192 from uint256, reverting on - * overflow (when the input is greater than largest uint192). - * - * Counterpart to Solidity's `uint192` operator. - * - * Requirements: - * - * - input must fit into 192 bits - * - * _Available since v4.7._ - */ - function toUint192(uint256 value) internal pure returns (uint192) { - require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits"); - return uint192(value); - } - - /** - * @dev Returns the downcasted uint184 from uint256, reverting on - * overflow (when the input is greater than largest uint184). - * - * Counterpart to Solidity's `uint184` operator. - * - * Requirements: - * - * - input must fit into 184 bits - * - * _Available since v4.7._ - */ - function toUint184(uint256 value) internal pure returns (uint184) { - require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits"); - return uint184(value); - } - - /** - * @dev Returns the downcasted uint176 from uint256, reverting on - * overflow (when the input is greater than largest uint176). - * - * Counterpart to Solidity's `uint176` operator. - * - * Requirements: - * - * - input must fit into 176 bits - * - * _Available since v4.7._ - */ - function toUint176(uint256 value) internal pure returns (uint176) { - require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits"); - return uint176(value); - } - - /** - * @dev Returns the downcasted uint168 from uint256, reverting on - * overflow (when the input is greater than largest uint168). - * - * Counterpart to Solidity's `uint168` operator. - * - * Requirements: - * - * - input must fit into 168 bits - * - * _Available since v4.7._ - */ - function toUint168(uint256 value) internal pure returns (uint168) { - require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits"); - return uint168(value); - } - - /** - * @dev Returns the downcasted uint160 from uint256, reverting on - * overflow (when the input is greater than largest uint160). - * - * Counterpart to Solidity's `uint160` operator. - * - * Requirements: - * - * - input must fit into 160 bits - * - * _Available since v4.7._ - */ - function toUint160(uint256 value) internal pure returns (uint160) { - require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits"); - return uint160(value); - } - - /** - * @dev Returns the downcasted uint152 from uint256, reverting on - * overflow (when the input is greater than largest uint152). - * - * Counterpart to Solidity's `uint152` operator. - * - * Requirements: - * - * - input must fit into 152 bits - * - * _Available since v4.7._ - */ - function toUint152(uint256 value) internal pure returns (uint152) { - require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits"); - return uint152(value); - } - - /** - * @dev Returns the downcasted uint144 from uint256, reverting on - * overflow (when the input is greater than largest uint144). - * - * Counterpart to Solidity's `uint144` operator. - * - * Requirements: - * - * - input must fit into 144 bits - * - * _Available since v4.7._ - */ - function toUint144(uint256 value) internal pure returns (uint144) { - require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits"); - return uint144(value); - } - - /** - * @dev Returns the downcasted uint136 from uint256, reverting on - * overflow (when the input is greater than largest uint136). - * - * Counterpart to Solidity's `uint136` operator. - * - * Requirements: - * - * - input must fit into 136 bits - * - * _Available since v4.7._ - */ - function toUint136(uint256 value) internal pure returns (uint136) { - require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits"); - return uint136(value); - } - - /** - * @dev Returns the downcasted uint128 from uint256, reverting on - * overflow (when the input is greater than largest uint128). - * - * Counterpart to Solidity's `uint128` operator. - * - * Requirements: - * - * - input must fit into 128 bits - * - * _Available since v2.5._ - */ - function toUint128(uint256 value) internal pure returns (uint128) { - require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits"); - return uint128(value); - } - - /** - * @dev Returns the downcasted uint120 from uint256, reverting on - * overflow (when the input is greater than largest uint120). - * - * Counterpart to Solidity's `uint120` operator. - * - * Requirements: - * - * - input must fit into 120 bits - * - * _Available since v4.7._ - */ - function toUint120(uint256 value) internal pure returns (uint120) { - require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits"); - return uint120(value); - } - - /** - * @dev Returns the downcasted uint112 from uint256, reverting on - * overflow (when the input is greater than largest uint112). - * - * Counterpart to Solidity's `uint112` operator. - * - * Requirements: - * - * - input must fit into 112 bits - * - * _Available since v4.7._ - */ - function toUint112(uint256 value) internal pure returns (uint112) { - require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits"); - return uint112(value); - } - - /** - * @dev Returns the downcasted uint104 from uint256, reverting on - * overflow (when the input is greater than largest uint104). - * - * Counterpart to Solidity's `uint104` operator. - * - * Requirements: - * - * - input must fit into 104 bits - * - * _Available since v4.7._ - */ - function toUint104(uint256 value) internal pure returns (uint104) { - require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits"); - return uint104(value); - } - - /** - * @dev Returns the downcasted uint96 from uint256, reverting on - * overflow (when the input is greater than largest uint96). - * - * Counterpart to Solidity's `uint96` operator. - * - * Requirements: - * - * - input must fit into 96 bits - * - * _Available since v4.2._ - */ - function toUint96(uint256 value) internal pure returns (uint96) { - require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits"); - return uint96(value); - } - - /** - * @dev Returns the downcasted uint88 from uint256, reverting on - * overflow (when the input is greater than largest uint88). - * - * Counterpart to Solidity's `uint88` operator. - * - * Requirements: - * - * - input must fit into 88 bits - * - * _Available since v4.7._ - */ - function toUint88(uint256 value) internal pure returns (uint88) { - require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); - return uint88(value); - } - - /** - * @dev Returns the downcasted uint80 from uint256, reverting on - * overflow (when the input is greater than largest uint80). - * - * Counterpart to Solidity's `uint80` operator. - * - * Requirements: - * - * - input must fit into 80 bits - * - * _Available since v4.7._ - */ - function toUint80(uint256 value) internal pure returns (uint80) { - require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits"); - return uint80(value); - } - - /** - * @dev Returns the downcasted uint72 from uint256, reverting on - * overflow (when the input is greater than largest uint72). - * - * Counterpart to Solidity's `uint72` operator. - * - * Requirements: - * - * - input must fit into 72 bits - * - * _Available since v4.7._ - */ - function toUint72(uint256 value) internal pure returns (uint72) { - require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits"); - return uint72(value); - } - - /** - * @dev Returns the downcasted uint64 from uint256, reverting on - * overflow (when the input is greater than largest uint64). - * - * Counterpart to Solidity's `uint64` operator. - * - * Requirements: - * - * - input must fit into 64 bits - * - * _Available since v2.5._ - */ - function toUint64(uint256 value) internal pure returns (uint64) { - require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits"); - return uint64(value); - } - - /** - * @dev Returns the downcasted uint56 from uint256, reverting on - * overflow (when the input is greater than largest uint56). - * - * Counterpart to Solidity's `uint56` operator. - * - * Requirements: - * - * - input must fit into 56 bits - * - * _Available since v4.7._ - */ - function toUint56(uint256 value) internal pure returns (uint56) { - require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits"); - return uint56(value); - } - - /** - * @dev Returns the downcasted uint48 from uint256, reverting on - * overflow (when the input is greater than largest uint48). - * - * Counterpart to Solidity's `uint48` operator. - * - * Requirements: - * - * - input must fit into 48 bits - * - * _Available since v4.7._ - */ - function toUint48(uint256 value) internal pure returns (uint48) { - require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits"); - return uint48(value); - } - - /** - * @dev Returns the downcasted uint40 from uint256, reverting on - * overflow (when the input is greater than largest uint40). - * - * Counterpart to Solidity's `uint40` operator. - * - * Requirements: - * - * - input must fit into 40 bits - * - * _Available since v4.7._ - */ - function toUint40(uint256 value) internal pure returns (uint40) { - require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits"); - return uint40(value); - } - - /** - * @dev Returns the downcasted uint32 from uint256, reverting on - * overflow (when the input is greater than largest uint32). - * - * Counterpart to Solidity's `uint32` operator. - * - * Requirements: - * - * - input must fit into 32 bits - * - * _Available since v2.5._ - */ - function toUint32(uint256 value) internal pure returns (uint32) { - require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); - return uint32(value); - } - - /** - * @dev Returns the downcasted uint24 from uint256, reverting on - * overflow (when the input is greater than largest uint24). - * - * Counterpart to Solidity's `uint24` operator. - * - * Requirements: - * - * - input must fit into 24 bits - * - * _Available since v4.7._ - */ - function toUint24(uint256 value) internal pure returns (uint24) { - require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits"); - return uint24(value); - } - - /** - * @dev Returns the downcasted uint16 from uint256, reverting on - * overflow (when the input is greater than largest uint16). - * - * Counterpart to Solidity's `uint16` operator. - * - * Requirements: - * - * - input must fit into 16 bits - * - * _Available since v2.5._ - */ - function toUint16(uint256 value) internal pure returns (uint16) { - require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits"); - return uint16(value); - } - - /** - * @dev Returns the downcasted uint8 from uint256, reverting on - * overflow (when the input is greater than largest uint8). - * - * Counterpart to Solidity's `uint8` operator. - * - * Requirements: - * - * - input must fit into 8 bits - * - * _Available since v2.5._ - */ - function toUint8(uint256 value) internal pure returns (uint8) { - require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits"); - return uint8(value); - } - - /** - * @dev Converts a signed int256 into an unsigned uint256. - * - * Requirements: - * - * - input must be greater than or equal to 0. - * - * _Available since v3.0._ - */ - function toUint256(int256 value) internal pure returns (uint256) { - require(value >= 0, "SafeCast: value must be positive"); - return uint256(value); - } - - /** - * @dev Returns the downcasted int248 from int256, reverting on - * overflow (when the input is less than smallest int248 or - * greater than largest int248). - * - * Counterpart to Solidity's `int248` operator. - * - * Requirements: - * - * - input must fit into 248 bits - * - * _Available since v4.7._ - */ - function toInt248(int256 value) internal pure returns (int248) { - require(value >= type(int248).min && value <= type(int248).max, "SafeCast: value doesn't fit in 248 bits"); - return int248(value); - } - - /** - * @dev Returns the downcasted int240 from int256, reverting on - * overflow (when the input is less than smallest int240 or - * greater than largest int240). - * - * Counterpart to Solidity's `int240` operator. - * - * Requirements: - * - * - input must fit into 240 bits - * - * _Available since v4.7._ - */ - function toInt240(int256 value) internal pure returns (int240) { - require(value >= type(int240).min && value <= type(int240).max, "SafeCast: value doesn't fit in 240 bits"); - return int240(value); - } - - /** - * @dev Returns the downcasted int232 from int256, reverting on - * overflow (when the input is less than smallest int232 or - * greater than largest int232). - * - * Counterpart to Solidity's `int232` operator. - * - * Requirements: - * - * - input must fit into 232 bits - * - * _Available since v4.7._ - */ - function toInt232(int256 value) internal pure returns (int232) { - require(value >= type(int232).min && value <= type(int232).max, "SafeCast: value doesn't fit in 232 bits"); - return int232(value); - } - - /** - * @dev Returns the downcasted int224 from int256, reverting on - * overflow (when the input is less than smallest int224 or - * greater than largest int224). - * - * Counterpart to Solidity's `int224` operator. - * - * Requirements: - * - * - input must fit into 224 bits - * - * _Available since v4.7._ - */ - function toInt224(int256 value) internal pure returns (int224) { - require(value >= type(int224).min && value <= type(int224).max, "SafeCast: value doesn't fit in 224 bits"); - return int224(value); - } - - /** - * @dev Returns the downcasted int216 from int256, reverting on - * overflow (when the input is less than smallest int216 or - * greater than largest int216). - * - * Counterpart to Solidity's `int216` operator. - * - * Requirements: - * - * - input must fit into 216 bits - * - * _Available since v4.7._ - */ - function toInt216(int256 value) internal pure returns (int216) { - require(value >= type(int216).min && value <= type(int216).max, "SafeCast: value doesn't fit in 216 bits"); - return int216(value); - } - - /** - * @dev Returns the downcasted int208 from int256, reverting on - * overflow (when the input is less than smallest int208 or - * greater than largest int208). - * - * Counterpart to Solidity's `int208` operator. - * - * Requirements: - * - * - input must fit into 208 bits - * - * _Available since v4.7._ - */ - function toInt208(int256 value) internal pure returns (int208) { - require(value >= type(int208).min && value <= type(int208).max, "SafeCast: value doesn't fit in 208 bits"); - return int208(value); - } - - /** - * @dev Returns the downcasted int200 from int256, reverting on - * overflow (when the input is less than smallest int200 or - * greater than largest int200). - * - * Counterpart to Solidity's `int200` operator. - * - * Requirements: - * - * - input must fit into 200 bits - * - * _Available since v4.7._ - */ - function toInt200(int256 value) internal pure returns (int200) { - require(value >= type(int200).min && value <= type(int200).max, "SafeCast: value doesn't fit in 200 bits"); - return int200(value); - } - - /** - * @dev Returns the downcasted int192 from int256, reverting on - * overflow (when the input is less than smallest int192 or - * greater than largest int192). - * - * Counterpart to Solidity's `int192` operator. - * - * Requirements: - * - * - input must fit into 192 bits - * - * _Available since v4.7._ - */ - function toInt192(int256 value) internal pure returns (int192) { - require(value >= type(int192).min && value <= type(int192).max, "SafeCast: value doesn't fit in 192 bits"); - return int192(value); - } - - /** - * @dev Returns the downcasted int184 from int256, reverting on - * overflow (when the input is less than smallest int184 or - * greater than largest int184). - * - * Counterpart to Solidity's `int184` operator. - * - * Requirements: - * - * - input must fit into 184 bits - * - * _Available since v4.7._ - */ - function toInt184(int256 value) internal pure returns (int184) { - require(value >= type(int184).min && value <= type(int184).max, "SafeCast: value doesn't fit in 184 bits"); - return int184(value); - } - - /** - * @dev Returns the downcasted int176 from int256, reverting on - * overflow (when the input is less than smallest int176 or - * greater than largest int176). - * - * Counterpart to Solidity's `int176` operator. - * - * Requirements: - * - * - input must fit into 176 bits - * - * _Available since v4.7._ - */ - function toInt176(int256 value) internal pure returns (int176) { - require(value >= type(int176).min && value <= type(int176).max, "SafeCast: value doesn't fit in 176 bits"); - return int176(value); - } - - /** - * @dev Returns the downcasted int168 from int256, reverting on - * overflow (when the input is less than smallest int168 or - * greater than largest int168). - * - * Counterpart to Solidity's `int168` operator. - * - * Requirements: - * - * - input must fit into 168 bits - * - * _Available since v4.7._ - */ - function toInt168(int256 value) internal pure returns (int168) { - require(value >= type(int168).min && value <= type(int168).max, "SafeCast: value doesn't fit in 168 bits"); - return int168(value); - } - - /** - * @dev Returns the downcasted int160 from int256, reverting on - * overflow (when the input is less than smallest int160 or - * greater than largest int160). - * - * Counterpart to Solidity's `int160` operator. - * - * Requirements: - * - * - input must fit into 160 bits - * - * _Available since v4.7._ - */ - function toInt160(int256 value) internal pure returns (int160) { - require(value >= type(int160).min && value <= type(int160).max, "SafeCast: value doesn't fit in 160 bits"); - return int160(value); - } - - /** - * @dev Returns the downcasted int152 from int256, reverting on - * overflow (when the input is less than smallest int152 or - * greater than largest int152). - * - * Counterpart to Solidity's `int152` operator. - * - * Requirements: - * - * - input must fit into 152 bits - * - * _Available since v4.7._ - */ - function toInt152(int256 value) internal pure returns (int152) { - require(value >= type(int152).min && value <= type(int152).max, "SafeCast: value doesn't fit in 152 bits"); - return int152(value); - } - - /** - * @dev Returns the downcasted int144 from int256, reverting on - * overflow (when the input is less than smallest int144 or - * greater than largest int144). - * - * Counterpart to Solidity's `int144` operator. - * - * Requirements: - * - * - input must fit into 144 bits - * - * _Available since v4.7._ - */ - function toInt144(int256 value) internal pure returns (int144) { - require(value >= type(int144).min && value <= type(int144).max, "SafeCast: value doesn't fit in 144 bits"); - return int144(value); - } - - /** - * @dev Returns the downcasted int136 from int256, reverting on - * overflow (when the input is less than smallest int136 or - * greater than largest int136). - * - * Counterpart to Solidity's `int136` operator. - * - * Requirements: - * - * - input must fit into 136 bits - * - * _Available since v4.7._ - */ - function toInt136(int256 value) internal pure returns (int136) { - require(value >= type(int136).min && value <= type(int136).max, "SafeCast: value doesn't fit in 136 bits"); - return int136(value); - } - - /** - * @dev Returns the downcasted int128 from int256, reverting on - * overflow (when the input is less than smallest int128 or - * greater than largest int128). - * - * Counterpart to Solidity's `int128` operator. - * - * Requirements: - * - * - input must fit into 128 bits - * - * _Available since v3.1._ - */ - function toInt128(int256 value) internal pure returns (int128) { - require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits"); - return int128(value); - } - - /** - * @dev Returns the downcasted int120 from int256, reverting on - * overflow (when the input is less than smallest int120 or - * greater than largest int120). - * - * Counterpart to Solidity's `int120` operator. - * - * Requirements: - * - * - input must fit into 120 bits - * - * _Available since v4.7._ - */ - function toInt120(int256 value) internal pure returns (int120) { - require(value >= type(int120).min && value <= type(int120).max, "SafeCast: value doesn't fit in 120 bits"); - return int120(value); - } - - /** - * @dev Returns the downcasted int112 from int256, reverting on - * overflow (when the input is less than smallest int112 or - * greater than largest int112). - * - * Counterpart to Solidity's `int112` operator. - * - * Requirements: - * - * - input must fit into 112 bits - * - * _Available since v4.7._ - */ - function toInt112(int256 value) internal pure returns (int112) { - require(value >= type(int112).min && value <= type(int112).max, "SafeCast: value doesn't fit in 112 bits"); - return int112(value); - } - - /** - * @dev Returns the downcasted int104 from int256, reverting on - * overflow (when the input is less than smallest int104 or - * greater than largest int104). - * - * Counterpart to Solidity's `int104` operator. - * - * Requirements: - * - * - input must fit into 104 bits - * - * _Available since v4.7._ - */ - function toInt104(int256 value) internal pure returns (int104) { - require(value >= type(int104).min && value <= type(int104).max, "SafeCast: value doesn't fit in 104 bits"); - return int104(value); - } - - /** - * @dev Returns the downcasted int96 from int256, reverting on - * overflow (when the input is less than smallest int96 or - * greater than largest int96). - * - * Counterpart to Solidity's `int96` operator. - * - * Requirements: - * - * - input must fit into 96 bits - * - * _Available since v4.7._ - */ - function toInt96(int256 value) internal pure returns (int96) { - require(value >= type(int96).min && value <= type(int96).max, "SafeCast: value doesn't fit in 96 bits"); - return int96(value); - } - - /** - * @dev Returns the downcasted int88 from int256, reverting on - * overflow (when the input is less than smallest int88 or - * greater than largest int88). - * - * Counterpart to Solidity's `int88` operator. - * - * Requirements: - * - * - input must fit into 88 bits - * - * _Available since v4.7._ - */ - function toInt88(int256 value) internal pure returns (int88) { - require(value >= type(int88).min && value <= type(int88).max, "SafeCast: value doesn't fit in 88 bits"); - return int88(value); - } - - /** - * @dev Returns the downcasted int80 from int256, reverting on - * overflow (when the input is less than smallest int80 or - * greater than largest int80). - * - * Counterpart to Solidity's `int80` operator. - * - * Requirements: - * - * - input must fit into 80 bits - * - * _Available since v4.7._ - */ - function toInt80(int256 value) internal pure returns (int80) { - require(value >= type(int80).min && value <= type(int80).max, "SafeCast: value doesn't fit in 80 bits"); - return int80(value); - } - - /** - * @dev Returns the downcasted int72 from int256, reverting on - * overflow (when the input is less than smallest int72 or - * greater than largest int72). - * - * Counterpart to Solidity's `int72` operator. - * - * Requirements: - * - * - input must fit into 72 bits - * - * _Available since v4.7._ - */ - function toInt72(int256 value) internal pure returns (int72) { - require(value >= type(int72).min && value <= type(int72).max, "SafeCast: value doesn't fit in 72 bits"); - return int72(value); - } - - /** - * @dev Returns the downcasted int64 from int256, reverting on - * overflow (when the input is less than smallest int64 or - * greater than largest int64). - * - * Counterpart to Solidity's `int64` operator. - * - * Requirements: - * - * - input must fit into 64 bits - * - * _Available since v3.1._ - */ - function toInt64(int256 value) internal pure returns (int64) { - require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits"); - return int64(value); - } - - /** - * @dev Returns the downcasted int56 from int256, reverting on - * overflow (when the input is less than smallest int56 or - * greater than largest int56). - * - * Counterpart to Solidity's `int56` operator. - * - * Requirements: - * - * - input must fit into 56 bits - * - * _Available since v4.7._ - */ - function toInt56(int256 value) internal pure returns (int56) { - require(value >= type(int56).min && value <= type(int56).max, "SafeCast: value doesn't fit in 56 bits"); - return int56(value); - } - - /** - * @dev Returns the downcasted int48 from int256, reverting on - * overflow (when the input is less than smallest int48 or - * greater than largest int48). - * - * Counterpart to Solidity's `int48` operator. - * - * Requirements: - * - * - input must fit into 48 bits - * - * _Available since v4.7._ - */ - function toInt48(int256 value) internal pure returns (int48) { - require(value >= type(int48).min && value <= type(int48).max, "SafeCast: value doesn't fit in 48 bits"); - return int48(value); - } - - /** - * @dev Returns the downcasted int40 from int256, reverting on - * overflow (when the input is less than smallest int40 or - * greater than largest int40). - * - * Counterpart to Solidity's `int40` operator. - * - * Requirements: - * - * - input must fit into 40 bits - * - * _Available since v4.7._ - */ - function toInt40(int256 value) internal pure returns (int40) { - require(value >= type(int40).min && value <= type(int40).max, "SafeCast: value doesn't fit in 40 bits"); - return int40(value); - } - - /** - * @dev Returns the downcasted int32 from int256, reverting on - * overflow (when the input is less than smallest int32 or - * greater than largest int32). - * - * Counterpart to Solidity's `int32` operator. - * - * Requirements: - * - * - input must fit into 32 bits - * - * _Available since v3.1._ - */ - function toInt32(int256 value) internal pure returns (int32) { - require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits"); - return int32(value); - } - - /** - * @dev Returns the downcasted int24 from int256, reverting on - * overflow (when the input is less than smallest int24 or - * greater than largest int24). - * - * Counterpart to Solidity's `int24` operator. - * - * Requirements: - * - * - input must fit into 24 bits - * - * _Available since v4.7._ - */ - function toInt24(int256 value) internal pure returns (int24) { - require(value >= type(int24).min && value <= type(int24).max, "SafeCast: value doesn't fit in 24 bits"); - return int24(value); - } - - /** - * @dev Returns the downcasted int16 from int256, reverting on - * overflow (when the input is less than smallest int16 or - * greater than largest int16). - * - * Counterpart to Solidity's `int16` operator. - * - * Requirements: - * - * - input must fit into 16 bits - * - * _Available since v3.1._ - */ - function toInt16(int256 value) internal pure returns (int16) { - require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits"); - return int16(value); - } - - /** - * @dev Returns the downcasted int8 from int256, reverting on - * overflow (when the input is less than smallest int8 or - * greater than largest int8). - * - * Counterpart to Solidity's `int8` operator. - * - * Requirements: - * - * - input must fit into 8 bits - * - * _Available since v3.1._ - */ - function toInt8(int256 value) internal pure returns (int8) { - require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits"); - return int8(value); - } - - /** - * @dev Converts an unsigned uint256 into a signed int256. - * - * Requirements: - * - * - input must be less than or equal to maxInt256. - * - * _Available since v3.0._ - */ - function toInt256(uint256 value) internal pure returns (int256) { - // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive - require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256"); - return int256(value); - } -} - - -// File contracts/routers/UnoswapV3Router.sol - - -pragma solidity 0.8.17; - - - - - - - - - -contract UnoswapV3Router is EthReceiver, IUniswapV3SwapCallback { - using Address for address payable; - using SafeERC20 for IERC20; - - error EmptyPools(); - error BadPool(); - - uint256 private constant _ONE_FOR_ZERO_MASK = 1 << 255; - uint256 private constant _WETH_UNWRAP_MASK = 1 << 253; - bytes32 private constant _POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; - bytes32 private constant _FF_FACTORY = 0xff1F98431c8aD98523631AE4a59f267346ea31F9840000000000000000000000; - // concatenation of token0(), token1() fee(), transfer() and transferFrom() selectors - bytes32 private constant _SELECTORS = 0x0dfe1681d21220a7ddca3f43a9059cbb23b872dd000000000000000000000000; - uint256 private constant _ADDRESS_MASK = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff; - /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) - uint160 private constant _MIN_SQRT_RATIO = 4295128739 + 1; - /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) - uint160 private constant _MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342 - 1; - IWETH private immutable _WETH; // solhint-disable-line var-name-mixedcase - - constructor(IWETH weth) { - _WETH = weth; - } - - /// @notice Same as `uniswapV3SwapTo` but calls permit first, - /// allowing to approve token spending and make a swap in one transaction. - /// @param recipient Address that will receive swap funds - /// @param srcToken Source token - /// @param amount Amount of source tokens to swap - /// @param minReturn Minimal allowed returnAmount to make transaction commit - /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen - /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls. - /// See tests for examples - function uniswapV3SwapToWithPermit( - address payable recipient, - IERC20 srcToken, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools, - bytes calldata permit - ) external returns(uint256 returnAmount) { - srcToken.safePermit(permit); - return _uniswapV3Swap(recipient, amount, minReturn, pools); - } - - /// @notice Same as `uniswapV3SwapTo` but uses `msg.sender` as recipient - /// @param amount Amount of source tokens to swap - /// @param minReturn Minimal allowed returnAmount to make transaction commit - /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen - function uniswapV3Swap( - uint256 amount, - uint256 minReturn, - uint256[] calldata pools - ) external payable returns(uint256 returnAmount) { - return _uniswapV3Swap(payable(msg.sender), amount, minReturn, pools); - } - - /// @notice Performs swap using Uniswap V3 exchange. Wraps and unwraps ETH if required. - /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited - /// @param recipient Address that will receive swap funds - /// @param amount Amount of source tokens to swap - /// @param minReturn Minimal allowed returnAmount to make transaction commit - /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen - function uniswapV3SwapTo( - address payable recipient, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools - ) external payable returns(uint256 returnAmount) { - return _uniswapV3Swap(recipient, amount, minReturn, pools); - } - - function _uniswapV3Swap( - address payable recipient, - uint256 amount, - uint256 minReturn, - uint256[] calldata pools - ) private returns(uint256 returnAmount) { - unchecked { - uint256 len = pools.length; - if (len == 0) revert EmptyPools(); - uint256 lastIndex = len - 1; - returnAmount = amount; - bool wrapWeth = msg.value > 0; - bool unwrapWeth = pools[lastIndex] & _WETH_UNWRAP_MASK > 0; - if (wrapWeth) { - if (msg.value != amount) revert RouterErrors.InvalidMsgValue(); - _WETH.deposit{value: amount}(); - } - if (len > 1) { - returnAmount = _makeSwap(address(this), wrapWeth ? address(this) : msg.sender, pools[0], returnAmount); - - for (uint256 i = 1; i < lastIndex; i++) { - returnAmount = _makeSwap(address(this), address(this), pools[i], returnAmount); - } - returnAmount = _makeSwap(unwrapWeth ? address(this) : recipient, address(this), pools[lastIndex], returnAmount); - } else { - returnAmount = _makeSwap(unwrapWeth ? address(this) : recipient, wrapWeth ? address(this) : msg.sender, pools[0], returnAmount); - } - - if (returnAmount < minReturn) revert RouterErrors.ReturnAmountIsNotEnough(); - - if (unwrapWeth) { - _WETH.withdraw(returnAmount); - recipient.sendValue(returnAmount); - } - } - } - - /// @inheritdoc IUniswapV3SwapCallback - function uniswapV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata /* data */ - ) external override { - assembly { // solhint-disable-line no-inline-assembly - function reRevert() { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } - - function validateERC20Transfer(status) { - if iszero(status) { - reRevert() - } - let success := or( - iszero(returndatasize()), // empty return data - and(gt(returndatasize(), 31), eq(mload(0), 1)) // true in return data - ) - if iszero(success) { - mstore(0, 0xf27f64e400000000000000000000000000000000000000000000000000000000) // ERC20TransferFailed() - revert(0, 4) - } - } - - let emptyPtr := mload(0x40) - let resultPtr := add(emptyPtr, 0x15) // 0x15 = _FF_FACTORY size - - mstore(emptyPtr, _SELECTORS) - if iszero(staticcall(gas(), caller(), emptyPtr, 0x4, resultPtr, 0x20)) { - reRevert() - } - if iszero(staticcall(gas(), caller(), add(emptyPtr, 0x4), 0x4, add(resultPtr, 0x20), 0x20)) { - reRevert() - } - if iszero(staticcall(gas(), caller(), add(emptyPtr, 0x8), 0x4, add(resultPtr, 0x40), 0x20)) { - reRevert() - } - - let token - let amount - switch sgt(amount0Delta, 0) - case 1 { - token := mload(resultPtr) - amount := amount0Delta - } - default { - token := mload(add(resultPtr, 0x20)) - amount := amount1Delta - } - - mstore(emptyPtr, _FF_FACTORY) - mstore(resultPtr, keccak256(resultPtr, 0x60)) // Compute the inner hash in-place - mstore(add(resultPtr, 0x20), _POOL_INIT_CODE_HASH) - let pool := and(keccak256(emptyPtr, 0x55), _ADDRESS_MASK) - if xor(pool, caller()) { - mstore(0, 0xb2c0272200000000000000000000000000000000000000000000000000000000) // BadPool() - revert(0, 4) - } - - let payer := calldataload(0x84) - mstore(emptyPtr, _SELECTORS) - switch eq(payer, address()) - case 1 { - // token.safeTransfer(msg.sender,amount) - mstore(add(emptyPtr, 0x10), caller()) - mstore(add(emptyPtr, 0x30), amount) - validateERC20Transfer( - call(gas(), token, 0, add(emptyPtr, 0x0c), 0x44, 0, 0x20) - ) - } - default { - // token.safeTransferFrom(payer, msg.sender, amount); - mstore(add(emptyPtr, 0x14), payer) - mstore(add(emptyPtr, 0x34), caller()) - mstore(add(emptyPtr, 0x54), amount) - validateERC20Transfer( - call(gas(), token, 0, add(emptyPtr, 0x10), 0x64, 0, 0x20) - ) - } - } - } - - function _makeSwap(address recipient, address payer, uint256 pool, uint256 amount) private returns (uint256) { - bool zeroForOne = pool & _ONE_FOR_ZERO_MASK == 0; - if (zeroForOne) { - (, int256 amount1) = IUniswapV3Pool(address(uint160(pool))).swap( - recipient, - zeroForOne, - SafeCast.toInt256(amount), - _MIN_SQRT_RATIO, - abi.encode(payer) - ); - return SafeCast.toUint256(-amount1); - } else { - (int256 amount0,) = IUniswapV3Pool(address(uint160(pool))).swap( - recipient, - zeroForOne, - SafeCast.toInt256(amount), - _MAX_SQRT_RATIO, - abi.encode(payer) - ); - return SafeCast.toUint256(-amount0); - } - } -} - - -// File @1inch/solidity-utils/contracts/OnlyWethReceiver.sol@v2.1.1 - - -pragma solidity ^0.8.0; - -abstract contract OnlyWethReceiver is EthReceiver { - address private immutable _WETH; // solhint-disable-line var-name-mixedcase - - constructor(address weth) { - _WETH = address(weth); - } - - function _receive() internal virtual override { - if (msg.sender != _WETH) revert EthDepositRejected(); - } -} - - -// File @openzeppelin/contracts/interfaces/IERC1271.sol@v4.7.3 - -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Interface of the ERC1271 standard signature validation method for - * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. - * - * _Available since v4.1._ - */ -interface IERC1271 { - /** - * @dev Should return whether the signature provided is valid for the provided data - * @param hash Hash of the data to be signed - * @param signature Signature byte array associated with _data - */ - function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); -} - - -// File @1inch/solidity-utils/contracts/libraries/ECDSA.sol@v2.1.1 - - -pragma solidity ^0.8.0; - -library ECDSA { - // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature - // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines - // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most - // signatures from current libraries generate a unique signature with an s-value in the lower half order. - // - // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value - // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or - // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept - // these malleable signatures as well. - uint256 private constant _S_BOUNDARY = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 + 1; - uint256 private constant _COMPACT_S_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; - uint256 private constant _COMPACT_V_SHIFT = 255; - - function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns(address signer) { - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - if lt(s, _S_BOUNDARY) { - let ptr := mload(0x40) - - mstore(ptr, hash) - mstore(add(ptr, 0x20), v) - mstore(add(ptr, 0x40), r) - mstore(add(ptr, 0x60), s) - mstore(0, 0) - pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20)) - signer := mload(0) - } - } - } - - function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns(address signer) { - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let s := and(vs, _COMPACT_S_MASK) - if lt(s, _S_BOUNDARY) { - let ptr := mload(0x40) - - mstore(ptr, hash) - mstore(add(ptr, 0x20), add(27, shr(_COMPACT_V_SHIFT, vs))) - mstore(add(ptr, 0x40), r) - mstore(add(ptr, 0x60), s) - mstore(0, 0) - pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20)) - signer := mload(0) - } - } - } - - /// WARNING!!! - /// There is a known signature malleability issue with two representations of signatures! - /// Even though this function is able to verify both standard 65-byte and compact 64-byte EIP-2098 signatures - /// one should never use raw signatures for any kind of invalidation logic in their code. - /// As the standard and compact representations are interchangeable any invalidation logic that relies on - /// signature uniqueness will get rekt. - /// More info: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-4h98-2769-gh6h - function recover(bytes32 hash, bytes calldata signature) internal view returns(address signer) { - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - // memory[ptr:ptr+0x80] = (hash, v, r, s) - switch signature.length - case 65 { - // memory[ptr+0x20:ptr+0x80] = (v, r, s) - mstore(add(ptr, 0x20), byte(0, calldataload(add(signature.offset, 0x40)))) - calldatacopy(add(ptr, 0x40), signature.offset, 0x40) - } - case 64 { - // memory[ptr+0x20:ptr+0x80] = (v, r, s) - let vs := calldataload(add(signature.offset, 0x20)) - mstore(add(ptr, 0x20), add(27, shr(_COMPACT_V_SHIFT, vs))) - calldatacopy(add(ptr, 0x40), signature.offset, 0x20) - mstore(add(ptr, 0x60), and(vs, _COMPACT_S_MASK)) - } - default { - ptr := 0 - } - - if ptr { - if lt(mload(add(ptr, 0x60)), _S_BOUNDARY) { - // memory[ptr:ptr+0x20] = (hash) - mstore(ptr, hash) - - mstore(0, 0) - pop(staticcall(gas(), 0x1, ptr, 0x80, 0, 0x20)) - signer := mload(0) - } - } - } - } - - function recoverOrIsValidSignature(address signer, bytes32 hash, bytes calldata signature) internal view returns(bool success) { - if (signer == address(0)) return false; - if ((signature.length == 64 || signature.length == 65) && recover(hash, signature) == signer) { - return true; - } - return isValidSignature(signer, hash, signature); - } - - function recoverOrIsValidSignature(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns(bool success) { - if (signer == address(0)) return false; - if (recover(hash, v, r, s) == signer) { - return true; - } - return isValidSignature(signer, hash, v, r, s); - } - - function recoverOrIsValidSignature(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) { - if (signer == address(0)) return false; - if (recover(hash, r, vs) == signer) { - return true; - } - return isValidSignature(signer, hash, r, vs); - } - - function recoverOrIsValidSignature65(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) { - if (signer == address(0)) return false; - if (recover(hash, r, vs) == signer) { - return true; - } - return isValidSignature65(signer, hash, r, vs); - } - - function isValidSignature(address signer, bytes32 hash, bytes calldata signature) internal view returns(bool success) { - // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature)); - // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector; - bytes4 selector = IERC1271.isValidSignature.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - mstore(ptr, selector) - mstore(add(ptr, 0x04), hash) - mstore(add(ptr, 0x24), 0x40) - mstore(add(ptr, 0x44), signature.length) - calldatacopy(add(ptr, 0x64), signature.offset, signature.length) - if staticcall(gas(), signer, ptr, add(0x64, signature.length), 0, 0x20) { - success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20)) - } - } - } - - function isValidSignature(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns(bool success) { - bytes4 selector = IERC1271.isValidSignature.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - mstore(ptr, selector) - mstore(add(ptr, 0x04), hash) - mstore(add(ptr, 0x24), 0x40) - mstore(add(ptr, 0x44), 65) - mstore(add(ptr, 0x64), r) - mstore(add(ptr, 0x84), s) - mstore8(add(ptr, 0xa4), v) - if staticcall(gas(), signer, ptr, 0xa5, 0, 0x20) { - success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20)) - } - } - } - - function isValidSignature(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) { - // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, abi.encodePacked(r, vs))); - // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector; - bytes4 selector = IERC1271.isValidSignature.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - mstore(ptr, selector) - mstore(add(ptr, 0x04), hash) - mstore(add(ptr, 0x24), 0x40) - mstore(add(ptr, 0x44), 64) - mstore(add(ptr, 0x64), r) - mstore(add(ptr, 0x84), vs) - if staticcall(gas(), signer, ptr, 0xa4, 0, 0x20) { - success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20)) - } - } - } - - function isValidSignature65(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns(bool success) { - // (bool success, bytes memory data) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, abi.encodePacked(r, vs & ~uint256(1 << 255), uint8(vs >> 255)))); - // return success && data.length >= 4 && abi.decode(data, (bytes4)) == IERC1271.isValidSignature.selector; - bytes4 selector = IERC1271.isValidSignature.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - mstore(ptr, selector) - mstore(add(ptr, 0x04), hash) - mstore(add(ptr, 0x24), 0x40) - mstore(add(ptr, 0x44), 65) - mstore(add(ptr, 0x64), r) - mstore(add(ptr, 0x84), and(vs, _COMPACT_S_MASK)) - mstore8(add(ptr, 0xa4), add(27, shr(_COMPACT_V_SHIFT, vs))) - if staticcall(gas(), signer, ptr, 0xa5, 0, 0x20) { - success := and(eq(selector, mload(0)), eq(returndatasize(), 0x20)) - } - } - } - - function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 res) { - // 32 is the length in bytes of hash, enforced by the type signature above - // return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - mstore(0, 0x19457468657265756d205369676e6564204d6573736167653a0a333200000000) // "\x19Ethereum Signed Message:\n32" - mstore(28, hash) - res := keccak256(0, 60) - } - } - - function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 res) { - // return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - mstore(ptr, 0x1901000000000000000000000000000000000000000000000000000000000000) // "\x19\x01" - mstore(add(ptr, 0x02), domainSeparator) - mstore(add(ptr, 0x22), structHash) - res := keccak256(ptr, 66) - } - } -} - - -// File @1inch/limit-order-protocol/contracts/OrderRFQLib.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - -library OrderRFQLib { - struct OrderRFQ { - uint256 info; // lowest 64 bits is the order id, next 64 bits is the expiration timestamp - address makerAsset; - address takerAsset; - address maker; - address allowedSender; // equals to Zero address on public orders - uint256 makingAmount; - uint256 takingAmount; - } - - bytes32 constant internal _LIMIT_ORDER_RFQ_TYPEHASH = keccak256( - "OrderRFQ(" - "uint256 info," - "address makerAsset," - "address takerAsset," - "address maker," - "address allowedSender," - "uint256 makingAmount," - "uint256 takingAmount" - ")" - ); - - function hash(OrderRFQ memory order, bytes32 domainSeparator) internal pure returns(bytes32 result) { - bytes32 typehash = _LIMIT_ORDER_RFQ_TYPEHASH; - bytes32 orderHash; - // this assembly is memory unsafe :( - assembly { // solhint-disable-line no-inline-assembly - let ptr := sub(order, 0x20) - - // keccak256(abi.encode(_LIMIT_ORDER_RFQ_TYPEHASH, order)); - let tmp := mload(ptr) - mstore(ptr, typehash) - orderHash := keccak256(ptr, 0x100) - mstore(ptr, tmp) - } - return ECDSA.toTypedDataHash(domainSeparator, orderHash); - } -} - - -// File @openzeppelin/contracts/utils/Strings.sol@v4.7.3 - -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol) - -pragma solidity ^0.8.0; - -/** - * @dev String operations. - */ -library Strings { - bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; - uint8 private constant _ADDRESS_LENGTH = 20; - - /** - * @dev Converts a `uint256` to its ASCII `string` decimal representation. - */ - function toString(uint256 value) internal pure returns (string memory) { - // Inspired by OraclizeAPI's implementation - MIT licence - // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol - - if (value == 0) { - return "0"; - } - uint256 temp = value; - uint256 digits; - while (temp != 0) { - digits++; - temp /= 10; - } - bytes memory buffer = new bytes(digits); - while (value != 0) { - digits -= 1; - buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); - value /= 10; - } - return string(buffer); - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. - */ - function toHexString(uint256 value) internal pure returns (string memory) { - if (value == 0) { - return "0x00"; - } - uint256 temp = value; - uint256 length = 0; - while (temp != 0) { - length++; - temp >>= 8; - } - return toHexString(value, length); - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. - */ - function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { - bytes memory buffer = new bytes(2 * length + 2); - buffer[0] = "0"; - buffer[1] = "x"; - for (uint256 i = 2 * length + 1; i > 1; --i) { - buffer[i] = _HEX_SYMBOLS[value & 0xf]; - value >>= 4; - } - require(value == 0, "Strings: hex length insufficient"); - return string(buffer); - } - - /** - * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. - */ - function toHexString(address addr) internal pure returns (string memory) { - return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); - } -} - - -// File @openzeppelin/contracts/utils/cryptography/draft-EIP712.sol@v4.7.3 - -// OpenZeppelin Contracts v4.4.1 (utils/cryptography/draft-EIP712.sol) - -pragma solidity ^0.8.0; - -/** - * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. - * - * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, - * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding - * they need in their contracts using a combination of `abi.encode` and `keccak256`. - * - * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding - * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA - * ({_hashTypedDataV4}). - * - * The implementation of the domain separator was designed to be as efficient as possible while still properly updating - * the chain id to protect against replay attacks on an eventual fork of the chain. - * - * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method - * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. - * - * _Available since v3.4._ - */ -abstract contract EIP712 { - /* solhint-disable var-name-mixedcase */ - // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to - // invalidate the cached domain separator if the chain id changes. - bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; - uint256 private immutable _CACHED_CHAIN_ID; - address private immutable _CACHED_THIS; - - bytes32 private immutable _HASHED_NAME; - bytes32 private immutable _HASHED_VERSION; - bytes32 private immutable _TYPE_HASH; - - /* solhint-enable var-name-mixedcase */ - - /** - * @dev Initializes the domain separator and parameter caches. - * - * The meaning of `name` and `version` is specified in - * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: - * - * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. - * - `version`: the current major version of the signing domain. - * - * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart - * contract upgrade]. - */ - constructor(string memory name, string memory version) { - bytes32 hashedName = keccak256(bytes(name)); - bytes32 hashedVersion = keccak256(bytes(version)); - bytes32 typeHash = keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ); - _HASHED_NAME = hashedName; - _HASHED_VERSION = hashedVersion; - _CACHED_CHAIN_ID = block.chainid; - _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion); - _CACHED_THIS = address(this); - _TYPE_HASH = typeHash; - } - - /** - * @dev Returns the domain separator for the current chain. - */ - function _domainSeparatorV4() internal view returns (bytes32) { - if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) { - return _CACHED_DOMAIN_SEPARATOR; - } else { - return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION); - } - } - - function _buildDomainSeparator( - bytes32 typeHash, - bytes32 nameHash, - bytes32 versionHash - ) private view returns (bytes32) { - return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this))); - } - - /** - * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this - * function returns the hash of the fully encoded EIP712 message for this domain. - * - * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: - * - * ```solidity - * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( - * keccak256("Mail(address to,string contents)"), - * mailTo, - * keccak256(bytes(mailContents)) - * ))); - * address signer = ECDSA.recover(digest, signature); - * ``` - */ - function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { - return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); - } -} - - -// File @1inch/limit-order-protocol/contracts/libraries/Errors.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - -library Errors { - error InvalidMsgValue(); - error ETHTransferFailed(); -} - - -// File @1inch/limit-order-protocol/contracts/helpers/AmountCalculator.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - -/// @title A helper contract for calculations related to order amounts -library AmountCalculator { - /// @notice Calculates maker amount - /// @return Result Floored maker amount - function getMakingAmount(uint256 orderMakerAmount, uint256 orderTakerAmount, uint256 swapTakerAmount) internal pure returns(uint256) { - return swapTakerAmount * orderMakerAmount / orderTakerAmount; - } - - /// @notice Calculates taker amount - /// @return Result Ceiled taker amount - function getTakingAmount(uint256 orderMakerAmount, uint256 orderTakerAmount, uint256 swapMakerAmount) internal pure returns(uint256) { - return (swapMakerAmount * orderTakerAmount + orderMakerAmount - 1) / orderMakerAmount; - } -} - - -// File @1inch/limit-order-protocol/contracts/OrderRFQMixin.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - - - - - - - -/// @title RFQ Limit Order mixin -abstract contract OrderRFQMixin is EIP712, OnlyWethReceiver { - using SafeERC20 for IERC20; - using OrderRFQLib for OrderRFQLib.OrderRFQ; - - error RFQZeroTargetIsForbidden(); - error RFQPrivateOrder(); - error RFQBadSignature(); - error OrderExpired(); - error MakingAmountExceeded(); - error TakingAmountExceeded(); - error RFQSwapWithZeroAmount(); - error InvalidatedOrder(); - - /** - * @notice Emitted when RFQ gets filled - * @param orderHash Hash of the order - * @param makingAmount Amount of the maker asset that was transferred from maker to taker - */ - event OrderFilledRFQ( - bytes32 orderHash, - uint256 makingAmount - ); - - uint256 private constant _RAW_CALL_GAS_LIMIT = 5000; - uint256 private constant _MAKER_AMOUNT_FLAG = 1 << 255; - uint256 private constant _SIGNER_SMART_CONTRACT_HINT = 1 << 254; - uint256 private constant _IS_VALID_SIGNATURE_65_BYTES = 1 << 253; - uint256 private constant _UNWRAP_WETH_FLAG = 1 << 252; - uint256 private constant _AMOUNT_MASK = ~( - _MAKER_AMOUNT_FLAG | - _SIGNER_SMART_CONTRACT_HINT | - _IS_VALID_SIGNATURE_65_BYTES | - _UNWRAP_WETH_FLAG - ); - - IWETH private immutable _WETH; // solhint-disable-line var-name-mixedcase - mapping(address => mapping(uint256 => uint256)) private _invalidator; - - constructor(IWETH weth) OnlyWethReceiver(address(weth)) { - _WETH = weth; - } - - /** - * @notice Returns bitmask for double-spend invalidators based on lowest byte of order.info and filled quotes - * @param maker Maker address - * @param slot Slot number to return bitmask for - * @return result Each bit represents whether corresponding was already invalidated - */ - function invalidatorForOrderRFQ(address maker, uint256 slot) external view returns(uint256 /* result */) { - return _invalidator[maker][slot]; - } - - /** - * @notice Cancels order's quote - * @param orderInfo Order info (only order id in lowest 64 bits is used) - */ - function cancelOrderRFQ(uint256 orderInfo) external { - _invalidateOrder(msg.sender, orderInfo, 0); - } - - /// @notice Cancels multiple order's quotes - function cancelOrderRFQ(uint256 orderInfo, uint256 additionalMask) external { - _invalidateOrder(msg.sender, orderInfo, additionalMask); - } - - /** - * @notice Fills order's quote, fully or partially (whichever is possible) - * @param order Order quote to fill - * @param signature Signature to confirm quote ownership - * @param flagsAndAmount Fill configuration flags with amount packed in one slot - * @return filledMakingAmount Actual amount transferred from maker to taker - * @return filledTakingAmount Actual amount transferred from taker to maker - * @return orderHash Hash of the filled order - */ - function fillOrderRFQ( - OrderRFQLib.OrderRFQ memory order, - bytes calldata signature, - uint256 flagsAndAmount - ) external payable returns(uint256 /* filledMakingAmount */, uint256 /* filledTakingAmount */, bytes32 /* orderHash */) { - return fillOrderRFQTo(order, signature, flagsAndAmount, msg.sender); - } - - /** - * @notice Fills order's quote, fully or partially, with compact signature - * @param order Order quote to fill - * @param r R component of signature - * @param vs VS component of signature - * @param flagsAndAmount Fill configuration flags with amount packed in one slot - * - Bits 0-252 contain the amount to fill - * - Bit 253 is used to indicate whether signature is 64-bit (0) or 65-bit (1) - * - Bit 254 is used to indicate whether smart contract (1) signed the order or not (0) - * - Bit 255 is used to indicate whether maker (1) or taker amount (0) is given in the amount parameter - * @return filledMakingAmount Actual amount transferred from maker to taker - * @return filledTakingAmount Actual amount transferred from taker to maker - * @return orderHash Hash of the filled order - */ - function fillOrderRFQCompact( - OrderRFQLib.OrderRFQ memory order, - bytes32 r, - bytes32 vs, - uint256 flagsAndAmount - ) external payable returns(uint256 filledMakingAmount, uint256 filledTakingAmount, bytes32 orderHash) { - orderHash = order.hash(_domainSeparatorV4()); - if (flagsAndAmount & _SIGNER_SMART_CONTRACT_HINT != 0) { - if (flagsAndAmount & _IS_VALID_SIGNATURE_65_BYTES != 0) { - if (!ECDSA.isValidSignature65(order.maker, orderHash, r, vs)) revert RFQBadSignature(); - } else { - if (!ECDSA.isValidSignature(order.maker, orderHash, r, vs)) revert RFQBadSignature(); - } - } else { - if(!ECDSA.recoverOrIsValidSignature(order.maker, orderHash, r, vs)) revert RFQBadSignature(); - } - - (filledMakingAmount, filledTakingAmount) = _fillOrderRFQTo(order, flagsAndAmount, msg.sender); - emit OrderFilledRFQ(orderHash, filledMakingAmount); - } - - /** - * @notice Same as `fillOrderRFQTo` but calls permit first. - * It allows to approve token spending and make a swap in one transaction. - * Also allows to specify funds destination instead of `msg.sender` - * @param order Order quote to fill - * @param signature Signature to confirm quote ownership - * @param flagsAndAmount Fill configuration flags with amount packed in one slot - * @param target Address that will receive swap funds - * @param permit Should consist of abiencoded token address and encoded `IERC20Permit.permit` call. - * @return filledMakingAmount Actual amount transferred from maker to taker - * @return filledTakingAmount Actual amount transferred from taker to maker - * @return orderHash Hash of the filled order - * @dev See tests for examples - */ - function fillOrderRFQToWithPermit( - OrderRFQLib.OrderRFQ memory order, - bytes calldata signature, - uint256 flagsAndAmount, - address target, - bytes calldata permit - ) external returns(uint256 /* filledMakingAmount */, uint256 /* filledTakingAmount */, bytes32 /* orderHash */) { - IERC20(order.takerAsset).safePermit(permit); - return fillOrderRFQTo(order, signature, flagsAndAmount, target); - } - - /** - * @notice Same as `fillOrderRFQ` but allows to specify funds destination instead of `msg.sender` - * @param order Order quote to fill - * @param signature Signature to confirm quote ownership - * @param flagsAndAmount Fill configuration flags with amount packed in one slot - * @param target Address that will receive swap funds - * @return filledMakingAmount Actual amount transferred from maker to taker - * @return filledTakingAmount Actual amount transferred from taker to maker - * @return orderHash Hash of the filled order - */ - function fillOrderRFQTo( - OrderRFQLib.OrderRFQ memory order, - bytes calldata signature, - uint256 flagsAndAmount, - address target - ) public payable returns(uint256 filledMakingAmount, uint256 filledTakingAmount, bytes32 orderHash) { - orderHash = order.hash(_domainSeparatorV4()); - if (flagsAndAmount & _SIGNER_SMART_CONTRACT_HINT != 0) { - if (flagsAndAmount & _IS_VALID_SIGNATURE_65_BYTES != 0 && signature.length != 65) revert RFQBadSignature(); - if (!ECDSA.isValidSignature(order.maker, orderHash, signature)) revert RFQBadSignature(); - } else { - if(!ECDSA.recoverOrIsValidSignature(order.maker, orderHash, signature)) revert RFQBadSignature(); - } - (filledMakingAmount, filledTakingAmount) = _fillOrderRFQTo(order, flagsAndAmount, target); - emit OrderFilledRFQ(orderHash, filledMakingAmount); - } - - function _fillOrderRFQTo( - OrderRFQLib.OrderRFQ memory order, - uint256 flagsAndAmount, - address target - ) private returns(uint256 makingAmount, uint256 takingAmount) { - if (target == address(0)) revert RFQZeroTargetIsForbidden(); - - address maker = order.maker; - - // Validate order - if (order.allowedSender != address(0) && order.allowedSender != msg.sender) revert RFQPrivateOrder(); - - { // Stack too deep - uint256 info = order.info; - // Check time expiration - uint256 expiration = uint128(info) >> 64; - if (expiration != 0 && block.timestamp > expiration) revert OrderExpired(); // solhint-disable-line not-rely-on-time - _invalidateOrder(maker, info, 0); - } - - { // Stack too deep - uint256 orderMakingAmount = order.makingAmount; - uint256 orderTakingAmount = order.takingAmount; - uint256 amount = flagsAndAmount & _AMOUNT_MASK; - // Compute partial fill if needed - if (amount == 0) { - // zero amount means whole order - makingAmount = orderMakingAmount; - takingAmount = orderTakingAmount; - } - else if (flagsAndAmount & _MAKER_AMOUNT_FLAG != 0) { - if (amount > orderMakingAmount) revert MakingAmountExceeded(); - makingAmount = amount; - takingAmount = AmountCalculator.getTakingAmount(orderMakingAmount, orderTakingAmount, makingAmount); - } - else { - if (amount > orderTakingAmount) revert TakingAmountExceeded(); - takingAmount = amount; - makingAmount = AmountCalculator.getMakingAmount(orderMakingAmount, orderTakingAmount, takingAmount); - } - } - - if (makingAmount == 0 || takingAmount == 0) revert RFQSwapWithZeroAmount(); - - // Maker => Taker - if (order.makerAsset == address(_WETH) && flagsAndAmount & _UNWRAP_WETH_FLAG != 0) { - _WETH.transferFrom(maker, address(this), makingAmount); - _WETH.withdraw(makingAmount); - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = target.call{value: makingAmount, gas: _RAW_CALL_GAS_LIMIT}(""); - if (!success) revert Errors.ETHTransferFailed(); - } else { - IERC20(order.makerAsset).safeTransferFrom(maker, target, makingAmount); - } - - // Taker => Maker - if (order.takerAsset == address(_WETH) && msg.value > 0) { - if (msg.value != takingAmount) revert Errors.InvalidMsgValue(); - _WETH.deposit{ value: takingAmount }(); - _WETH.transfer(maker, takingAmount); - } else { - if (msg.value != 0) revert Errors.InvalidMsgValue(); - IERC20(order.takerAsset).safeTransferFrom(msg.sender, maker, takingAmount); - } - } - - function _invalidateOrder(address maker, uint256 orderInfo, uint256 additionalMask) private { - uint256 invalidatorSlot = uint64(orderInfo) >> 8; - uint256 invalidatorBits = (1 << uint8(orderInfo)) | additionalMask; - mapping(uint256 => uint256) storage invalidatorStorage = _invalidator[maker]; - uint256 invalidator = invalidatorStorage[invalidatorSlot]; - if (invalidator & invalidatorBits == invalidatorBits) revert InvalidatedOrder(); - invalidatorStorage[invalidatorSlot] = invalidator | invalidatorBits; - } -} - - -// File @1inch/limit-order-protocol/contracts/OrderLib.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - -library OrderLib { - struct Order { - uint256 salt; - address makerAsset; - address takerAsset; - address maker; - address receiver; - address allowedSender; // equals to Zero address on public orders - uint256 makingAmount; - uint256 takingAmount; - uint256 offsets; - // bytes makerAssetData; - // bytes takerAssetData; - // bytes getMakingAmount; // this.staticcall(abi.encodePacked(bytes, swapTakerAmount)) => (swapMakerAmount) - // bytes getTakingAmount; // this.staticcall(abi.encodePacked(bytes, swapMakerAmount)) => (swapTakerAmount) - // bytes predicate; // this.staticcall(bytes) => (bool) - // bytes permit; // On first fill: permit.1.call(abi.encodePacked(permit.selector, permit.2)) - // bytes preInteraction; - // bytes postInteraction; - bytes interactions; // concat(makerAssetData, takerAssetData, getMakingAmount, getTakingAmount, predicate, permit, preIntercation, postInteraction) - } - - bytes32 constant internal _LIMIT_ORDER_TYPEHASH = keccak256( - "Order(" - "uint256 salt," - "address makerAsset," - "address takerAsset," - "address maker," - "address receiver," - "address allowedSender," - "uint256 makingAmount," - "uint256 takingAmount," - "uint256 offsets," - "bytes interactions" - ")" - ); - - enum DynamicField { - MakerAssetData, - TakerAssetData, - GetMakingAmount, - GetTakingAmount, - Predicate, - Permit, - PreInteraction, - PostInteraction - } - - function getterIsFrozen(bytes calldata getter) internal pure returns(bool) { - return getter.length == 1 && getter[0] == "x"; - } - - function _get(Order calldata order, DynamicField field) private pure returns(bytes calldata) { - uint256 bitShift = uint256(field) << 5; // field * 32 - return order.interactions[ - uint32((order.offsets << 32) >> bitShift): - uint32(order.offsets >> bitShift) - ]; - } - - function makerAssetData(Order calldata order) internal pure returns(bytes calldata) { - return _get(order, DynamicField.MakerAssetData); - } - - function takerAssetData(Order calldata order) internal pure returns(bytes calldata) { - return _get(order, DynamicField.TakerAssetData); - } - - function getMakingAmount(Order calldata order) internal pure returns(bytes calldata) { - return _get(order, DynamicField.GetMakingAmount); - } - - function getTakingAmount(Order calldata order) internal pure returns(bytes calldata) { - return _get(order, DynamicField.GetTakingAmount); - } - - function predicate(Order calldata order) internal pure returns(bytes calldata) { - return _get(order, DynamicField.Predicate); - } - - function permit(Order calldata order) internal pure returns(bytes calldata) { - return _get(order, DynamicField.Permit); - } - - function preInteraction(Order calldata order) internal pure returns(bytes calldata) { - return _get(order, DynamicField.PreInteraction); - } - - function postInteraction(Order calldata order) internal pure returns(bytes calldata) { - return _get(order, DynamicField.PostInteraction); - } - - function hash(Order calldata order, bytes32 domainSeparator) internal pure returns(bytes32 result) { - bytes calldata interactions = order.interactions; - bytes32 typehash = _LIMIT_ORDER_TYPEHASH; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - - // keccak256(abi.encode(_LIMIT_ORDER_TYPEHASH, orderWithoutInteractions, keccak256(order.interactions))); - calldatacopy(ptr, interactions.offset, interactions.length) - mstore(add(ptr, 0x140), keccak256(ptr, interactions.length)) - calldatacopy(add(ptr, 0x20), order, 0x120) - mstore(ptr, typehash) - result := keccak256(ptr, 0x160) - } - result = ECDSA.toTypedDataHash(domainSeparator, result); - } -} - - -// File @1inch/limit-order-protocol/contracts/libraries/ArgumentsDecoder.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - -/// @title Library with gas efficient alternatives to `abi.decode` -library ArgumentsDecoder { - error IncorrectDataLength(); - - function decodeUint256(bytes calldata data, uint256 offset) internal pure returns(uint256 value) { - unchecked { if (data.length < offset + 32) revert IncorrectDataLength(); } - // no memory ops inside so this insertion is automatically memory safe - assembly { // solhint-disable-line no-inline-assembly - value := calldataload(add(data.offset, offset)) - } - } - - function decodeSelector(bytes calldata data) internal pure returns(bytes4 value) { - if (data.length < 4) revert IncorrectDataLength(); - // no memory ops inside so this insertion is automatically memory safe - assembly { // solhint-disable-line no-inline-assembly - value := calldataload(data.offset) - } - } - - function decodeTailCalldata(bytes calldata data, uint256 tailOffset) internal pure returns(bytes calldata args) { - if (data.length < tailOffset) revert IncorrectDataLength(); - // no memory ops inside so this insertion is automatically memory safe - assembly { // solhint-disable-line no-inline-assembly - args.offset := add(data.offset, tailOffset) - args.length := sub(data.length, tailOffset) - } - } - - function decodeTargetAndCalldata(bytes calldata data) internal pure returns(address target, bytes calldata args) { - if (data.length < 20) revert IncorrectDataLength(); - // no memory ops inside so this insertion is automatically memory safe - assembly { // solhint-disable-line no-inline-assembly - target := shr(96, calldataload(data.offset)) - args.offset := add(data.offset, 20) - args.length := sub(data.length, 20) - } - } -} - - -// File @1inch/limit-order-protocol/contracts/helpers/NonceManager.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - -/// @title A helper contract for managing nonce of tx sender -contract NonceManager { - error AdvanceNonceFailed(); - event NonceIncreased(address indexed maker, uint256 newNonce); - - mapping(address => uint256) public nonce; - - /// @notice Advances nonce by one - function increaseNonce() external { - advanceNonce(1); - } - - /// @notice Advances nonce by specified amount - function advanceNonce(uint8 amount) public { - if (amount == 0) revert AdvanceNonceFailed(); - uint256 newNonce = nonce[msg.sender] + amount; - nonce[msg.sender] = newNonce; - emit NonceIncreased(msg.sender, newNonce); - } - - /// @notice Checks if `makerAddress` has specified `makerNonce` - /// @return Result True if `makerAddress` has specified nonce. Otherwise, false - function nonceEquals(address makerAddress, uint256 makerNonce) public view returns(bool) { - return nonce[makerAddress] == makerNonce; - } -} - - -// File @1inch/limit-order-protocol/contracts/helpers/PredicateHelper.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - - -/// @title A helper contract for executing boolean functions on arbitrary target call results -contract PredicateHelper is NonceManager { - using ArgumentsDecoder for bytes; - - error ArbitraryStaticCallFailed(); - - /// @notice Calls every target with corresponding data - /// @return Result True if call to any target returned True. Otherwise, false - function or(uint256 offsets, bytes calldata data) public view returns(bool) { - uint256 current; - uint256 previous; - for (uint256 i = 0; (current = uint32(offsets >> i)) != 0; i += 32) { - (bool success, uint256 res) = _selfStaticCall(data[previous:current]); - if (success && res == 1) { - return true; - } - previous = current; - } - return false; - } - - /// @notice Calls every target with corresponding data - /// @return Result True if calls to all targets returned True. Otherwise, false - function and(uint256 offsets, bytes calldata data) public view returns(bool) { - uint256 current; - uint256 previous; - for (uint256 i = 0; (current = uint32(offsets >> i)) != 0; i += 32) { - (bool success, uint256 res) = _selfStaticCall(data[previous:current]); - if (!success || res != 1) { - return false; - } - previous = current; - } - return true; - } - - /// @notice Calls target with specified data and tests if it's equal to the value - /// @param value Value to test - /// @return Result True if call to target returns the same value as `value`. Otherwise, false - function eq(uint256 value, bytes calldata data) public view returns(bool) { - (bool success, uint256 res) = _selfStaticCall(data); - return success && res == value; - } - - /// @notice Calls target with specified data and tests if it's lower than value - /// @param value Value to test - /// @return Result True if call to target returns value which is lower than `value`. Otherwise, false - function lt(uint256 value, bytes calldata data) public view returns(bool) { - (bool success, uint256 res) = _selfStaticCall(data); - return success && res < value; - } - - /// @notice Calls target with specified data and tests if it's bigger than value - /// @param value Value to test - /// @return Result True if call to target returns value which is bigger than `value`. Otherwise, false - function gt(uint256 value, bytes calldata data) public view returns(bool) { - (bool success, uint256 res) = _selfStaticCall(data); - return success && res > value; - } - - /// @notice Checks passed time against block timestamp - /// @return Result True if current block timestamp is lower than `time`. Otherwise, false - function timestampBelow(uint256 time) public view returns(bool) { - return block.timestamp < time; // solhint-disable-line not-rely-on-time - } - - /// @notice Performs an arbitrary call to target with data - /// @return Result Bytes transmuted to uint256 - function arbitraryStaticCall(address target, bytes calldata data) public view returns(uint256) { - (bool success, uint256 res) = _staticcallForUint(target, data); - if (!success) revert ArbitraryStaticCallFailed(); - return res; - } - - function timestampBelowAndNonceEquals(uint256 timeNonceAccount) public view returns(bool) { - uint256 _time = uint48(timeNonceAccount >> 208); - uint256 _nonce = uint48(timeNonceAccount >> 160); - address _account = address(uint160(timeNonceAccount)); - return timestampBelow(_time) && nonceEquals(_account, _nonce); - } - - function _selfStaticCall(bytes calldata data) internal view returns(bool, uint256) { - uint256 selector = uint32(data.decodeSelector()); - uint256 arg = data.decodeUint256(4); - - // special case for the most often used predicate - if (selector == uint32(this.timestampBelowAndNonceEquals.selector)) { // 0x2cc2878d - return (true, timestampBelowAndNonceEquals(arg) ? 1 : 0); - } - - if (selector < uint32(this.arbitraryStaticCall.selector)) { // 0xbf15fcd8 - if (selector < uint32(this.eq.selector)) { // 0x6fe7b0ba - if (selector == uint32(this.gt.selector)) { // 0x4f38e2b8 - return (true, gt(arg, data.decodeTailCalldata(100)) ? 1 : 0); - } else if (selector == uint32(this.timestampBelow.selector)) { // 0x63592c2b - return (true, timestampBelow(arg) ? 1 : 0); - } - } else { - if (selector == uint32(this.eq.selector)) { // 0x6fe7b0ba - return (true, eq(arg, data.decodeTailCalldata(100)) ? 1 : 0); - } else if (selector == uint32(this.or.selector)) { // 0x74261145 - return (true, or(arg, data.decodeTailCalldata(100)) ? 1 : 0); - } - } - } else { - if (selector < uint32(this.lt.selector)) { // 0xca4ece22 - if (selector == uint32(this.arbitraryStaticCall.selector)) { // 0xbf15fcd8 - return (true, arbitraryStaticCall(address(uint160(arg)), data.decodeTailCalldata(100))); - } else if (selector == uint32(this.and.selector)) { // 0xbfa75143 - return (true, and(arg, data.decodeTailCalldata(100)) ? 1 : 0); - } - } else { - if (selector == uint32(this.lt.selector)) { // 0xca4ece22 - return (true, lt(arg, data.decodeTailCalldata(100)) ? 1 : 0); - } else if (selector == uint32(this.nonceEquals.selector)) { // 0xcf6fc6e3 - return (true, nonceEquals(address(uint160(arg)), data.decodeUint256(0x24)) ? 1 : 0); - } - } - } - - return _staticcallForUint(address(this), data); - } - - function _staticcallForUint(address target, bytes calldata input) private view returns(bool success, uint256 res) { - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let data := mload(0x40) - - calldatacopy(data, input.offset, input.length) - success := staticcall(gas(), target, data, input.length, 0x0, 0x20) - success := and(success, eq(returndatasize(), 32)) - if success { - res := mload(0) - } - } - } -} - - -// File @1inch/limit-order-protocol/contracts/interfaces/IOrderMixin.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - -interface IOrderMixin { - /** - * @notice Returns unfilled amount for order. Throws if order does not exist - * @param orderHash Order's hash. Can be obtained by the `hashOrder` function - * @return amount Unfilled amount - */ - function remaining(bytes32 orderHash) external view returns(uint256 amount); - - /** - * @notice Returns unfilled amount for order - * @param orderHash Order's hash. Can be obtained by the `hashOrder` function - * @return rawAmount Unfilled amount of order plus one if order exists. Otherwise 0 - */ - function remainingRaw(bytes32 orderHash) external view returns(uint256 rawAmount); - - /** - * @notice Same as `remainingRaw` but for multiple orders - * @param orderHashes Array of hashes - * @return rawAmounts Array of amounts for each order plus one if order exists or 0 otherwise - */ - function remainingsRaw(bytes32[] memory orderHashes) external view returns(uint256[] memory rawAmounts); - - /** - * @notice Checks order predicate - * @param order Order to check predicate for - * @return result Predicate evaluation result. True if predicate allows to fill the order, false otherwise - */ - function checkPredicate(OrderLib.Order calldata order) external view returns(bool result); - - /** - * @notice Returns order hash according to EIP712 standard - * @param order Order to get hash for - * @return orderHash Hash of the order - */ - function hashOrder(OrderLib.Order calldata order) external view returns(bytes32); - - /** - * @notice Delegates execution to custom implementation. Could be used to validate if `transferFrom` works properly - * @dev The function always reverts and returns the simulation results in revert data. - * @param target Addresses that will be delegated - * @param data Data that will be passed to delegatee - */ - function simulate(address target, bytes calldata data) external; - - /** - * @notice Cancels order. - * @dev Order is cancelled by setting remaining amount to _ORDER_FILLED value - * @param order Order quote to cancel - * @return orderRemaining Unfilled amount of order before cancellation - * @return orderHash Hash of the filled order - */ - function cancelOrder(OrderLib.Order calldata order) external returns(uint256 orderRemaining, bytes32 orderHash); - - /** - * @notice Fills an order. If one doesn't exist (first fill) it will be created using order.makerAssetData - * @param order Order quote to fill - * @param signature Signature to confirm quote ownership - * @param interaction A call data for InteractiveNotificationReceiver. Taker may execute interaction after getting maker assets and before sending taker assets. - * @param makingAmount Making amount - * @param takingAmount Taking amount - * @param skipPermitAndThresholdAmount Specifies maximum allowed takingAmount when takingAmount is zero, otherwise specifies minimum allowed makingAmount. Top-most bit specifies whether taker wants to skip maker's permit. - * @return actualMakingAmount Actual amount transferred from maker to taker - * @return actualTakingAmount Actual amount transferred from taker to maker - * @return orderHash Hash of the filled order - */ - function fillOrder( - OrderLib.Order calldata order, - bytes calldata signature, - bytes calldata interaction, - uint256 makingAmount, - uint256 takingAmount, - uint256 skipPermitAndThresholdAmount - ) external payable returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash); - - /** - * @notice Same as `fillOrderTo` but calls permit first, - * allowing to approve token spending and make a swap in one transaction. - * Also allows to specify funds destination instead of `msg.sender` - * @dev See tests for examples - * @param order Order quote to fill - * @param signature Signature to confirm quote ownership - * @param interaction A call data for InteractiveNotificationReceiver. Taker may execute interaction after getting maker assets and before sending taker assets. - * @param makingAmount Making amount - * @param takingAmount Taking amount - * @param skipPermitAndThresholdAmount Specifies maximum allowed takingAmount when takingAmount is zero, otherwise specifies minimum allowed makingAmount. Top-most bit specifies whether taker wants to skip maker's permit. - * @param target Address that will receive swap funds - * @param permit Should consist of abiencoded token address and encoded `IERC20Permit.permit` call. - * @return actualMakingAmount Actual amount transferred from maker to taker - * @return actualTakingAmount Actual amount transferred from taker to maker - * @return orderHash Hash of the filled order - */ - function fillOrderToWithPermit( - OrderLib.Order calldata order, - bytes calldata signature, - bytes calldata interaction, - uint256 makingAmount, - uint256 takingAmount, - uint256 skipPermitAndThresholdAmount, - address target, - bytes calldata permit - ) external returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash); - - /** - * @notice Same as `fillOrder` but allows to specify funds destination instead of `msg.sender` - * @param order_ Order quote to fill - * @param signature Signature to confirm quote ownership - * @param interaction A call data for InteractiveNotificationReceiver. Taker may execute interaction after getting maker assets and before sending taker assets. - * @param makingAmount Making amount - * @param takingAmount Taking amount - * @param skipPermitAndThresholdAmount Specifies maximum allowed takingAmount when takingAmount is zero, otherwise specifies minimum allowed makingAmount. Top-most bit specifies whether taker wants to skip maker's permit. - * @param target Address that will receive swap funds - * @return actualMakingAmount Actual amount transferred from maker to taker - * @return actualTakingAmount Actual amount transferred from taker to maker - * @return orderHash Hash of the filled order - */ - function fillOrderTo( - OrderLib.Order calldata order_, - bytes calldata signature, - bytes calldata interaction, - uint256 makingAmount, - uint256 takingAmount, - uint256 skipPermitAndThresholdAmount, - address target - ) external payable returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash); -} - - -// File @1inch/limit-order-protocol/contracts/interfaces/NotificationReceiver.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - -/// @title Interface for interactor which acts between `maker => taker` and `taker => maker` transfers. -interface PreInteractionNotificationReceiver { - function fillOrderPreInteraction( - bytes32 orderHash, - address maker, - address taker, - uint256 makingAmount, - uint256 takingAmount, - uint256 remainingAmount, - bytes memory interactiveData - ) external; -} - -interface PostInteractionNotificationReceiver { - /// @notice Callback method that gets called after taker transferred funds to maker but before - /// the opposite transfer happened - function fillOrderPostInteraction( - bytes32 orderHash, - address maker, - address taker, - uint256 makingAmount, - uint256 takingAmount, - uint256 remainingAmount, - bytes memory interactiveData - ) external; -} - -interface InteractionNotificationReceiver { - function fillOrderInteraction( - address taker, - uint256 makingAmount, - uint256 takingAmount, - bytes memory interactiveData - ) external returns(uint256 offeredTakingAmount); -} - - -// File @1inch/limit-order-protocol/contracts/OrderMixin.sol@v0.3.0-prerelease - - -pragma solidity 0.8.17; - - - - -/// @title Regular Limit Order mixin -abstract contract OrderMixin is IOrderMixin, EIP712, PredicateHelper { - using SafeERC20 for IERC20; - using ArgumentsDecoder for bytes; - using OrderLib for OrderLib.Order; - - error UnknownOrder(); - error AccessDenied(); - error AlreadyFilled(); - error PermitLengthTooLow(); - error ZeroTargetIsForbidden(); - error RemainingAmountIsZero(); - error PrivateOrder(); - error BadSignature(); - error ReentrancyDetected(); - error PredicateIsNotTrue(); - error OnlyOneAmountShouldBeZero(); - error TakingAmountTooHigh(); - error MakingAmountTooLow(); - error SwapWithZeroAmount(); - error TransferFromMakerToTakerFailed(); - error TransferFromTakerToMakerFailed(); - error WrongAmount(); - error WrongGetter(); - error GetAmountCallFailed(); - error TakingAmountIncreased(); - error SimulationResults(bool success, bytes res); - - /// @notice Emitted every time order gets filled, including partial fills - event OrderFilled( - address indexed maker, - bytes32 orderHash, - uint256 remaining - ); - - /// @notice Emitted when order gets cancelled - event OrderCanceled( - address indexed maker, - bytes32 orderHash, - uint256 remainingRaw - ); - - uint256 constant private _ORDER_DOES_NOT_EXIST = 0; - uint256 constant private _ORDER_FILLED = 1; - uint256 constant private _SKIP_PERMIT_FLAG = 1 << 255; - uint256 constant private _THRESHOLD_MASK = ~_SKIP_PERMIT_FLAG; - - IWETH private immutable _WETH; // solhint-disable-line var-name-mixedcase - /// @notice Stores unfilled amounts for each order plus one. - /// Therefore 0 means order doesn't exist and 1 means order was filled - mapping(bytes32 => uint256) private _remaining; - - constructor(IWETH weth) { - _WETH = weth; - } - - /** - * @notice See {IOrderMixin-remaining}. - */ - function remaining(bytes32 orderHash) external view returns(uint256 /* amount */) { - uint256 amount = _remaining[orderHash]; - if (amount == _ORDER_DOES_NOT_EXIST) revert UnknownOrder(); - unchecked { return amount - 1; } - } - - /** - * @notice See {IOrderMixin-remainingRaw}. - */ - function remainingRaw(bytes32 orderHash) external view returns(uint256 /* rawAmount */) { - return _remaining[orderHash]; - } - - /** - * @notice See {IOrderMixin-remainingsRaw}. - */ - function remainingsRaw(bytes32[] memory orderHashes) external view returns(uint256[] memory /* rawAmounts */) { - uint256[] memory results = new uint256[](orderHashes.length); - for (uint256 i = 0; i < orderHashes.length; i++) { - results[i] = _remaining[orderHashes[i]]; - } - return results; - } - - /** - * @notice See {IOrderMixin-simulate}. - */ - function simulate(address target, bytes calldata data) external { - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory result) = target.delegatecall(data); - revert SimulationResults(success, result); - } - - /** - * @notice See {IOrderMixin-cancelOrder}. - */ - function cancelOrder(OrderLib.Order calldata order) external returns(uint256 orderRemaining, bytes32 orderHash) { - if (order.maker != msg.sender) revert AccessDenied(); - - orderHash = hashOrder(order); - orderRemaining = _remaining[orderHash]; - if (orderRemaining == _ORDER_FILLED) revert AlreadyFilled(); - emit OrderCanceled(msg.sender, orderHash, orderRemaining); - _remaining[orderHash] = _ORDER_FILLED; - } - - /** - * @notice See {IOrderMixin-fillOrder}. - */ - function fillOrder( - OrderLib.Order calldata order, - bytes calldata signature, - bytes calldata interaction, - uint256 makingAmount, - uint256 takingAmount, - uint256 skipPermitAndThresholdAmount - ) external payable returns(uint256 /* actualMakingAmount */, uint256 /* actualTakingAmount */, bytes32 /* orderHash */) { - return fillOrderTo(order, signature, interaction, makingAmount, takingAmount, skipPermitAndThresholdAmount, msg.sender); - } - - /** - * @notice See {IOrderMixin-fillOrderToWithPermit}. - */ - function fillOrderToWithPermit( - OrderLib.Order calldata order, - bytes calldata signature, - bytes calldata interaction, - uint256 makingAmount, - uint256 takingAmount, - uint256 skipPermitAndThresholdAmount, - address target, - bytes calldata permit - ) external returns(uint256 /* actualMakingAmount */, uint256 /* actualTakingAmount */, bytes32 /* orderHash */) { - if (permit.length < 20) revert PermitLengthTooLow(); - { // Stack too deep - (address token, bytes calldata permitData) = permit.decodeTargetAndCalldata(); - IERC20(token).safePermit(permitData); - } - return fillOrderTo(order, signature, interaction, makingAmount, takingAmount, skipPermitAndThresholdAmount, target); - } - - /** - * @notice See {IOrderMixin-fillOrderTo}. - */ - function fillOrderTo( - OrderLib.Order calldata order_, - bytes calldata signature, - bytes calldata interaction, - uint256 makingAmount, - uint256 takingAmount, - uint256 skipPermitAndThresholdAmount, - address target - ) public payable returns(uint256 actualMakingAmount, uint256 actualTakingAmount, bytes32 orderHash) { - if (target == address(0)) revert ZeroTargetIsForbidden(); - orderHash = hashOrder(order_); - - OrderLib.Order calldata order = order_; // Helps with "Stack too deep" - actualMakingAmount = makingAmount; - actualTakingAmount = takingAmount; - - uint256 remainingMakingAmount = _remaining[orderHash]; - if (remainingMakingAmount == _ORDER_FILLED) revert RemainingAmountIsZero(); - if (order.allowedSender != address(0) && order.allowedSender != msg.sender) revert PrivateOrder(); - if (remainingMakingAmount == _ORDER_DOES_NOT_EXIST) { - // First fill: validate order and permit maker asset - if (!ECDSA.recoverOrIsValidSignature(order.maker, orderHash, signature)) revert BadSignature(); - remainingMakingAmount = order.makingAmount; - - bytes calldata permit = order.permit(); - if (skipPermitAndThresholdAmount & _SKIP_PERMIT_FLAG == 0 && permit.length >= 20) { - // proceed only if taker is willing to execute permit and its length is enough to store address - (address token, bytes calldata permitCalldata) = permit.decodeTargetAndCalldata(); - IERC20(token).safePermit(permitCalldata); - if (_remaining[orderHash] != _ORDER_DOES_NOT_EXIST) revert ReentrancyDetected(); - } - } else { - unchecked { remainingMakingAmount -= 1; } - } - - // Check if order is valid - if (order.predicate().length > 0) { - if (!checkPredicate(order)) revert PredicateIsNotTrue(); - } - - // Compute maker and taker assets amount - if ((actualTakingAmount == 0) == (actualMakingAmount == 0)) { - revert OnlyOneAmountShouldBeZero(); - } else if (actualTakingAmount == 0) { - if (actualMakingAmount > remainingMakingAmount) { - actualMakingAmount = remainingMakingAmount; - } - actualTakingAmount = _getTakingAmount(order.getTakingAmount(), order.makingAmount, actualMakingAmount, order.takingAmount, remainingMakingAmount, orderHash); - uint256 thresholdAmount = skipPermitAndThresholdAmount & _THRESHOLD_MASK; - // check that actual rate is not worse than what was expected - // actualTakingAmount / actualMakingAmount <= thresholdAmount / makingAmount - if (actualTakingAmount * makingAmount > thresholdAmount * actualMakingAmount) revert TakingAmountTooHigh(); - } else { - actualMakingAmount = _getMakingAmount(order.getMakingAmount(), order.takingAmount, actualTakingAmount, order.makingAmount, remainingMakingAmount, orderHash); - if (actualMakingAmount > remainingMakingAmount) { - actualMakingAmount = remainingMakingAmount; - actualTakingAmount = _getTakingAmount(order.getTakingAmount(), order.makingAmount, actualMakingAmount, order.takingAmount, remainingMakingAmount, orderHash); - if (actualTakingAmount > takingAmount) revert TakingAmountIncreased(); - } - uint256 thresholdAmount = skipPermitAndThresholdAmount & _THRESHOLD_MASK; - // check that actual rate is not worse than what was expected - // actualMakingAmount / actualTakingAmount >= thresholdAmount / takingAmount - if (actualMakingAmount * takingAmount < thresholdAmount * actualTakingAmount) revert MakingAmountTooLow(); - } - - if (actualMakingAmount == 0 || actualTakingAmount == 0) revert SwapWithZeroAmount(); - - // Update remaining amount in storage - unchecked { - remainingMakingAmount = remainingMakingAmount - actualMakingAmount; - _remaining[orderHash] = remainingMakingAmount + 1; - } - emit OrderFilled(order_.maker, orderHash, remainingMakingAmount); - - // Maker can handle funds interactively - if (order.preInteraction().length >= 20) { - // proceed only if interaction length is enough to store address - (address interactionTarget, bytes calldata interactionData) = order.preInteraction().decodeTargetAndCalldata(); - PreInteractionNotificationReceiver(interactionTarget).fillOrderPreInteraction( - orderHash, order.maker, msg.sender, actualMakingAmount, actualTakingAmount, remainingMakingAmount, interactionData - ); - } - - // Maker => Taker - if (!_callTransferFrom( - order.makerAsset, - order.maker, - target, - actualMakingAmount, - order.makerAssetData() - )) revert TransferFromMakerToTakerFailed(); - - if (interaction.length >= 20) { - // proceed only if interaction length is enough to store address - (address interactionTarget, bytes calldata interactionData) = interaction.decodeTargetAndCalldata(); - uint256 offeredTakingAmount = InteractionNotificationReceiver(interactionTarget).fillOrderInteraction( - msg.sender, actualMakingAmount, actualTakingAmount, interactionData - ); - - if (offeredTakingAmount > actualTakingAmount && - !OrderLib.getterIsFrozen(order.getMakingAmount()) && - !OrderLib.getterIsFrozen(order.getTakingAmount())) - { - actualTakingAmount = offeredTakingAmount; - } - } - - // Taker => Maker - if (order.takerAsset == address(_WETH) && msg.value > 0) { - if (msg.value < actualTakingAmount) revert Errors.InvalidMsgValue(); - if (msg.value > actualTakingAmount) { - unchecked { - (bool success, ) = msg.sender.call{value: msg.value - actualTakingAmount}(""); // solhint-disable-line avoid-low-level-calls - if (!success) revert Errors.ETHTransferFailed(); - } - } - _WETH.deposit{ value: actualTakingAmount }(); - _WETH.transfer(order.receiver == address(0) ? order.maker : order.receiver, actualTakingAmount); - } else { - if (msg.value != 0) revert Errors.InvalidMsgValue(); - if (!_callTransferFrom( - order.takerAsset, - msg.sender, - order.receiver == address(0) ? order.maker : order.receiver, - actualTakingAmount, - order.takerAssetData() - )) revert TransferFromTakerToMakerFailed(); - } - - // Maker can handle funds interactively - if (order.postInteraction().length >= 20) { - // proceed only if interaction length is enough to store address - (address interactionTarget, bytes calldata interactionData) = order.postInteraction().decodeTargetAndCalldata(); - PostInteractionNotificationReceiver(interactionTarget).fillOrderPostInteraction( - orderHash, order.maker, msg.sender, actualMakingAmount, actualTakingAmount, remainingMakingAmount, interactionData - ); - } - } - - /** - * @notice See {IOrderMixin-checkPredicate}. - */ - function checkPredicate(OrderLib.Order calldata order) public view returns(bool) { - (bool success, uint256 res) = _selfStaticCall(order.predicate()); - return success && res == 1; - } - - /** - * @notice See {IOrderMixin-hashOrder}. - */ - function hashOrder(OrderLib.Order calldata order) public view returns(bytes32) { - return order.hash(_domainSeparatorV4()); - } - - function _callTransferFrom(address asset, address from, address to, uint256 amount, bytes calldata input) private returns(bool success) { - bytes4 selector = IERC20.transferFrom.selector; - /// @solidity memory-safe-assembly - assembly { // solhint-disable-line no-inline-assembly - let data := mload(0x40) - - mstore(data, selector) - mstore(add(data, 0x04), from) - mstore(add(data, 0x24), to) - mstore(add(data, 0x44), amount) - calldatacopy(add(data, 0x64), input.offset, input.length) - let status := call(gas(), asset, 0, data, add(0x64, input.length), 0x0, 0x20) - success := and(status, or(iszero(returndatasize()), and(gt(returndatasize(), 31), eq(mload(0), 1)))) - } - } - - function _getMakingAmount( - bytes calldata getter, - uint256 orderTakingAmount, - uint256 requestedTakingAmount, - uint256 orderMakingAmount, - uint256 remainingMakingAmount, - bytes32 orderHash - ) private view returns(uint256) { - if (getter.length == 0) { - // Linear proportion - return AmountCalculator.getMakingAmount(orderMakingAmount, orderTakingAmount, requestedTakingAmount); - } - return _callGetter(getter, orderTakingAmount, requestedTakingAmount, orderMakingAmount, remainingMakingAmount, orderHash); - } - - function _getTakingAmount( - bytes calldata getter, - uint256 orderMakingAmount, - uint256 requestedMakingAmount, - uint256 orderTakingAmount, - uint256 remainingMakingAmount, - bytes32 orderHash - ) private view returns(uint256) { - if (getter.length == 0) { - // Linear proportion - return AmountCalculator.getTakingAmount(orderMakingAmount, orderTakingAmount, requestedMakingAmount); - } - return _callGetter(getter, orderMakingAmount, requestedMakingAmount, orderTakingAmount, remainingMakingAmount, orderHash); - } - - function _callGetter( - bytes calldata getter, - uint256 orderExpectedAmount, - uint256 requestedAmount, - uint256 orderResultAmount, - uint256 remainingMakingAmount, - bytes32 orderHash - ) private view returns(uint256) { - if (getter.length == 1) { - if (OrderLib.getterIsFrozen(getter)) { - // On "x" getter calldata only exact amount is allowed - if (requestedAmount != orderExpectedAmount) revert WrongAmount(); - return orderResultAmount; - } else { - revert WrongGetter(); - } - } else { - (address target, bytes calldata data) = getter.decodeTargetAndCalldata(); - (bool success, bytes memory result) = target.staticcall(abi.encodePacked(data, requestedAmount, remainingMakingAmount, orderHash)); - if (!success || result.length != 32) revert GetAmountCallFailed(); - return abi.decode(result, (uint256)); - } - } -} - - -// File contracts/AggregationRouterV5.sol - - -pragma solidity 0.8.17; - - - - - -/// @notice Main contract incorporates a number of routers to perform swaps and limit orders protocol to fill limit orders -contract AggregationRouterV5 is EIP712("1inch Aggregation Router", "5"), Ownable, - ClipperRouter, GenericRouter, UnoswapRouter, UnoswapV3Router, OrderMixin, OrderRFQMixin -{ - using UniERC20 for IERC20; - - error ZeroAddress(); - - /** - * @dev Sets the wrapped eth token and clipper exhange interface - * Both values are immutable: they can only be set once during - * construction. - */ - constructor(IWETH weth) - UnoswapV3Router(weth) - ClipperRouter(weth) - OrderMixin(weth) - OrderRFQMixin(weth) - { - if (address(weth) == address(0)) revert ZeroAddress(); - } - - /** - * @notice Retrieves funds accidently sent directly to the contract address - * @param token ERC20 token to retrieve - * @param amount amount to retrieve - */ - function rescueFunds(IERC20 token, uint256 amount) external onlyOwner { - token.uniTransfer(payable(msg.sender), amount); - } - - /** - * @notice Destroys the contract and sends eth to sender. Use with caution. - * The only case when the use of the method is justified is if there is an exploit found. - * And the damage from the exploit is greater than from just an urgent contract change. - */ - function destroy() external onlyOwner { - selfdestruct(payable(msg.sender)); - } - - function _receive() internal override(EthReceiver, OnlyWethReceiver) { - EthReceiver._receive(); - } -} \ No newline at end of file diff --git a/packages/foundry/src/vault/strategy/trade/1InchTradeModule.sol b/packages/foundry/src/vault/strategy/trade/1InchTradeModule.sol new file mode 100644 index 00000000..64f461be --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/1InchTradeModule.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; + +contract UniV2TradeModule { + function getAmountOut( + uint256 amountIn, + address[] calldata tradePath, + address router + ) external view returns (uint256) { + uint256 len = tradePath.length; + uint256[] memory amountsOut = IUniswapRouterV2(router).getAmountsOut(amountIn, tradePath); + return amountsOut[len - 1]; + } + + function trade( + uint256 amountIn, + uint256 amountOut, + address[] calldata tradePath, + address router + ) external returns (uint256) { + IERC20(tradePath[0]).transferFrom(msg.sender, address(this), amountIn); + + uint256[] memory amountsOut = IUniswapRouterV2(router).swapExactTokensForTokens( + amountIn, + amountOut, + tradePath, + msg.sender, + block.timestamp + ); + + uint256 len = tradePath.length; + IERC20(tradePath[len - 1]).transfer(msg.sender, amountsOut[len - 1]); + return amountsOut[len - 1]; + } +} +/* +struct Order { + uint256 salt; + address makerAsset; + address takerAsset; + address maker; + address receiver; + address allowedSender; // equals to Zero address on public orders + uint256 makingAmount; + uint256 takingAmount; + uint256 offsets; + // bytes makerAssetData; + // bytes takerAssetData; + // bytes getMakingAmount; // this.staticcall(abi.encodePacked(bytes, swapTakerAmount)) => (swapMakerAmount) + // bytes getTakingAmount; // this.staticcall(abi.encodePacked(bytes, swapMakerAmount)) => (swapTakerAmount) + // bytes predicate; // this.staticcall(bytes) => (bool) + // bytes permit; // On first fill: permit.1.call(abi.encodePacked(permit.selector, permit.2)) + // bytes preInteraction; + // bytes postInteraction; + bytes interactions; // concat(makerAssetData, takerAssetData, getMakingAmount, getTakingAmount, predicate, permit, preIntercation, postInteraction) + } + */ + +/* +struct SwapDescription { + IERC20 srcToken; + IERC20 dstToken; + address payable srcReceiver; + address payable dstReceiver; + uint256 amount; + uint256 minReturnAmount; + uint256 flags; + } + */ + + /* + /// @notice Performs a swap, delegating all calls encoded in `data` to `executor`. See tests for usage examples + /// @dev router keeps 1 wei of every token on the contract balance for gas optimisations reasons. This affects first swap of every token by leaving 1 wei on the contract. + /// @param executor Aggregation executor that executes calls described in `data` + /// @param desc Swap description + /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls. + /// @param data Encoded calls that `caller` should execute in between of swaps + /// @return returnAmount Resulting token amount + /// @return spentAmount Source token amount + function swap( + IAggregationExecutor executor, + SwapDescription calldata desc, + bytes calldata permit, + bytes calldata data + ) + external + payable + returns ( + uint256 returnAmount, + uint256 spentAmount + ) + */ + + /* + /// @notice Swap token0 for token1, or token1 for token0 + /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback + /// @param recipient The address to receive the output of the swap + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this + /// value after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @param data Any data to be passed through to the callback + /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive + /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + */ \ No newline at end of file diff --git a/packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol b/packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol index 9b973a27..9049372c 100644 --- a/packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol +++ b/packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol @@ -3,54 +3,33 @@ pragma solidity ^0.8.15; import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; -import { IUniswapRouterV2 } from "../../interfaces/external/uni/IUniswapRouterV2.sol"; -import { IAdapter } from "../../interfaces/vault/IAdapter.sol"; -import { IWithRewards } from "../../interfaces/vault/IWithRewards.sol"; -import { StrategyBase } from "./StrategyBase.sol"; - -interface ITradeModule { - function getAmountOut( - uint256 amountIn, - address[] calldata tradePath, - address router - ) external view returns (uint256); - - function trade( - uint256 amountIn, - uint256 amountOut, - address[] calldata tradePath, - address router - ) external returns (uint256); -} - -struct Trade { - address tradeModule; - address router; - uint256 minTradeAmount; - uint256 slippage; - address[] tradePath; -} +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { StrategyBase } from "../StrategyBase.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import { ITradeModule, Trade } from "./ITradeModule.sol"; contract Pool2SingleAssetCompounder is StrategyBase { error NoValidTradePath(); /// @notice claim all token rewards and trade them for the underlying asset - function harvest() public override { - (address tradeModule, address router, uint256 minTradeAmount, uint256 slippage, address[] tradePath) = abi.decode( - IAdapter(address(this)).strategyConfig(), - ((address, address, uint256, uint256, address[])) - ); - - for (uint256 i = 0; i < len; i++) { - uint256 bal = ERC20(tradePath[0]).balanceOf(address(this)); - if (bal > minTradeAmount) { - // TODO amountOut currently doesnt account for oracle manipulation before the trade. It assumes a fair price - uint256 amountOut = ITradeModule(tradeModule).getAmountOut(bal, tradePath, router); - ITradeModule(tradeModule).trade(bal, (amountOut * (1e18 - slippage)) / 1e18, tradePath, router); - } - } - IAdapter(address(this)).strategyDeposit(ERC20(asset).balanceOf(address(this)), 0); - } + // function harvest() public override { + // (address tradeModule, address router, uint256 minTradeAmount, uint256 slippage, address[] tradePath) = abi.decode( + // IAdapter(address(this)).strategyConfig(), + // ((address, address, uint256, uint256, address[])) + // ); + + // for (uint256 i = 0; i < len; i++) { + // uint256 bal = ERC20(tradePath[0]).balanceOf(address(this)); + // if (bal > minTradeAmount) { + // // TODO amountOut currently doesnt account for oracle manipulation before the trade. It assumes a fair price + // uint256 amountOut = ITradeModule(tradeModule).getAmountOut(bal, tradePath, router); + // ITradeModule(tradeModule).trade(bal, (amountOut * (1e18 - slippage)) / 1e18, tradePath, router); + // } + // } + // IAdapter(address(this)).strategyDeposit(ERC20(asset).balanceOf(address(this)), 0); + // } } // asset -> asset diff --git a/packages/foundry/src/vault/strategy/trade/ITradeModule.sol b/packages/foundry/src/vault/strategy/trade/ITradeModule.sol new file mode 100644 index 00000000..0de5ddbe --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/ITradeModule.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 +pragma solidity ^0.8.15; + +struct Trade { + address tradeModule; + address router; + uint256 minTradeAmount; + uint256 slippage; + address[] tradePath; +} + +interface ITradeModule { + function getAmountOut( + uint256 amountIn, + address[] calldata tradePath, + address router + ) external view returns (uint256); + + function trade( + uint256 amountIn, + uint256 amountOut, + address[] calldata tradePath, + address router + ) external returns (uint256); +} \ No newline at end of file diff --git a/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol b/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol index b30d1f8e..fcff9dc7 100644 --- a/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol +++ b/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.15; import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; import { IUniswapV2Pair } from "../../../interfaces/external/uni/IUniswapV2Pair.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; contract UniV2TradeModule { function getAmountOut( @@ -19,7 +20,6 @@ contract UniV2TradeModule { function deposit( address asset, uint256 amountIn, - uint256 amountOut, address[] calldata tradePath, address router ) external returns (uint256) { @@ -35,28 +35,25 @@ contract UniV2TradeModule { // 4. Trade larger balance for smaller balance to achieve ratio ---- this will change the ratio // 5. Deposit to pool - address token0 = IUniswapV2Pair(asset).token0(); - address token1 = IUniswapV2Pair(asset).token1(); - - uint256 bal0 = IERC20(token0).balanceOf(msg.sender); - uint256 bal1 = IERC20(token1).balanceOf(msg.sender); - - (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) = IUniswapV2Pair(asset).getReserves(); - - // TODO how do i need to set the deadline? - IUniswapRouterV2(router).addLiquidity( - tokenA, - tokenB, - amountADesired, - amountBDesired, - amountAMin, - amountBMin, - msg.sender, - block.timestamp + 10 - ); - - uint256 len = tradePath.length; - IERC20(tradePath[len - 1]).transfer(msg.sender, amountsOut[len - 1]); - return amountsOut[len - 1]; + // address token0 = IUniswapV2Pair(asset).token0(); + // address token1 = IUniswapV2Pair(asset).token1(); + + // uint256 bal0 = IERC20(token0).balanceOf(msg.sender); + // uint256 bal1 = IERC20(token1).balanceOf(msg.sender); + + // (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) = IUniswapV2Pair(asset).getReserves(); + + // uint256 amountOut = IUniswapRouterV2(router).addLiquidity( + // token0, + // token1, + // amountADesired, + // amountBDesired, + // amountAMin, + // amountBMin, + // msg.sender, + // block.timestamp + // ); + // IERC20(asset).transfer(msg.sender, amountOut); + // return amountOut; } } diff --git a/packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol b/packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol index b668cdf2..75e04638 100644 --- a/packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol +++ b/packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol @@ -1,35 +1,51 @@ // SPDX-License-Identifier: GPL-3.0 // Docgen-SOLC: 0.8.15 - pragma solidity ^0.8.15; + import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; import { IUniswapV2Pair } from "../../../interfaces/external/uni/IUniswapV2Pair.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import { ITradeModule } from "./ITradeModule.sol"; contract SimplePool2Depositor { - function addLiquidity() internal { - uint256 nativeHalf = IERC20(native).balanceOf(address(this)).div(2); + function getAllocation( + address tradeModule, + uint256 allocation, + address[] calldata tradePath, + address router, + uint256 slippage + ) internal returns (uint256 amountOut, uint256 bal) { + amountOut = ITradeModule(tradeModule).getAmountOut(allocation, tradePath, router); + bal = ITradeModule(tradeModule).trade(allocation, (amountOut * (1e18 - slippage)) / 1e18, tradePath, router); + } + + function deposit( + address asset, + uint256 amountIn, + address[][2] calldata tradePaths, + address router, + address tradeModule, + uint256 slippage + ) external returns (uint256) { + address token0 = IUniswapV2Pair(asset).token0(); + address token1 = IUniswapV2Pair(asset).token1(); + uint256 allocation = amountIn / 2; - if (lpToken0 != native) { - uint256 amountOut0 = ITradeModule(tradeModule).getAmountOut(nativeHalf, nativeToLp0Route, router); - ITradeModule(tradeModule).trade(nativeHalf, (amountOut * (1e18 - slippage)) / 1e18, nativeToLp0Route, router); - } + (uint256 amountOut0, uint256 bal0) = getAllocation(tradeModule, allocation, tradePaths[0], router, slippage); - if (lpToken1 != native) { - uint256 amountOut1 = ITradeModule(tradeModule).getAmountOut(nativeHalf, nativeToLp1Route, router); - ITradeModule(tradeModule).trade(nativeHalf, (amountOut * (1e18 - slippage)) / 1e18, nativeToLp1Route, router); - } + (uint256 amountOut1, uint256 bal1) = getAllocation(tradeModule, allocation, tradePaths[1], router, slippage); - uint256 lp0Bal = IERC20(lpToken0).balanceOf(address(this)); - uint256 lp1Bal = IERC20(lpToken1).balanceOf(address(this)); - IUniswapRouterETH(unirouter).addLiquidity( - lpToken0, - lpToken1, - lp0Bal, - lp1Bal, - (amountOut * (1e18 - slippage)) / 1e18, - (amountOut * (1e18 - slippage)) / 1e18, - address(this), - now + (, , uint256 amountOut) = IUniswapRouterV2(router).addLiquidity( + token0, + token1, + bal0, + bal1, + (bal0 * (1e18 - 1e14)) / 1e18, + (bal1 * (1e18 - 1e14)) / 1e18, + msg.sender, + block.timestamp ); + IERC20(asset).transfer(msg.sender, amountOut); + return amountOut; } } diff --git a/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol b/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol index d2beda0d..3761b25f 100644 --- a/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol +++ b/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.15; import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; contract UniV2TradeModule { function getAmountOut( @@ -23,13 +24,12 @@ contract UniV2TradeModule { ) external returns (uint256) { IERC20(tradePath[0]).transferFrom(msg.sender, address(this), amountIn); - // TODO how do i need to set the deadline? uint256[] memory amountsOut = IUniswapRouterV2(router).swapExactTokensForTokens( - amount, + amountIn, amountOut, tradePath, msg.sender, - block.timestamp + 10 + block.timestamp ); uint256 len = tradePath.length; diff --git a/packages/hardhat/script/benGov/AddBeneficiariesToRegistry.s.sol b/packages/hardhat/script/benGov/AddBeneficiariesToRegistry.s.sol deleted file mode 100644 index db5a4293..00000000 --- a/packages/hardhat/script/benGov/AddBeneficiariesToRegistry.s.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { BeneficiaryRegistry } from "../../contracts/core/dao/BeneficiaryRegistry.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far - -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract AddBeneficiariesToRegistry is Script { - BeneficiaryRegistry internal beneficiaryRegistry = BeneficiaryRegistry(0x4d52E3Ff3B68E8824903e7a5743DD43586aEAF69); - - string[19] internal cids = [ - "QmSzq4gKUk9LhNNCFBVDbkkz26umqu63jx2dXNABBKcgJ9", - "QmNY4QEKhnreDZHW3yiphXn765im5y8RCGrySKzRoXDPTB", - "QmZn2p7xrRxqVGhKAtqjMZSY1Z6pW7A6RD7DiK2xt4nYTB", - "QmUW6peidPvHtbH4P7VNUpPdfaJqv7nmURbpgMcnzUbTtY", - "QmVAur87rJCyGh2ja1Pg4smAqD6MzXtuTXxWkeCkKYrf3n", - "QmNu5LeMyvMkP2sTBZhesXp352ihJUmGTPVeRp42Y9caXJ", - "QmQ3WQK41BozEznephMEh4cwGsRsFuXjFQRM6himtu5kHq", - "QmRnnDnHnF8uv1oour3oU9s1GmKNggzdEqMJueZViAvSAa", - "QmdoFZ6DL49SVmXDBv1zpkCngvr6bU7SUgxLbkh6ZM9L2V", - "QmchtjJsFDWNzLj93gPND1WhYUUGKeznNR6ATUeuaW71uq", - "QmYJ8KGfKo5iG8EbxZxvNoWiNuEMM4Hx5ok4paZ1F27G1w", - "QmTf7c3MaHsBR5rdT5CN3m3yZCuf1XkGxhV81GhJKxempA", - "QmfQ2Ffxw5P6NoKkQvkRoN31mAd65Dp2UBfPMdgzCDCDDx", - "QmVNQeMrLpvnizudP9kZquFSb28CmH9fHddEpdX7wyADxF", - "QmTiSvXRwdGFvPMPtKMurh4FJnzq3bZzws63UuErirSMFq", - "QmSLS4TKBM2M9tXv5CAdFqUQJ9wR8UjjyhtrUxtbRrXE9M", - "QmaisZuRUE1ndZGSzmvg4Uxr7nsi2ciQ1z9VoDCnpGRADt", - "Qmc4qtSHikeYvy4WScJWXtUeX2g3Qf6fC4xoJaU3fugQwu", - "QmcCmaXA3E8nzcBrTejHHjT8svKh6cSTwhKbPNuRyM6uH5" - ]; - - function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - - beneficiaryRegistry.addBeneficiary( - 0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199, - DEFAULT_REGION, - "QmcCmaXA3E8nzcBrTejHHjT8svKh6cSTwhKbPNuRyM6uH5" - ); - beneficiaryRegistry.addBeneficiary( - 0x2546BcD3c84621e976D8185a91A922aE77ECEc30, - DEFAULT_REGION, - "QmSLS4TKBM2M9tXv5CAdFqUQJ9wR8UjjyhtrUxtbRrXE9M" - ); - beneficiaryRegistry.addBeneficiary( - 0xbDA5747bFD65F08deb54cb465eB87D40e51B197E, - DEFAULT_REGION, - "QmaisZuRUE1ndZGSzmvg4Uxr7nsi2ciQ1z9VoDCnpGRADt" - ); - - // for (uint256 i = 9; i < 14; i++) { - // beneficiaryRegistry.addBeneficiary(vm.addr(i + 1), DEFAULT_REGION, cids[i]); - // } - vm.stopBroadcast(); - } -} - -contract RevokeBeneficiariesToRegistry is Script { - BeneficiaryRegistry internal beneficiaryRegistry = BeneficiaryRegistry(0xc9e244f134824fbd070858ab7A82B0e0B298cD5d); - - address[10] internal addresses = [ - 0x0000000000000000000000000000000000000000, - 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, - 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718, - 0xe1AB8145F7E55DC933d51a18c793F901A3A0b276, - 0xE57bFE9F44b819898F47BF37E5AF72a0783e1141, - 0xd41c057fd1c78805AAC12B0A94a405c0461A6FBb, - 0xF1F6619B38A98d6De0800F1DefC0a6399eB6d30C, - 0xF7Edc8FA1eCc32967F827C9043FcAe6ba73afA5c, - 0x4CCeBa2d7D2B4fdcE4304d3e09a1fea9fbEb1528, - 0x3DA8D322CB2435dA26E9C9fEE670f9fB7Fe74E49 - ]; - - address[3] internal addx = [ - 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, - 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF, - 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69 - ]; - - function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - - for (uint256 i; i < 3; i++) { - beneficiaryRegistry.revokeBeneficiary(addx[i]); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/AddNominationProposal.s.sol b/packages/hardhat/script/benGov/AddNominationProposal.s.sol deleted file mode 100644 index a52a1d2c..00000000 --- a/packages/hardhat/script/benGov/AddNominationProposal.s.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { BeneficiaryGovernance } from "../../contracts/core/dao/BeneficiaryGovernance.sol"; -import { ParticipationReward } from "../../contracts/core/utils/ParticipationReward.sol"; -import { ERC20PresetMinterPauser } from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract AddProposal is Script { - BeneficiaryGovernance internal beneficiaryGovernance = - BeneficiaryGovernance(0x1a46F07635d330883eAFdeA6946F95bd21cBf3Bb); // you must enter the new address from deployment here - ERC20PresetMinterPauser internal pop = ERC20PresetMinterPauser(0xc731FB77aFE6EfBE621c06b13478327c681aD334); // you must enter the new address from deployment here - ParticipationReward internal participationReward = ParticipationReward(0xE8d27AbeF7BD4aC929BCAAC050A443dA1cFb4d03); - string[19] internal cids = [ - "QmSzq4gKUk9LhNNCFBVDbkkz26umqu63jx2dXNABBKcgJ9", - "QmNY4QEKhnreDZHW3yiphXn765im5y8RCGrySKzRoXDPTB", - "QmZn2p7xrRxqVGhKAtqjMZSY1Z6pW7A6RD7DiK2xt4nYTB", - "QmUW6peidPvHtbH4P7VNUpPdfaJqv7nmURbpgMcnzUbTtY", - "QmVAur87rJCyGh2ja1Pg4smAqD6MzXtuTXxWkeCkKYrf3n", - "QmNu5LeMyvMkP2sTBZhesXp352ihJUmGTPVeRp42Y9caXJ", - "QmQ3WQK41BozEznephMEh4cwGsRsFuXjFQRM6himtu5kHq", - "QmRnnDnHnF8uv1oour3oU9s1GmKNggzdEqMJueZViAvSAa", - "QmdoFZ6DL49SVmXDBv1zpkCngvr6bU7SUgxLbkh6ZM9L2V", - "QmchtjJsFDWNzLj93gPND1WhYUUGKeznNR6ATUeuaW71uq", - "QmYJ8KGfKo5iG8EbxZxvNoWiNuEMM4Hx5ok4paZ1F27G1w", - "QmTf7c3MaHsBR5rdT5CN3m3yZCuf1XkGxhV81GhJKxempA", - "QmfQ2Ffxw5P6NoKkQvkRoN31mAd65Dp2UBfPMdgzCDCDDx", - "QmVNQeMrLpvnizudP9kZquFSb28CmH9fHddEpdX7wyADxF", - "QmTiSvXRwdGFvPMPtKMurh4FJnzq3bZzws63UuErirSMFq", - "QmSLS4TKBM2M9tXv5CAdFqUQJ9wR8UjjyhtrUxtbRrXE9M", - "QmaisZuRUE1ndZGSzmvg4Uxr7nsi2ciQ1z9VoDCnpGRADt", - "Qmc4qtSHikeYvy4WScJWXtUeX2g3Qf6fC4xoJaU3fugQwu", - "QmcCmaXA3E8nzcBrTejHHjT8svKh6cSTwhKbPNuRyM6uH5" - ]; - - function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - pop.mint(0xaD5459EBbA9110B0a77ab2c3A7C3F300bBc0bd04, 200000000000000000000000000000000); // put your address here - pop.approve(address(beneficiaryGovernance), 200000000000000000000000000000000); - for (uint256 i; i < 6; i++) { - beneficiaryGovernance.createProposal( - vm.addr(i + 1), - DEFAULT_REGION, - cids[i], - BeneficiaryGovernance.ProposalType.BeneficiaryNominationProposal - ); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/AddTakedownProposal.s.sol b/packages/hardhat/script/benGov/AddTakedownProposal.s.sol deleted file mode 100644 index 2d15260a..00000000 --- a/packages/hardhat/script/benGov/AddTakedownProposal.s.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { BeneficiaryGovernance } from "../../contracts/core/dao/BeneficiaryGovernance.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract AddProposal is Script { - BeneficiaryGovernance internal beneficiaryGovernance = - BeneficiaryGovernance(0xD8bE3e1D20C4620eCF7e5196056f9662C0e40E2B); - - string[19] internal cids = [ - "0x453686cb1d8c953ded406253aac5c37da2488f7c41c323cbe41be9d1e6936136", - "0x02ec2d6316d641dd3bcd5bc8b9c5e1693b4bb18b7b1e45fa237f4d3356d76c06", - "0xa9f111b356ed948342c3458b5422ce85831c32cec8a1ec5aafc6d7be29df2aa2", - "0x5b91dfed7bbf22163dc420883d9dea83245cb63322921ee9d0dd26f0580a4ab5", - "0x65831e033489d630441ba7c1a95206ed20914a9d70f03255a2e247a04c11df11", - "0x084e6f61ebc62f6f7830fd038567d3254eba8a50bf99ce12efc91ead08c76b99", - "0x19531b897392c00859b8fd8a6203646ac3b000a217f45806d23caac22e31a31c", - "0x3344a7be91be7304967d2657d3722ba25eddf3c0c87b546fe3b93b5eac05f127", - "0xe5afcdc556dec784675714313ccbdc6ded2d124559d2ee7c3882e0b2f3b7fac2", - "0xd574aad0b3ab7d4d57a32474bcf697904196a2aa1d1abca0ab4d9c459b3421d8", - "0x93ef576314edf05313724ab46fceb4ffec1a1187790813b5ba6917b3ed837912", - "0x4f054cc99fd70f608beeb51c93e2d7234990b643c77214a3b4f2a4bf8cbb0197", - "0xfd73c7c373e600eeab92276fb7da703dc0b98d70e003d260dd7491705647bcb7", - "0x6875113530fac1aaa15a8432826efeb8430d799ca3c20e1f7ded5ea93d333f5c", - "0x4fdfe2d6eb6a11d5fa67e89bf453c1e50aa9689d445803a1d42e55d93035eb84", - "0x3b6071fb19f07ac422472bb042b2827e5c715ad2a36f8b50e9b4df4299560178", - "0xb7fd8f6704c234984e2a630652a552e8100de0e8ffb06ad12b817851863b6dff", - "0xcbf6f690f0d6aab08193341217f157b32c5df7681fd9f8953a8448fee531b6c4", - "0xcdfebccf1e220187d76b7bd97b5e0b31ab76e54ad00126cd2936da0511cb92fc" - ]; - - function run() external { - vm.startBroadcast(); - for (uint256 i = 1; i < 2; i++) { - beneficiaryGovernance.createProposal( - vm.addr(i + 1), - DEFAULT_REGION, - cids[i], - BeneficiaryGovernance.ProposalType.BeneficiaryTakedownProposal - ); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/BeneficiaryGovernanceDeployment.s.sol b/packages/hardhat/script/benGov/BeneficiaryGovernanceDeployment.s.sol deleted file mode 100644 index 0de7afd8..00000000 --- a/packages/hardhat/script/benGov/BeneficiaryGovernanceDeployment.s.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { ERC20PresetMinterPauser } from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; -import { MockERC20PresetMinterPauser } from "../../contracts/mocks/MockERC20PresetMinterPauser.sol"; -import { ACLRegistry, IACLRegistry } from "../../contracts/core/utils/ACLRegistry.sol"; -import { ContractRegistry, IContractRegistry } from "../../contracts/core/utils/ContractRegistry.sol"; -import { KeeperIncentiveV2 } from "../../contracts/core/utils/KeeperIncentiveV2.sol"; -import { ParticipationReward } from "../../contracts/core/utils/ParticipationReward.sol"; -import { RewardsManager } from "../../contracts/core/utils/RewardsManager.sol"; -import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; - -import { GovStaking } from "../../contracts/core/dao/GovStaking.sol"; -import { BeneficiaryGovernance } from "../../contracts/core/dao/BeneficiaryGovernance.sol"; -import { BeneficiaryRegistry } from "../../contracts/core/dao/BeneficiaryRegistry.sol"; -import { GrantElections } from "../../contracts/core/dao/GrantElections.sol"; -import { BeneficiaryVaults } from "../../contracts/core/dao/BeneficiaryVaults.sol"; -import { Region } from "../../contracts/core/utils/Region.sol"; - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract DeployBenGov is Script { - address internal yourAddress = 0xaD5459EBbA9110B0a77ab2c3A7C3F300bBc0bd04; // enter your address here - ACLRegistry internal aclRegistry; - ContractRegistry internal contractRegistry; - MockERC20PresetMinterPauser internal pop; - KeeperIncentiveV2 internal keeperIncentive; - ParticipationReward internal participationReward; - GovStaking internal staking; - RewardsManager internal rewardsManager; - BeneficiaryGovernance internal beneficiaryGovernance; - BeneficiaryRegistry internal beneficiaryRegistry; - GrantElections internal grantElections; - BeneficiaryVaults internal beneficiaryVault; - Region internal region; - - function run() external returns (address) { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - // Deploying core contracts - aclRegistry = new ACLRegistry(); - aclRegistry.grantRole(keccak256("DAO"), yourAddress); // replace with your signer - aclRegistry.grantRole(keccak256("INCENTIVE_MANAGER_ROLE"), yourAddress); // replace with your signer - aclRegistry.grantRole(keccak256("BeneficiaryGovernance"), yourAddress); // replace with your signer - - contractRegistry = new ContractRegistry(IACLRegistry(address(aclRegistry))); - - pop = new MockERC20PresetMinterPauser("Popcorn", "POP"); - contractRegistry.addContract(keccak256("POP"), address(pop), "1"); - - keeperIncentive = new KeeperIncentiveV2(IContractRegistry(contractRegistry), 0, 0); - contractRegistry.addContract(keccak256("KeeperIncentive"), address(keeperIncentive), "1"); - - // Deploying Additional util contracts - participationReward = new ParticipationReward(IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("ParticipationReward"), address(participationReward), "1"); - - rewardsManager = new RewardsManager( - IContractRegistry(contractRegistry), - IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D) - ); - contractRegistry.addContract(keccak256("RewardsManager"), address(rewardsManager), "1"); - aclRegistry.grantRole(keccak256("RewardsManager"), address(rewardsManager)); - keeperIncentive.createIncentive(address(rewardsManager), 0, true, true, address(pop), 1, 0); - keeperIncentive.createIncentive(address(rewardsManager), 0, true, true, address(pop), 1, 0); - - GovStaking staking = new GovStaking(IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("GovStaking"), address(staking), "1"); - - // Deploying Beneficiary Governance contracts - beneficiaryGovernance = new BeneficiaryGovernance(IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("BeneficiaryGovernance"), address(beneficiaryGovernance), "1"); - aclRegistry.grantRole(keccak256("BeneficiaryGovernance"), address(beneficiaryGovernance)); - participationReward.addControllerContract(keccak256("BeneficiaryGovernance"), address(beneficiaryGovernance)); - - beneficiaryRegistry = new BeneficiaryRegistry(IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("BeneficiaryRegistry"), address(beneficiaryRegistry), "1"); - - grantElections = new GrantElections(IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("GrantElections"), address(grantElections), "1"); - aclRegistry.grantRole(keccak256("BeneficiaryGovernance"), address(grantElections)); - participationReward.addControllerContract("GrantElections", address(grantElections)); - - beneficiaryVault = new BeneficiaryVaults(IContractRegistry(contractRegistry)); - - region = new Region(address(beneficiaryVault), IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("Region"), address(region), "1"); - - vm.stopBroadcast(); - return address(pop); - } -} diff --git a/packages/hardhat/script/benGov/CreateFinalizedYearlyElection.s.sol b/packages/hardhat/script/benGov/CreateFinalizedYearlyElection.s.sol deleted file mode 100644 index f930b2cb..00000000 --- a/packages/hardhat/script/benGov/CreateFinalizedYearlyElection.s.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { GrantElections } from "../../contracts/core/dao/GrantElections.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World -uint256 constant DAYS = 60 * 60 * 24; - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract CreateElection is Script { - GrantElections internal grantElection = GrantElections(0x3df8bB3119f37eb93921e1afFe8872bC95a5b013); - - GrantElections.ElectionTerm ELECTION_TERM = GrantElections.ElectionTerm.Yearly; - address[] votedBeneficiaries; - uint256[] votes; - - function run() external { - vm.startBroadcast(); - // TODO set config in coordination with Fan correct for testing - grantElection.setConfiguration( - ELECTION_TERM, - 2, // ranking cutoff - 5, // awardees cutoff - false, // VRF - 10 * 60, // Registration period - default - 10 * 60, // Voting period - default - 1 * DAYS, // cooldown period - default - 0, // bondAmount - false, // bond required - 0, // finalization incentive - true, // enabled - GrantElections.ShareType.EqualWeight - ); - grantElection.initialize(ELECTION_TERM, DEFAULT_REGION); - - for (uint256 i = 2; i < 7; i++) { - votedBeneficiaries.push(vm.addr(i + 1)); - votes.push(10 ether); - } - for (uint256 i = 2; i < 12; i++) { - grantElection.registerForElection(vm.addr(i + 1), uint256(ELECTION_TERM)); - grantElection.vote(votedBeneficiaries, votes, uint256(ELECTION_TERM)); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/CreateOpenQuarterlyElection.s.sol b/packages/hardhat/script/benGov/CreateOpenQuarterlyElection.s.sol deleted file mode 100644 index d2785c66..00000000 --- a/packages/hardhat/script/benGov/CreateOpenQuarterlyElection.s.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { GrantElections } from "../../contracts/core/dao/GrantElections.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World -uint256 constant DAYS = 60 * 60 * 24; - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract CreateElection is Script { - GrantElections internal grantElection = GrantElections(0xF8C417F7a4593a561970358375Fc4b2F6F890E9c); - - GrantElections.ElectionTerm ELECTION_TERM = GrantElections.ElectionTerm.Quarterly; - address[] votedBeneficiaries; - uint256[] votes; - - function run() external { - vm.startBroadcast(); - // TODO set config in coordination with Fan correct for testing - grantElection.setConfiguration( - ELECTION_TERM, - 2, // ranking cutoff - 5, // awardees cutoff - false, // VRF - 30 * 60, // Registration period - default - 1 * DAYS, // Voting period - default - 15, // cooldown period - default - 0, // bondAmount - false, // bond required - 0, // finalization incentive - true, // enabled - GrantElections.ShareType.EqualWeight - ); - grantElection.initialize(ELECTION_TERM, DEFAULT_REGION); - - for (uint256 i = 2; i < 7; i++) { - votedBeneficiaries.push(vm.addr(i + 1)); - votes.push(10 ether); - } - for (uint256 i = 2; i < 12; i++) { - grantElection.registerForElection(vm.addr(i + 1), uint256(ELECTION_TERM)); - grantElection.vote(votedBeneficiaries, votes, uint256(ELECTION_TERM)); - } - - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/CreateRegistrationMonthlyElection.s.sol b/packages/hardhat/script/benGov/CreateRegistrationMonthlyElection.s.sol deleted file mode 100644 index 7c9d51d0..00000000 --- a/packages/hardhat/script/benGov/CreateRegistrationMonthlyElection.s.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { GrantElections } from "../../contracts/core/dao/GrantElections.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World -uint256 constant DAYS = 60 * 60 * 24; - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract CreateElection is Script { - GrantElections internal grantElection = GrantElections(0xF8C417F7a4593a561970358375Fc4b2F6F890E9c); - GrantElections.ElectionTerm ELECTION_TERM = GrantElections.ElectionTerm.Monthly; - - function run() external { - vm.startBroadcast(); - // TODO set config in coordination with Fan correct for testing - grantElection.setConfiguration( - ELECTION_TERM, - 2, // ranking cutoff - 5, // awardees cutoff - false, // VRF - 1 * DAYS, // Registration period - default - 15, // Voting period - default - 15, // cooldown period - default - 0, // bondAmount - false, // bond required - 0, // finalization incentive - true, // enabled - GrantElections.ShareType.EqualWeight - ); - grantElection.initialize(ELECTION_TERM, DEFAULT_REGION); - for (uint256 i = 2; i < 11; i++) { - grantElection.registerForElection(vm.addr(i + 1), uint256(ELECTION_TERM)); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/RemoveBeneficiariesFromRegistry.s.sol b/packages/hardhat/script/benGov/RemoveBeneficiariesFromRegistry.s.sol deleted file mode 100644 index dbfaa3a2..00000000 --- a/packages/hardhat/script/benGov/RemoveBeneficiariesFromRegistry.s.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { BeneficiaryRegistry } from "../../contracts/core/dao/BeneficiaryRegistry.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract RemoveBeneficiariesFromRegistry is Script { - BeneficiaryRegistry internal beneficiaryRegistry = BeneficiaryRegistry(0x408Ad169a738E5c4E9dBd2847bc0fD1FF9A8AcBe); - - function run() external { - vm.startBroadcast(); - for (uint256 i; i < 10; i++) { - beneficiaryRegistry.revokeBeneficiary(vm.addr(i + 1)); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/readme.MD b/packages/hardhat/script/benGov/readme.MD deleted file mode 100644 index eb65b7c6..00000000 --- a/packages/hardhat/script/benGov/readme.MD +++ /dev/null @@ -1,15 +0,0 @@ -Deployment script for Goerli: - -(Note, you will have to update the addresses on these scripts before you run them) - -forge script ./script/benGov/BeneficiaryGovernanceDeployment.s.sol:DeployBenGov -vvv --rpc-url --broadcast --verify - - - -Adding nomination proposals and eligible beneficiaries: - -forge script ./script/benGov/AddNominationProposal.s.sol:AddProposal --rpc-url --chain-id 5 --sender --broadcast - - -forge script ./script/benGov/AddBeneficiariesToRegistry.s.sol:AddBeneficiariesToRegistry --rpc-url --chain-id 5 --sender --broadcast - diff --git a/packages/hardhat/test/forge/batchSlippageSimulation/AbstractSimulateBatchSlippage.t.sol b/packages/hardhat/test/forge/batchSlippageSimulation/AbstractSimulateBatchSlippage.t.sol deleted file mode 100644 index 6ed62db4..00000000 --- a/packages/hardhat/test/forge/batchSlippageSimulation/AbstractSimulateBatchSlippage.t.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import { Test } from "forge-std/Test.sol"; -import "solmate/src/tokens/ERC20.sol"; - -import "../../../contracts/core/interfaces/IBatchContract.sol"; -import "../../../contracts/externals/interfaces/IBasicIssuanceModule.sol"; -import "../../../contracts/externals/interfaces/ISetToken.sol"; - -//TODO butter needs to be updated so that `setSlippage()` doesnt block any inputs above 2% -abstract contract AbstractSimulateBatchSlippage is Test { - address public constant DAO = 0x92a1cB552d0e177f3A135B4c87A4160C8f2a485f; - IBasicIssuanceModule public basicIssuanceModule = IBasicIssuanceModule(0xd8EF3cACe8b4907117a45B0b125c68560532F94D); - IBatchContract public batchContract; - - // Override with the address of the used batchContract - function setUp() public virtual { - //batchContract = IBatchContract(0x42189f909e1EFA409A4509070dDBc31A592422A8); - } - - function test__simulateMintSlippage() external { - _runSimulation(true); - } - - function test__simulateRedeemSlippage() external { - _runSimulation(false); - } - - function _runSimulation(bool isMint) internal { - bytes32 batchId; - Batch memory currentBatch; - uint256 sourceBatchValue; - uint256 targetBatchValue; - uint256 loss; - - vm.startPrank(DAO); - batchContract.setSlippage(10000, 10000); - - emit log_named_uint("blockNumber", block.number); - emit log_named_uint("blockTime", block.timestamp); - - if (isMint) { - batchId = batchContract.currentMintBatchId(); - currentBatch = batchContract.getBatch(batchId); - - sourceBatchValue = _getUnderlyingBatchValue(address(currentBatch.sourceToken), currentBatch.sourceTokenBalance); - emit log_named_uint("sourceBatchValue", sourceBatchValue); - - batchContract.batchMint(); - - currentBatch = batchContract.getBatch(batchId); - targetBatchValue = _getSetBatchValue(address(currentBatch.targetToken), currentBatch.targetTokenBalance); - emit log_named_uint("targetBatchValue", targetBatchValue); - } else { - batchId = batchContract.currentRedeemBatchId(); - currentBatch = batchContract.getBatch(batchId); - - sourceBatchValue = _getSetBatchValue(address(currentBatch.sourceToken), currentBatch.sourceTokenBalance); - emit log_named_uint("sourceBatchValue", sourceBatchValue); - - batchContract.batchRedeem(); - - currentBatch = batchContract.getBatch(batchId); - targetBatchValue = _getUnderlyingBatchValue(address(currentBatch.targetToken), currentBatch.targetTokenBalance); - emit log_named_uint("targetBatchValue", targetBatchValue); - } - loss = sourceBatchValue - targetBatchValue; - - emit log_named_uint("loss", loss); - - // Divide by 1e18 to get proper slippage - emit log_named_uint("slippage", (loss * 1e18) / sourceBatchValue); - - vm.stopPrank(); - } - - // Override with price calculation depending on what kind of batchContract is used - function _getUnderlyingBatchValue(address underlyingToken, uint256 batchBalance) - internal - view - virtual - returns (uint256 value) - { - // raise balances that are denominated in anything less than 18 decimals - // uint256 decimalDiff = 18 - ERC20(underlyingToken).decimals(); - // value = decimalDiff == 0 ? batchBalance : batchBalance * (10**decimalDiff); - } - - // previous batchContract.currentMintBatchId - function _getSetBatchValue(address setToken, uint256 batchBalance) internal view returns (uint256 value) { - (address[] memory tokenAddresses, uint256[] memory quantities) = basicIssuanceModule - .getRequiredComponentUnitsForIssue(ISetToken(setToken), 1e18); - - value = (batchBalance * batchContract.valueOfComponents(tokenAddresses, quantities)) / 1e18; - } -} diff --git a/packages/hardhat/test/forge/batchSlippageSimulation/SimulateThreeXBatchSlippage.t.sol b/packages/hardhat/test/forge/batchSlippageSimulation/SimulateThreeXBatchSlippage.t.sol deleted file mode 100644 index 3e5d4c7b..00000000 --- a/packages/hardhat/test/forge/batchSlippageSimulation/SimulateThreeXBatchSlippage.t.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import "solmate/src/tokens/ERC20.sol"; -import "./AbstractSimulateBatchSlippage.t.sol"; - -contract SimulateThreeXBatchSlippage is AbstractSimulateBatchSlippage { - function setUp() public override { - batchContract = IBatchContract(0x42189f909e1EFA409A4509070dDBc31A592422A8); - } - - function _getUnderlyingBatchValue(address underlyingToken, uint256 batchBalance) - internal - view - override - returns (uint256 value) - { - // raise balances that are denominated in anything less than 18 decimals - uint256 decimalDiff = 18 - ERC20(underlyingToken).decimals(); - value = decimalDiff == 0 ? batchBalance : batchBalance * (10**decimalDiff); - } -} diff --git a/packages/hardhat/test/forge/fork/ButterFeeConverter.t.sol b/packages/hardhat/test/forge/fork/ButterFeeConverter.t.sol deleted file mode 100644 index 9aa74a85..00000000 --- a/packages/hardhat/test/forge/fork/ButterFeeConverter.t.sol +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import { Test } from "forge-std/Test.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; - -import "../../../contracts/core/defi/butter/ButterFeeConverter.sol"; -import "../../../contracts/core/utils/RewardsManager.sol"; -import "../../../contracts/core/utils/KeeperIncentiveV2.sol"; -import "../../../contracts/core/interfaces/IACLRegistry.sol"; -import "../../../contracts/core/interfaces/IContractRegistry.sol"; -import "../../../contracts/core/interfaces/IButterBatchProcessing.sol"; - -import "../../../contracts/externals/interfaces/Curve3Pool.sol"; -import "../../../contracts/externals/interfaces/ISetToken.sol"; -import "../../../contracts/externals/interfaces/IStreamingFeeModule.sol"; - -// @dev fork block 15008113 -contract ButterFeeConverterTest is Test { - ISetToken internal butter; - IStreamingFeeModule internal streamingFeeModule; - Curve3Pool internal threePool; - IACLRegistry internal aclRegistry; - IContractRegistry internal contractRegistry; - IERC20Metadata internal threeCrv; - ButterFeeConverter internal converter; - IButterBatchProcessing internal butterBatchProcessing; - KeeperIncentiveV2 internal keeperIncentive; - - IERC20Metadata internal preferredStable; - - address constant rewardsManager = address(42); - - address constant BTR_TOKEN = address(0x109d2034e97eC88f50BEeBC778b5A5650F98c124); - address constant SET_STREAMING_FEE_MODULE = address(0x08f866c74205617B6F3903EF481798EcED10cDEC); - address constant THREE_POOL = address(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7); - address constant THREE_CRV = address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); - address constant UNISWAP_ROUTER = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); - - address constant ACL_REGISTRY = address(0x8A41aAa4B467ea545DDDc5759cE3D35984F093f4); - address constant CONTRACT_REGISTRY = address(0x85831b53AFb86889c20aF38e654d871D8b0B7eC3); - - address constant ADMIN = address(0x92a1cB552d0e177f3A135B4c87A4160C8f2a485f); - - event FeeTransferredToRewardsManager( - address indexed keeper, - int128 preferredStableCoinIndex, - uint256 threeCrvAmount, - uint256 keeperTip, - uint256 stableCoinAmount - ); - event PreferredStableCoinIndexUpdated(int128 oldIndex, int128 newIndex); - event MaxSlippageUpdated(uint256 oldSlippage, uint256 newSlippage); - event KeeperTipUpdated(uint256 oldKeeperTip, uint256 newKeeperTip); - - function setUp() public { - uint256 forkId = vm.createSelectFork(vm.rpcUrl("FORKING_RPC_URL"), 15008113); - vm.selectFork(forkId); - - butter = ISetToken(BTR_TOKEN); - streamingFeeModule = IStreamingFeeModule(SET_STREAMING_FEE_MODULE); - aclRegistry = IACLRegistry(ACL_REGISTRY); - contractRegistry = IContractRegistry(CONTRACT_REGISTRY); - threePool = Curve3Pool(THREE_POOL); - threeCrv = IERC20Metadata(THREE_CRV); - butterBatchProcessing = IButterBatchProcessing(contractRegistry.getContract(keccak256("ButterBatchProcessing"))); - - converter = new ButterFeeConverter( - butter, - streamingFeeModule, - contractRegistry, - threePool, - threeCrv, - int128(1), - 250 - ); - keeperIncentive = new KeeperIncentiveV2(contractRegistry, 0, 0); - - preferredStable = IERC20Metadata(threePool.coins(1)); - - vm.startPrank(ADMIN); - - aclRegistry.grantRole(keccak256("ApprovedContract"), address(converter)); - aclRegistry.grantRole(keccak256("INCENTIVE_MANAGER_ROLE"), address(ADMIN)); - - streamingFeeModule.updateFeeRecipient(butter, address(converter)); - butterBatchProcessing.setRedemptionFee(10, address(converter)); - - contractRegistry.addContract(keccak256("RewardsManager"), rewardsManager, keccak256("1")); - contractRegistry.updateContract(keccak256("KeeperIncentive"), address(keeperIncentive), keccak256("2")); - - keeperIncentive.createIncentive(address(converter), 0, true, true, address(threeCrv), 1, 0); - - vm.stopPrank(); - - // Note: This warp seems to hugely increase the gas costs of the Butter batch redeem step... - vm.warp(block.timestamp + 365 * 1 days); - } - - function test_has_contract_name() public { - assertEq(converter.contractName(), keccak256("ButterFeeConverter")); - } - - function test_has_streaming_fee_module() public { - assertEq(address(converter.streamingFeeModule()), address(streamingFeeModule)); - } - - function test_has_btr_token() public { - assertEq(address(converter.butter()), address(butter)); - } - - function test_has_three_pool() public { - assertEq(address(converter.threePool()), address(threePool)); - } - - function test_has_three_crv() public { - assertEq(address(converter.threeCRV()), address(threeCrv)); - } - - function test_has_preferred_coin() public { - assertEq(converter.preferredStableCoinIndex(), 1); - } - - function test_set_preferred_coin() public { - assertEq(converter.preferredStableCoinIndex(), 1); - - vm.expectEmit(false, false, false, true, address(converter)); - emit PreferredStableCoinIndexUpdated(1, 0); - - vm.prank(ADMIN); - converter.setPreferredStableCoinIndex(int128(0)); - - assertEq(converter.preferredStableCoinIndex(), 0); - - vm.expectEmit(false, false, false, true, address(converter)); - emit PreferredStableCoinIndexUpdated(0, 2); - - vm.prank(ADMIN); - converter.setPreferredStableCoinIndex(int128(2)); - - assertEq(converter.preferredStableCoinIndex(), 2); - } - - function test_reverts_on_invalid_index() public { - vm.prank(ADMIN); - vm.expectRevert("Invalid index"); - converter.setPreferredStableCoinIndex(-1); - - vm.prank(ADMIN); - vm.expectRevert("Invalid index"); - converter.setPreferredStableCoinIndex(3); - } - - function test_set_preferred_coin_reverts_on_invalid_role() public { - vm.expectRevert("you dont have the right role"); - converter.setPreferredStableCoinIndex(2); - } - - function test_set_max_slippage() public { - assertEq(converter.maxSlippage(), 50); - - vm.expectEmit(false, false, false, true, address(converter)); - emit MaxSlippageUpdated(50, 1000); - - vm.prank(ADMIN); - converter.setMaxSlippage(1000); - - assertEq(converter.maxSlippage(), 1000); - } - - function test_reverts_on_invalid_slippage() public { - vm.prank(ADMIN); - vm.expectRevert("Invalid slippage"); - converter.setMaxSlippage(10001); - } - - function test_set_max_slippage_reverts_on_invalid_role() public { - vm.expectRevert("you dont have the right role"); - converter.setMaxSlippage(1000); - } - - function test_set_keeper_tip() public { - assertEq(converter.keeperTip(), 250); - - vm.expectEmit(false, false, false, true, address(converter)); - emit KeeperTipUpdated(250, 500); - - vm.prank(ADMIN); - converter.setKeeperTip(500); - - assertEq(converter.keeperTip(), 500); - } - - function test_reverts_on_invalid_tip() public { - vm.prank(ADMIN); - vm.expectRevert("Invalid tip"); - converter.setKeeperTip(10001); - } - - function test_set_keeper_reverts_on_invalid_role() public { - vm.expectRevert("you dont have the right role"); - converter.setKeeperTip(1000); - } - - function test_accrue_fee_usdc() public { - assertEq(preferredStable.balanceOf(address(rewardsManager)), 0); - - vm.expectEmit(true, false, false, true, address(converter)); - emit FeeTransferredToRewardsManager(address(this), 1, 11723638014446496341294, 299212623, 11669292332); - - converter.accrueFee(); - - assertEq(preferredStable.balanceOf(address(rewardsManager)), 11669292332); - } - - function test_accrue_fee_dai() public { - vm.prank(ADMIN); - converter.setPreferredStableCoinIndex(int128(0)); - preferredStable = IERC20Metadata(threePool.coins(0)); - - assertEq(preferredStable.balanceOf(address(rewardsManager)), 0); - - vm.expectEmit(true, false, false, true, address(converter)); - emit FeeTransferredToRewardsManager( - address(this), - 0, - 11723638014446496341294, - 299208332027310994108, - 11669124949065128770215 - ); - - converter.accrueFee(); - - assertEq(preferredStable.balanceOf(address(rewardsManager)), 11669124949065128770215); - } - - function test_accrue_fee_usdt() public { - assertFalse(keeperIncentive.hasClaimableBalance(address(this))); - - vm.prank(ADMIN); - converter.setPreferredStableCoinIndex(int128(2)); - preferredStable = IERC20Metadata(threePool.coins(2)); - - assertEq(preferredStable.balanceOf(address(rewardsManager)), 0); - - vm.expectEmit(true, false, false, true, address(converter)); - emit FeeTransferredToRewardsManager(address(this), 2, 11723638014446496341294, 299590511, 11684029951); - - converter.accrueFee(); - - assertTrue(keeperIncentive.hasClaimableBalance(address(this))); - assertEq(preferredStable.balanceOf(address(rewardsManager)), 11684029951); - } - - function test_scale_value() public { - assertEq(converter.scaleValue(1e36, 18), 1e18); - assertEq(converter.scaleValue(1e36, 6), 1e6); - } -} diff --git a/packages/hardhat/test/forge/fork/FeeEscrow.t.sol b/packages/hardhat/test/forge/fork/FeeEscrow.t.sol deleted file mode 100644 index 74b64bf2..00000000 --- a/packages/hardhat/test/forge/fork/FeeEscrow.t.sol +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; - -import "../../../contracts/core/utils/FeeEscrow.sol"; -import "../../../contracts/mocks/MockERC20.sol"; - -// forge test --fork-url $FORKING_RPC_URL --match-contract FeeEscrowTest - -contract FeeEscrowTest is Test { - /* FeeEscrow events */ - event BurnerAddressAdded(address indexed feeToken, address indexed burnerAddress); - event BurnerAddressRemoved(address indexed feeToken); - event BurnerAddressChanged(address indexed feeToken, address indexed burnerAddress); - event FeesBurned(address indexed feeToken, uint256 burnAmount); - - FeeEscrow public feeEscrow; - MockERC20 public feeToken; - address public burnerAddress = address(1234); - address public burnerAddress2 = address(5678); - - function setUp() public { - feeEscrow = new FeeEscrow(); - feeToken = new MockERC20("FeeToken", "FT", 18); - - vm.label(address(feeEscrow), "FeeEscrow"); - vm.label(address(this), "FeeEscrowTest"); - vm.label(address(feeToken), "FeeToken"); - vm.label(burnerAddress, "BurnerAddress"); - vm.label(burnerAddress2, "BurnerAddress2"); - - feeToken.mint(address(this), 1e18); - } - - /* ========== MUTATIVE FUNCTIONS TESTS ========== */ - - /* Adding a burner address to FeeEscrow */ - - function test__addBurnerAddressNoFeeTokenReverts() public { - vm.expectRevert("need to specify fee token"); - feeEscrow.addBurnerAddress(address(0), burnerAddress); - } - - function test__addBurnerAddressNoBurnerAddressReverts() public { - vm.expectRevert("need to specify burner address"); - feeEscrow.addBurnerAddress(address(feeToken), address(0)); - } - - function test__addBurnerAddress() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - address feeEscrowBurnerAddress = feeEscrow.getBurnerAddress(address(feeToken)); - assertEq(feeEscrowBurnerAddress, burnerAddress); - assertEq(feeEscrowBurnerAddress, address(1234)); - assertEq(feeEscrow.feeTokenToBurnerAddress(address(feeToken)), burnerAddress); - } - - function test__addBurnerAddressEvent() public { - vm.expectEmit(true, true, false, true, address(feeEscrow)); - emit BurnerAddressAdded(address(feeToken), burnerAddress); - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - } - - /* Removing a burner address from FeeEscrow */ - - function test__removeBurnerAddressNoFeeTokenReverts() public { - vm.expectRevert("need to specify fee token"); - feeEscrow.removeBurnerAddress(address(0)); - } - - function test__removeBurnerAddress() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - feeEscrow.removeBurnerAddress(address(feeToken)); - vm.expectRevert("burner address not registered"); - feeEscrow.getBurnerAddress(address(feeToken)); - assertEq(feeEscrow.feeTokenToBurnerAddress(address(feeToken)), address(0)); - } - - function test__removeBurnerAddressEvent() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - vm.expectEmit(true, false, false, true, address(feeEscrow)); - emit BurnerAddressRemoved(address(feeToken)); - feeEscrow.removeBurnerAddress(address(feeToken)); - } - - /* Changing a burner address in FeeEscrow */ - - function test__changeBurnerAddressNoFeeTokenReverts() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - vm.expectRevert("need to specify fee token"); - feeEscrow.changeBurnerAddress(address(0), burnerAddress2); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - } - - function test__changeBurnerAddressNoBurnerAddressReverts() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - vm.expectRevert("need to specify burner address"); - feeEscrow.changeBurnerAddress(address(feeToken), address(0)); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - } - - function test__changeBurnerAddressNoBurnerAddressRegisteredReverts() public { - vm.expectRevert("no burner address registered"); - feeEscrow.changeBurnerAddress(address(feeToken), burnerAddress2); - assertEq(feeEscrow.feeTokenToBurnerAddress(address(feeToken)), address(0)); - } - - function test__changeBurnerAddress() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - feeEscrow.changeBurnerAddress(address(feeToken), burnerAddress2); - assertTrue(feeEscrow.getBurnerAddress(address(feeToken)) != burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress2); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), address(5678)); - assertEq(feeEscrow.feeTokenToBurnerAddress(address(feeToken)), burnerAddress2); - } - - function test__changeBurnerAddressEvent() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - vm.expectEmit(true, true, false, true, address(feeEscrow)); - emit BurnerAddressChanged(address(feeToken), burnerAddress2); - feeEscrow.changeBurnerAddress(address(feeToken), burnerAddress2); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress2); - } - - /* Transferring fees to burner address from FeeEscrow */ - - function test__transferFeesNoBurnerAddressRegisteredReverts() public { - vm.expectRevert("no burner address registered"); - feeEscrow.transferFees(address(feeToken), 1e18); - } - - function test__transferFeesNoBurnAmountReverts() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - vm.expectRevert("burn amount must be > 0"); - feeEscrow.transferFees(address(feeToken), 0); - } - - function test__transferFees() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - uint256 burnAmount = 1e18; - feeToken.approve(address(feeEscrow), burnAmount); - feeEscrow.transferFees(address(feeToken), burnAmount); - assertEq(feeToken.balanceOf(burnerAddress), burnAmount); - assertEq(feeToken.balanceOf(address(this)), 0); - } - - function test__transferFeesEvent() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - uint256 burnAmount = 1e18; - feeToken.approve(address(feeEscrow), burnAmount); - vm.expectEmit(true, false, false, true, address(feeEscrow)); - emit FeesBurned(address(feeToken), burnAmount); - feeEscrow.transferFees(address(feeToken), burnAmount); - assertEq(feeToken.balanceOf(burnerAddress), burnAmount); - } -} From 9905fe62a562357af458f01c5b02d9e3b35881c8 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Mon, 20 Feb 2023 21:32:34 -0800 Subject: [PATCH 06/21] simple generic compounder draft --- .../src/vault/strategy/SimpleCompounder.sol | 65 ++++++++ .../src/vault/strategy/StrategyBase2.sol | 152 ++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 packages/foundry/src/vault/strategy/SimpleCompounder.sol create mode 100644 packages/foundry/src/vault/strategy/StrategyBase2.sol diff --git a/packages/foundry/src/vault/strategy/SimpleCompounder.sol b/packages/foundry/src/vault/strategy/SimpleCompounder.sol new file mode 100644 index 00000000..a77b1734 --- /dev/null +++ b/packages/foundry/src/vault/strategy/SimpleCompounder.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { StrategyBase } from "./StrategyBase2.sol"; + +contract SimpleCompounder is StrategyBase { + using SafeERC20 for IERC20; + using Math for uint256; + + constructor( + address _native, + address _lpPair, + address _vault, + address _strategist, + address[] memory _nativeToLp0Route, + address[] memory _nativeToLpRoute + ) public { + native = _native; + lpPair = _lpPair; + vault = _vault; + strategist = _strategist; + + _setUp(); + } + + // Setup for routes and allowances in constructor. + function _setUp() internal override { + // Lp routing checks + lpToken0 = IUniswapV2Pair(lpPair).token0(); + if (_nativeToLp0Route[0] != native) revert InvalidRoute(); + if (_nativeToLp0Route[_nativeToLp0Route.length - 1] != lpToken0) revert InvalidRoute(); + nativeToLp0Route = _nativeToLp0Route; + + lpToken1 = IUniswapV2Pair(lpPair).token0(); + if (_nativeToLp1Route[0] != native) revert InvalidRoute(); + if (_nativeToLp1Route[_nativeToLp1Route.length - 1] != lpToken1) revert InvalidRoute(); + nativeToLp1Route = _nativeToLp1Route; + + _giveInitialAllowances(); + } + + // Give allowances for rewardToken swaps. + function _giveAllowances() internal override { + IERC20(want).safeApprove(chef, type(uint256).max); + IERC20(native).safeApprove(router, type(uint256).max); + IERC20(lpToken0).safeApprove(router, type(uint256).max); + IERC20(lpToken1).safeApprove(router, type(uint256).max); + } + + // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. + function _compound() internal virtual {} + + // Return available rewards for all rewardTokens. + function rewardsAvailable() public view virtual returns (uint256[] memory) {} + + // Calculate the total underlaying 'want' held by the strat. + function balanceOf() public view virtual returns (uint256) {} + + // Calculates how much 'want' this contract holds. + function balanceOfWant() public view virtual returns (uint256) {} + + // Calculates how much 'want' the strategy has working in the farm. + function balanceOfPool() public view virtual returns (uint256) {} +} diff --git a/packages/foundry/src/vault/strategy/StrategyBase2.sol b/packages/foundry/src/vault/strategy/StrategyBase2.sol new file mode 100644 index 00000000..83788d7c --- /dev/null +++ b/packages/foundry/src/vault/strategy/StrategyBase2.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../interfaces/vault/IWithRewards.sol"; +import { IEIP165 } from "../../interfaces/IEIP165.sol"; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import { IUniswapRouterV2 } from "../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../interfaces/vault/IWithRewards.sol"; + +contract StrategyBase { + // Tokens used + address public immutable native; + address public lpPair; + address public lpToken0; + address public lpToken1; + + // Protocol contracts info (masterchefs, poolIds, etc.) + address[] public protocolAddresses; + uint256[] public protocolUints; + + // Rewards and Routes + address public router; + /** + * @dev rewardToken index must match respective path index. + * @dev Paths follow this pattern: [rewardToken, ...hops, native] + */ + address[] public rewardTokens; + address[][] public rewardRoutes; + address[] public nativeToLp0Route; + address[] public nativeToLp1Route; + + // Data management + uint256 public lastHarvest; + + // Events + event Harvest(); + + // Errors + error InvalidRoute(); + error FunctionNotImplemented(bytes4 sig); + + function verifyAdapterSelectorCompatibility(bytes4[8] memory sigs) public { + uint8 len = uint8(sigs.length); + for (uint8 i; i < len; i++) { + if (sigs[i].length == 0) return; + if (!IEIP165(address(this)).supportsInterface(sigs[i])) revert FunctionNotImplemented(sigs[i]); + } + } + + function verifyAdapterCompatibility(bytes memory data) public virtual {} + + /*////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////*/ + + // Setup for routes and allowances in constructor. + function _setUp() internal virtual {} + + // Give allowances necessary for deposit, withdraw, lpToken swaps, and addLiquidity. + function _giveAllowances() internal virtual {} + + // Give allowances for rewardToken swaps. + function giveRewardAllowances() public { + uint256 len = rewardTokens.length; + for (uint256 i; i < len; ++i) { + IERC20(rewardTokens[i]).safeApprove(router, type(uint256).max); + } + } + + // Give initial allowances for setup. + function _giveInitialAllowances() internal { + _giveAllowances(); + giveRewardAllowances(); + } + + /*////////////////////////////////////////////////////////////// + HARVEST LOGIC + //////////////////////////////////////////////////////////////*/ + + // harvest rewards. + function harvest() public virtual { + if (_rewardsCheck() == true && _rewardRoutesCheck() == true) { + _compound(); + } + + lastHarvest = block.timestamp; + + emit Harvest(); + } + + // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. + function _compound() internal virtual {} + + /*////////////////////////////////////////////////////////////// + REWARDS AND ROUTES + //////////////////////////////////////////////////////////////*/ + + // Return available rewards for all rewardTokens. + function rewardsAvailable() public view virtual returns (uint256[] memory) {} + + // Check to see that at least 1 reward is available. + function _rewardsCheck() internal view virtual returns (bool) { + uint256[] rewards; + rewards = rewardsAvailable(); + + uint256 len = rewards.length; + for (uint256 i; i < len; ++i) { + if (rewards[i] > 0) { + return true; + } + } + + return false; + } + + // Check to make sure all rewardTokens have correct respective routes. + function _rewardRoutesCheck() internal view virtual returns (bool) { + uint256 len = rewards.length; + + for (uint256 i; i < len; ++i) { + if (rewardTokens[i] != rewardRoutes[i][0] || native != rewardRoutes[i][rewardRoutes.length - 1]) return false; + } + + return true; + } + + // Set rewardRoute at index. + function setRewardRoute(uint256 rewardIndex, address[] memory route) public virtual { + rewardRoutes[rewardIndex] = route; + } + + // Get rewardRoute at index. + function getRewardRoute(uint256 rewardIndex) public view virtual returns (address[] memory) { + return rewardRoute[index]; + } + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + // Calculate the total underlaying 'want' held by the strat. + function balanceOf() public view virtual returns (uint256) {} + + // Calculates how much 'want' this contract holds. + function balanceOfWant() public view virtual returns (uint256) {} + + // Calculates how much 'want' the strategy has working in the farm. + function balanceOfPool() public view virtual returns (uint256) {} +} From 75935057ccc77c5d5cb7344a46a151c434ebc651 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Mon, 20 Feb 2023 22:00:34 -0800 Subject: [PATCH 07/21] fix compilation errors --- .../src/vault/strategy/SimpleCompounder.sol | 30 +++++++++---------- .../src/vault/strategy/StrategyBase2.sol | 19 ++++++++---- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/packages/foundry/src/vault/strategy/SimpleCompounder.sol b/packages/foundry/src/vault/strategy/SimpleCompounder.sol index a77b1734..ce0b21c1 100644 --- a/packages/foundry/src/vault/strategy/SimpleCompounder.sol +++ b/packages/foundry/src/vault/strategy/SimpleCompounder.sol @@ -2,30 +2,29 @@ // Docgen-SOLC: 0.8.15 pragma solidity ^0.8.15; -import { StrategyBase } from "./StrategyBase2.sol"; +import { StrategyBase, ERC20 } from "./StrategyBase2.sol"; +import { IUniswapRouterV2 } from "../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IUniswapV2Pair } from "../../interfaces/external/uni/IUniswapV2Pair.sol"; contract SimpleCompounder is StrategyBase { - using SafeERC20 for IERC20; - using Math for uint256; - constructor( address _native, address _lpPair, address _vault, address _strategist, address[] memory _nativeToLp0Route, - address[] memory _nativeToLpRoute + address[] memory _nativeToLp1Route ) public { native = _native; lpPair = _lpPair; vault = _vault; strategist = _strategist; - _setUp(); + _setUp(_nativeToLp0Route, nativeToLp1Route); } // Setup for routes and allowances in constructor. - function _setUp() internal override { + function _setUp(address[] memory _nativeToLp0Route, address[] memory _nativeToLp1Route) internal override { // Lp routing checks lpToken0 = IUniswapV2Pair(lpPair).token0(); if (_nativeToLp0Route[0] != native) revert InvalidRoute(); @@ -42,24 +41,23 @@ contract SimpleCompounder is StrategyBase { // Give allowances for rewardToken swaps. function _giveAllowances() internal override { - IERC20(want).safeApprove(chef, type(uint256).max); - IERC20(native).safeApprove(router, type(uint256).max); - IERC20(lpToken0).safeApprove(router, type(uint256).max); - IERC20(lpToken1).safeApprove(router, type(uint256).max); + ERC20(native).approve(router, type(uint256).max); + ERC20(lpToken0).approve(router, type(uint256).max); + ERC20(lpToken1).approve(router, type(uint256).max); } // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. - function _compound() internal virtual {} + function _compound() internal override {} // Return available rewards for all rewardTokens. - function rewardsAvailable() public view virtual returns (uint256[] memory) {} + function rewardsAvailable() public view override returns (uint256[] memory) {} // Calculate the total underlaying 'want' held by the strat. - function balanceOf() public view virtual returns (uint256) {} + function balanceOf() public view override returns (uint256) {} // Calculates how much 'want' this contract holds. - function balanceOfWant() public view virtual returns (uint256) {} + function balanceOfWant() public view override returns (uint256) {} // Calculates how much 'want' the strategy has working in the farm. - function balanceOfPool() public view virtual returns (uint256) {} + function balanceOfPool() public view override returns (uint256) {} } diff --git a/packages/foundry/src/vault/strategy/StrategyBase2.sol b/packages/foundry/src/vault/strategy/StrategyBase2.sol index 83788d7c..4ce77c26 100644 --- a/packages/foundry/src/vault/strategy/StrategyBase2.sol +++ b/packages/foundry/src/vault/strategy/StrategyBase2.sol @@ -5,14 +5,17 @@ pragma solidity ^0.8.15; import { IWithRewards } from "../../interfaces/vault/IWithRewards.sol"; import { IEIP165 } from "../../interfaces/IEIP165.sol"; +import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import { IUniswapRouterV2 } from "../../interfaces/external/uni/IUniswapRouterV2.sol"; import { IAdapter } from "../../interfaces/vault/IAdapter.sol"; import { IWithRewards } from "../../interfaces/vault/IWithRewards.sol"; contract StrategyBase { + using Math for uint256; + // Tokens used - address public immutable native; + address public native; address public lpPair; address public lpToken0; address public lpToken1; @@ -21,6 +24,10 @@ contract StrategyBase { address[] public protocolAddresses; uint256[] public protocolUints; + // Vault and Strategist + address public vault; + address public strategist; + // Rewards and Routes address public router; /** @@ -57,7 +64,7 @@ contract StrategyBase { //////////////////////////////////////////////////////////////*/ // Setup for routes and allowances in constructor. - function _setUp() internal virtual {} + function _setUp(address[] memory _nativeToLp0Route, address[] memory _nativeToLp1Route) internal virtual {} // Give allowances necessary for deposit, withdraw, lpToken swaps, and addLiquidity. function _giveAllowances() internal virtual {} @@ -66,7 +73,7 @@ contract StrategyBase { function giveRewardAllowances() public { uint256 len = rewardTokens.length; for (uint256 i; i < len; ++i) { - IERC20(rewardTokens[i]).safeApprove(router, type(uint256).max); + ERC20(rewardTokens[i]).approve(router, type(uint256).max); } } @@ -103,7 +110,7 @@ contract StrategyBase { // Check to see that at least 1 reward is available. function _rewardsCheck() internal view virtual returns (bool) { - uint256[] rewards; + uint256[] memory rewards; rewards = rewardsAvailable(); uint256 len = rewards.length; @@ -118,7 +125,7 @@ contract StrategyBase { // Check to make sure all rewardTokens have correct respective routes. function _rewardRoutesCheck() internal view virtual returns (bool) { - uint256 len = rewards.length; + uint256 len = rewardTokens.length; for (uint256 i; i < len; ++i) { if (rewardTokens[i] != rewardRoutes[i][0] || native != rewardRoutes[i][rewardRoutes.length - 1]) return false; @@ -134,7 +141,7 @@ contract StrategyBase { // Get rewardRoute at index. function getRewardRoute(uint256 rewardIndex) public view virtual returns (address[] memory) { - return rewardRoute[index]; + return rewardRoutes[rewardIndex]; } /*////////////////////////////////////////////////////////////// From 822c4f6addd3909670f92574e827714beb50d28a Mon Sep 17 00:00:00 2001 From: amatureApe Date: Mon, 20 Feb 2023 22:02:33 -0800 Subject: [PATCH 08/21] minor touchup --- packages/foundry/src/vault/strategy/SimpleCompounder.sol | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/foundry/src/vault/strategy/SimpleCompounder.sol b/packages/foundry/src/vault/strategy/SimpleCompounder.sol index ce0b21c1..a8493a97 100644 --- a/packages/foundry/src/vault/strategy/SimpleCompounder.sol +++ b/packages/foundry/src/vault/strategy/SimpleCompounder.sol @@ -51,13 +51,4 @@ contract SimpleCompounder is StrategyBase { // Return available rewards for all rewardTokens. function rewardsAvailable() public view override returns (uint256[] memory) {} - - // Calculate the total underlaying 'want' held by the strat. - function balanceOf() public view override returns (uint256) {} - - // Calculates how much 'want' this contract holds. - function balanceOfWant() public view override returns (uint256) {} - - // Calculates how much 'want' the strategy has working in the farm. - function balanceOfPool() public view override returns (uint256) {} } From 090020120fe8ea16f62f3eac439573423aba1976 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Mon, 20 Feb 2023 22:09:02 -0800 Subject: [PATCH 09/21] constructor updates --- packages/foundry/src/vault/strategy/SimpleCompounder.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/foundry/src/vault/strategy/SimpleCompounder.sol b/packages/foundry/src/vault/strategy/SimpleCompounder.sol index a8493a97..7b7054e5 100644 --- a/packages/foundry/src/vault/strategy/SimpleCompounder.sol +++ b/packages/foundry/src/vault/strategy/SimpleCompounder.sol @@ -12,6 +12,8 @@ contract SimpleCompounder is StrategyBase { address _lpPair, address _vault, address _strategist, + address[] memory _rewardTokens, + address[][] memory _rewardRoutes, address[] memory _nativeToLp0Route, address[] memory _nativeToLp1Route ) public { @@ -20,6 +22,9 @@ contract SimpleCompounder is StrategyBase { vault = _vault; strategist = _strategist; + rewardTokens = _rewardTokens; + rewardRoutes = _rewardRoutes; + _setUp(_nativeToLp0Route, nativeToLp1Route); } From e3297f22dc4bc3e6565cea12838dba21c0cdd617 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Wed, 22 Feb 2023 05:14:32 -0800 Subject: [PATCH 10/21] add compouding functionality to autocompounder --- .../src/interfaces/external/IMiniChefV2.sol | 47 ++++++++ .../src/interfaces/external/IRewarder.sol | 10 ++ .../src/vault/strategy/SimpleCompounder.sol | 102 ++++++++++++++++-- .../src/vault/strategy/StrategyBase2.sol | 94 +++++++++++++--- 4 files changed, 230 insertions(+), 23 deletions(-) create mode 100644 packages/foundry/src/interfaces/external/IMiniChefV2.sol create mode 100644 packages/foundry/src/interfaces/external/IRewarder.sol diff --git a/packages/foundry/src/interfaces/external/IMiniChefV2.sol b/packages/foundry/src/interfaces/external/IMiniChefV2.sol new file mode 100644 index 00000000..cad26f3d --- /dev/null +++ b/packages/foundry/src/interfaces/external/IMiniChefV2.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IMiniChefV2 { + struct UserInfo { + uint256 amount; + uint256 rewardDebt; + } + + struct PoolInfo { + uint128 accSushiPerShare; + uint64 lastRewardTime; + uint64 allocPoint; + } + + function poolLength() external view returns (uint256); + + function updatePool(uint256 pid) external returns (IMiniChefV2.PoolInfo memory); + + function userInfo(uint256 _pid, address _user) external view returns (uint256, uint256); + + function deposit( + uint256 pid, + uint256 amount, + address to + ) external; + + function withdraw( + uint256 pid, + uint256 amount, + address to + ) external; + + function harvest(uint256 pid, address to) external; + + function pendingSushi(uint256 _pid, address _user) external returns (uint256); + + function rewarder(uint256 _pid) external returns (address); + + function withdrawAndHarvest( + uint256 pid, + uint256 amount, + address to + ) external; + + function emergencyWithdraw(uint256 pid, address to) external; +} diff --git a/packages/foundry/src/interfaces/external/IRewarder.sol b/packages/foundry/src/interfaces/external/IRewarder.sol new file mode 100644 index 00000000..903f26ed --- /dev/null +++ b/packages/foundry/src/interfaces/external/IRewarder.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IRewarder { + function pendingTokens( + uint256 _pid, + address _user, + uint256 + ) external returns (address[] memory rewardTokens, uint256[] memory rewardAmounts); +} diff --git a/packages/foundry/src/vault/strategy/SimpleCompounder.sol b/packages/foundry/src/vault/strategy/SimpleCompounder.sol index 7b7054e5..3c7b4de3 100644 --- a/packages/foundry/src/vault/strategy/SimpleCompounder.sol +++ b/packages/foundry/src/vault/strategy/SimpleCompounder.sol @@ -5,6 +5,8 @@ pragma solidity ^0.8.15; import { StrategyBase, ERC20 } from "./StrategyBase2.sol"; import { IUniswapRouterV2 } from "../../interfaces/external/uni/IUniswapRouterV2.sol"; import { IUniswapV2Pair } from "../../interfaces/external/uni/IUniswapV2Pair.sol"; +import { IMiniChefV2 } from "../../interfaces/external/IMiniChefV2.sol"; +import { IRewarder } from "../../interfaces/external/IRewarder.sol"; contract SimpleCompounder is StrategyBase { constructor( @@ -12,8 +14,9 @@ contract SimpleCompounder is StrategyBase { address _lpPair, address _vault, address _strategist, - address[] memory _rewardTokens, - address[][] memory _rewardRoutes, + address[] memory _protocolAddresses, + uint256[] memory _protocolUints, + address[][] memory _rewardToNativeRoutes, address[] memory _nativeToLp0Route, address[] memory _nativeToLp1Route ) public { @@ -22,15 +25,24 @@ contract SimpleCompounder is StrategyBase { vault = _vault; strategist = _strategist; - rewardTokens = _rewardTokens; - rewardRoutes = _rewardRoutes; + protocolAddresses = _protocolAddresses; + protocolUints = _protocolUints; - _setUp(_nativeToLp0Route, nativeToLp1Route); + rewardToNativeRoutes = _rewardToNativeRoutes; + + _setUp(nativeToLp0Route, nativeToLp1Route, rewardToNativeRoutes); } + /*////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////*/ + // Setup for routes and allowances in constructor. - function _setUp(address[] memory _nativeToLp0Route, address[] memory _nativeToLp1Route) internal override { - // Lp routing checks + function _setUp( + address[] memory _nativeToLp0Route, + address[] memory _nativeToLp1Route, + address[][] memory _rewardToNativeRoutes + ) internal override { lpToken0 = IUniswapV2Pair(lpPair).token0(); if (_nativeToLp0Route[0] != native) revert InvalidRoute(); if (_nativeToLp0Route[_nativeToLp0Route.length - 1] != lpToken0) revert InvalidRoute(); @@ -41,19 +53,87 @@ contract SimpleCompounder is StrategyBase { if (_nativeToLp1Route[_nativeToLp1Route.length - 1] != lpToken1) revert InvalidRoute(); nativeToLp1Route = _nativeToLp1Route; + uint256 len = rewardToNativeRoutes.length; + for (uint256 i; i < len; ++i) { + rewardTokens[i] = rewardToNativeRoutes[i][0]; + } + _giveInitialAllowances(); } - // Give allowances for rewardToken swaps. + // Give allowances for protocol deposit and rewardToken swaps. function _giveAllowances() internal override { + address minichef = protocolAddresses[0]; + + ERC20(lpPair).approve(minichef, type(uint256).max); ERC20(native).approve(router, type(uint256).max); ERC20(lpToken0).approve(router, type(uint256).max); ERC20(lpToken1).approve(router, type(uint256).max); } - // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. - function _compound() internal override {} + /*////////////////////////////////////////////////////////////// + HARVEST LOGIC + //////////////////////////////////////////////////////////////*/ + + // Claim rewards from underlying protocol + function _claimRewards() internal override { + address minichef = protocolAddresses[0]; + uint256 pid = protocolUints[0]; + + IMiniChefV2(minichef).harvest(pid, address(this)); + } + + // Swap all rewards to native token + function _swapRewardsToNative() internal override { + uint256 len = rewardToNativeRoutes.length; + for (uint256 i; i < len; ++i) { + address reward = rewardToNativeRoutes[i][0]; + address[] memory rewardRoute = rewardToNativeRoutes[i]; + uint256 rewardAmount = ERC20(reward).balanceOf(address(this)); + if (rewardAmount > 0) { + _uniV2Swap(router, rewardRoute, rewardAmount); + } + } + } + + // Charge fees for Popcorn + function _chargeFees() internal override {} + + // Swap native tokens for lpTokens + function _swapNativeToLpTokens() internal override { + _uniV2Swap(router, nativeToLp0Route, ERC20(native).balanceOf(address(this)) / 2); + _uniV2Swap(router, nativeToLp1Route, ERC20(native).balanceOf(address(this))); + } + + // Use lpTokens to create lpPair + function _addLiquidity() internal override { + _uniV2AddLiquidity(router, lpToken0, lpToken1); + } + + // redeposit lpPair into underlying protocol + function _redeposit() internal override { + address minichef = protocolAddresses[0]; + uint256 pid = protocolUints[0]; + + IMiniChefV2(minichef).deposit(pid, ERC20(lpPair).balanceOf(address(this)), address(this)); + } // Return available rewards for all rewardTokens. - function rewardsAvailable() public view override returns (uint256[] memory) {} + function rewardsAvailable() public override returns (uint256[] memory) { + address minichef = protocolAddresses[0]; + uint256 pid = protocolUints[0]; + + pendingRewards.push(IMiniChefV2(minichef).pendingSushi(pid, address(this))); + + address rewarder = IMiniChefV2(minichef).rewarder(pid); + + (, uint256[] memory rewardAmounts) = IRewarder(rewarder).pendingTokens(pid, address(this), 0); + + uint256 len = rewardAmounts.length; + for (uint256 i; i < len; ++i) { + pendingRewards.push(rewardAmounts[i]); + } + + return pendingRewards; + } } diff --git a/packages/foundry/src/vault/strategy/StrategyBase2.sol b/packages/foundry/src/vault/strategy/StrategyBase2.sol index 4ce77c26..a15f1ee3 100644 --- a/packages/foundry/src/vault/strategy/StrategyBase2.sol +++ b/packages/foundry/src/vault/strategy/StrategyBase2.sol @@ -35,12 +35,13 @@ contract StrategyBase { * @dev Paths follow this pattern: [rewardToken, ...hops, native] */ address[] public rewardTokens; - address[][] public rewardRoutes; + address[][] public rewardToNativeRoutes; address[] public nativeToLp0Route; address[] public nativeToLp1Route; // Data management uint256 public lastHarvest; + uint256[] public pendingRewards; // Events event Harvest(); @@ -64,7 +65,11 @@ contract StrategyBase { //////////////////////////////////////////////////////////////*/ // Setup for routes and allowances in constructor. - function _setUp(address[] memory _nativeToLp0Route, address[] memory _nativeToLp1Route) internal virtual {} + function _setUp( + address[] memory _nativeToLp0Route, + address[] memory _nativeToLp1Route, + address[][] memory _rewardToNativeRoutes + ) internal virtual {} // Give allowances necessary for deposit, withdraw, lpToken swaps, and addLiquidity. function _giveAllowances() internal virtual {} @@ -99,23 +104,47 @@ contract StrategyBase { } // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. - function _compound() internal virtual {} + function _compound() internal virtual { + _claimRewards(); + _swapRewardsToNative(); + _chargeFees(); + _swapNativeToLpTokens(); + _addLiquidity(); + _redeposit(); + } + + // Claim rewards from underlying protocol + function _claimRewards() internal virtual {} + + // Swap all rewards to native token + function _swapRewardsToNative() internal virtual {} + + // Charge fees for Popcorn + function _chargeFees() internal virtual {} + + // Swap native tokens for lpTokens + function _swapNativeToLpTokens() internal virtual {} + + // Use lpTokens to create lpPair + function _addLiquidity() internal virtual {} + + // redeposit lpPair into underlying protocol + function _redeposit() internal virtual {} /*////////////////////////////////////////////////////////////// REWARDS AND ROUTES //////////////////////////////////////////////////////////////*/ // Return available rewards for all rewardTokens. - function rewardsAvailable() public view virtual returns (uint256[] memory) {} + function rewardsAvailable() public virtual returns (uint256[] memory) {} // Check to see that at least 1 reward is available. - function _rewardsCheck() internal view virtual returns (bool) { - uint256[] memory rewards; - rewards = rewardsAvailable(); + function _rewardsCheck() internal virtual returns (bool) { + pendingRewards = rewardsAvailable(); - uint256 len = rewards.length; + uint256 len = pendingRewards.length; for (uint256 i; i < len; ++i) { - if (rewards[i] > 0) { + if (pendingRewards[i] > 0) { return true; } } @@ -128,7 +157,10 @@ contract StrategyBase { uint256 len = rewardTokens.length; for (uint256 i; i < len; ++i) { - if (rewardTokens[i] != rewardRoutes[i][0] || native != rewardRoutes[i][rewardRoutes.length - 1]) return false; + if ( + rewardTokens[i] != rewardToNativeRoutes[i][0] || + native != rewardToNativeRoutes[i][rewardToNativeRoutes.length - 1] + ) return false; } return true; @@ -136,12 +168,12 @@ contract StrategyBase { // Set rewardRoute at index. function setRewardRoute(uint256 rewardIndex, address[] memory route) public virtual { - rewardRoutes[rewardIndex] = route; + rewardToNativeRoutes[rewardIndex] = route; } // Get rewardRoute at index. function getRewardRoute(uint256 rewardIndex) public view virtual returns (address[] memory) { - return rewardRoutes[rewardIndex]; + return rewardToNativeRoutes[rewardIndex]; } /*////////////////////////////////////////////////////////////// @@ -156,4 +188,42 @@ contract StrategyBase { // Calculates how much 'want' the strategy has working in the farm. function balanceOfPool() public view virtual returns (uint256) {} + + /*////////////////////////////////////////////////////////////// + SWAPPING LOGIC + //////////////////////////////////////////////////////////////*/ + // Swap compatible with UniswapV2 interface. + function _uniV2Swap( + address _router, + address[] memory _route, + uint256 _amount + ) internal { + IUniswapRouterV2(router).swapExactTokensForTokens(_amount, 0, _route, address(this), block.timestamp + 60); + } + + /*////////////////////////////////////////////////////////////// + LIQUIDITY LOGIC + //////////////////////////////////////////////////////////////*/ + // Add liquidity to UniswapV2-compatible protocol. + function _uniV2AddLiquidity( + address _router, + address _lpToken0, + address _lpToken1 + ) internal { + uint256 lpToken0Amount = ERC20(_lpToken0).balanceOf(address(this)); + uint256 lpToken1Amount = ERC20(_lpToken1).balanceOf(address(this)); + + if (lpToken0Amount > 0 && lpToken1Amount > 0) { + IUniswapRouterV2(_router).addLiquidity( + _lpToken0, + _lpToken1, + lpToken0Amount, + lpToken1Amount, + 0, + 0, + address(this), + block.timestamp + 60 + ); + } + } } From 16b1b8218e0fe0c78c7fe1697dedbf7f545b3011 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Wed, 22 Feb 2023 08:28:06 -0800 Subject: [PATCH 11/21] add single stake compouding functionality --- ...mpounder.sol => SimpleSushiCompounder.sol} | 8 +++---- .../src/vault/strategy/StrategyBase2.sol | 22 ++++++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) rename packages/foundry/src/vault/strategy/{SimpleCompounder.sol => SimpleSushiCompounder.sol} (97%) diff --git a/packages/foundry/src/vault/strategy/SimpleCompounder.sol b/packages/foundry/src/vault/strategy/SimpleSushiCompounder.sol similarity index 97% rename from packages/foundry/src/vault/strategy/SimpleCompounder.sol rename to packages/foundry/src/vault/strategy/SimpleSushiCompounder.sol index 3c7b4de3..da9da31c 100644 --- a/packages/foundry/src/vault/strategy/SimpleCompounder.sol +++ b/packages/foundry/src/vault/strategy/SimpleSushiCompounder.sol @@ -8,8 +8,9 @@ import { IUniswapV2Pair } from "../../interfaces/external/uni/IUniswapV2Pair.sol import { IMiniChefV2 } from "../../interfaces/external/IMiniChefV2.sol"; import { IRewarder } from "../../interfaces/external/IRewarder.sol"; -contract SimpleCompounder is StrategyBase { +contract SimpleSushiCompounder is StrategyBase { constructor( + bool _isLiquidityPair, address _native, address _lpPair, address _vault, @@ -20,6 +21,8 @@ contract SimpleCompounder is StrategyBase { address[] memory _nativeToLp0Route, address[] memory _nativeToLp1Route ) public { + isAssetLiquidityPair = _isAssetLiquidityPair; + native = _native; lpPair = _lpPair; vault = _vault; @@ -96,9 +99,6 @@ contract SimpleCompounder is StrategyBase { } } - // Charge fees for Popcorn - function _chargeFees() internal override {} - // Swap native tokens for lpTokens function _swapNativeToLpTokens() internal override { _uniV2Swap(router, nativeToLp0Route, ERC20(native).balanceOf(address(this)) / 2); diff --git a/packages/foundry/src/vault/strategy/StrategyBase2.sol b/packages/foundry/src/vault/strategy/StrategyBase2.sol index a15f1ee3..5b0719ab 100644 --- a/packages/foundry/src/vault/strategy/StrategyBase2.sol +++ b/packages/foundry/src/vault/strategy/StrategyBase2.sol @@ -40,6 +40,7 @@ contract StrategyBase { address[] public nativeToLp1Route; // Data management + bool public isAssetLiquidityPair; uint256 public lastHarvest; uint256[] public pendingRewards; @@ -106,22 +107,27 @@ contract StrategyBase { // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. function _compound() internal virtual { _claimRewards(); - _swapRewardsToNative(); - _chargeFees(); - _swapNativeToLpTokens(); - _addLiquidity(); + + if (isAssetLiquidityPair == true) { + _swapRewardsToNative(); + _swapNativeToLpTokens(); + _addLiquidity(); + } else { + _swapRewards(); + } + _redeposit(); } // Claim rewards from underlying protocol function _claimRewards() internal virtual {} + // Swap rewards + function _swapRewards() internal virtual {} + // Swap all rewards to native token function _swapRewardsToNative() internal virtual {} - // Charge fees for Popcorn - function _chargeFees() internal virtual {} - // Swap native tokens for lpTokens function _swapNativeToLpTokens() internal virtual {} @@ -167,7 +173,7 @@ contract StrategyBase { } // Set rewardRoute at index. - function setRewardRoute(uint256 rewardIndex, address[] memory route) public virtual { + function setRewardRoute(uint256 rewardIndex, address[] calldata route) public virtual { rewardToNativeRoutes[rewardIndex] = route; } From 21b5eaf10c0a0e12ea8268bb5da791d92830935f Mon Sep 17 00:00:00 2001 From: amatureApe Date: Wed, 22 Feb 2023 08:51:03 -0800 Subject: [PATCH 12/21] add isVaultFunctional check --- .../foundry/src/vault/strategy/StrategyBase2.sol | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/foundry/src/vault/strategy/StrategyBase2.sol b/packages/foundry/src/vault/strategy/StrategyBase2.sol index 5b0719ab..4cb43d69 100644 --- a/packages/foundry/src/vault/strategy/StrategyBase2.sol +++ b/packages/foundry/src/vault/strategy/StrategyBase2.sol @@ -31,15 +31,23 @@ contract StrategyBase { // Rewards and Routes address public router; /** - * @dev rewardToken index must match respective path index. - * @dev Paths follow this pattern: [rewardToken, ...hops, native] + * @dev rewardToken index must match respective index in rewardRoutes. + * @dev Routes follow this pattern: [rewardToken, ...hops, native] */ address[] public rewardTokens; address[][] public rewardToNativeRoutes; address[] public nativeToLp0Route; address[] public nativeToLp1Route; + struct LpRoutes { + address[] public rewardTokens, + address[][] public rewardToNativeRoutes, + address[] public nativeToLp0Route, + address[] public nativeToLp1Route + } + // Data management + bool public isVaultFunctional; bool public isAssetLiquidityPair; uint256 public lastHarvest; uint256[] public pendingRewards; @@ -100,6 +108,7 @@ contract StrategyBase { } lastHarvest = block.timestamp; + if (isVaultFunctional == false) isVaultFunctional = true; emit Harvest(); } From 890bed64e733307ef22723da5dd0c75ecdd3a6f6 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Wed, 22 Feb 2023 14:51:40 -0800 Subject: [PATCH 13/21] autocompounder refactor --- .../CompounderStrategyBase.sol} | 122 +++++------------- .../liquidityPair/LiquidityPairBase.sol | 84 ++++++++++++ .../protocols/sushi/MiniChefV2Compounder.sol} | 56 ++++---- .../singleStake/SingleStakeBase.sol | 51 ++++++++ .../protocols/stargate/StargateCompounder.sol | 119 +++++++++++++++++ 5 files changed, 312 insertions(+), 120 deletions(-) rename packages/foundry/src/vault/strategy/{StrategyBase2.sol => autocompouding/CompounderStrategyBase.sol} (64%) create mode 100644 packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol rename packages/foundry/src/vault/strategy/{SimpleSushiCompounder.sol => autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol} (66%) create mode 100644 packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol create mode 100644 packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol diff --git a/packages/foundry/src/vault/strategy/StrategyBase2.sol b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol similarity index 64% rename from packages/foundry/src/vault/strategy/StrategyBase2.sol rename to packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol index 4cb43d69..b18509fd 100644 --- a/packages/foundry/src/vault/strategy/StrategyBase2.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol @@ -3,22 +3,17 @@ pragma solidity ^0.8.15; -import { IWithRewards } from "../../interfaces/vault/IWithRewards.sol"; -import { IEIP165 } from "../../interfaces/IEIP165.sol"; +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { IEIP165 } from "../../../interfaces/IEIP165.sol"; import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; -import { IUniswapRouterV2 } from "../../interfaces/external/uni/IUniswapRouterV2.sol"; -import { IAdapter } from "../../interfaces/vault/IAdapter.sol"; -import { IWithRewards } from "../../interfaces/vault/IWithRewards.sol"; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; -contract StrategyBase { - using Math for uint256; - - // Tokens used +contract CompounderStrategyBase { + // Native token on chain address public native; - address public lpPair; - address public lpToken0; - address public lpToken1; // Protocol contracts info (masterchefs, poolIds, etc.) address[] public protocolAddresses; @@ -28,28 +23,18 @@ contract StrategyBase { address public vault; address public strategist; - // Rewards and Routes - address public router; + // Router and Routes /** - * @dev rewardToken index must match respective index in rewardRoutes. + * @dev rewardToken index must match respective index in rewardRoutes and pendingRewards. * @dev Routes follow this pattern: [rewardToken, ...hops, native] */ - address[] public rewardTokens; - address[][] public rewardToNativeRoutes; - address[] public nativeToLp0Route; - address[] public nativeToLp1Route; - - struct LpRoutes { - address[] public rewardTokens, - address[][] public rewardToNativeRoutes, - address[] public nativeToLp0Route, - address[] public nativeToLp1Route - } + address public router; + address[][] public rewardsToNativeRoutes; // Data management bool public isVaultFunctional; - bool public isAssetLiquidityPair; uint256 public lastHarvest; + address[] public rewardTokens; uint256[] public pendingRewards; // Events @@ -73,13 +58,6 @@ contract StrategyBase { SETUP //////////////////////////////////////////////////////////////*/ - // Setup for routes and allowances in constructor. - function _setUp( - address[] memory _nativeToLp0Route, - address[] memory _nativeToLp1Route, - address[][] memory _rewardToNativeRoutes - ) internal virtual {} - // Give allowances necessary for deposit, withdraw, lpToken swaps, and addLiquidity. function _giveAllowances() internal virtual {} @@ -113,38 +91,17 @@ contract StrategyBase { emit Harvest(); } - // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. - function _compound() internal virtual { - _claimRewards(); - - if (isAssetLiquidityPair == true) { - _swapRewardsToNative(); - _swapNativeToLpTokens(); - _addLiquidity(); - } else { - _swapRewards(); - } - - _redeposit(); - } - - // Claim rewards from underlying protocol - function _claimRewards() internal virtual {} - - // Swap rewards - function _swapRewards() internal virtual {} + // Logic to claim rewards, swap rewards to native, charge fees, swap native to deposit token, add liquidity (if necessary), and re-deposit. + function _compound() internal virtual {} // Swap all rewards to native token function _swapRewardsToNative() internal virtual {} - // Swap native tokens for lpTokens - function _swapNativeToLpTokens() internal virtual {} - - // Use lpTokens to create lpPair - function _addLiquidity() internal virtual {} + // Claim rewards from underlying protocol + function _claimRewards() internal virtual {} - // redeposit lpPair into underlying protocol - function _redeposit() internal virtual {} + // deposit lpPair into underlying protocol + function _deposit() internal virtual {} /*////////////////////////////////////////////////////////////// REWARDS AND ROUTES @@ -153,6 +110,15 @@ contract StrategyBase { // Return available rewards for all rewardTokens. function rewardsAvailable() public virtual returns (uint256[] memory) {} + // Set rewards tokens according to rewardToNativeRoutes + function _setRewardTokens(address[][] memory _rewardsToNativeRoutes) internal virtual { + uint256 len = _rewardsToNativeRoutes.length; + for (uint256 i; i < len; ++i) { + if (_rewardsToNativeRoutes[i][_rewardsToNativeRoutes.length - 1] != native) revert InvalidRoute(); + rewardTokens[i] = _rewardsToNativeRoutes[i][0]; + } + } + // Check to see that at least 1 reward is available. function _rewardsCheck() internal virtual returns (bool) { pendingRewards = rewardsAvailable(); @@ -173,8 +139,8 @@ contract StrategyBase { for (uint256 i; i < len; ++i) { if ( - rewardTokens[i] != rewardToNativeRoutes[i][0] || - native != rewardToNativeRoutes[i][rewardToNativeRoutes.length - 1] + rewardTokens[i] != rewardsToNativeRoutes[i][0] || + native != rewardsToNativeRoutes[i][rewardsToNativeRoutes.length - 1] ) return false; } @@ -183,12 +149,12 @@ contract StrategyBase { // Set rewardRoute at index. function setRewardRoute(uint256 rewardIndex, address[] calldata route) public virtual { - rewardToNativeRoutes[rewardIndex] = route; + rewardsToNativeRoutes[rewardIndex] = route; } // Get rewardRoute at index. function getRewardRoute(uint256 rewardIndex) public view virtual returns (address[] memory) { - return rewardToNativeRoutes[rewardIndex]; + return rewardsToNativeRoutes[rewardIndex]; } /*////////////////////////////////////////////////////////////// @@ -215,30 +181,4 @@ contract StrategyBase { ) internal { IUniswapRouterV2(router).swapExactTokensForTokens(_amount, 0, _route, address(this), block.timestamp + 60); } - - /*////////////////////////////////////////////////////////////// - LIQUIDITY LOGIC - //////////////////////////////////////////////////////////////*/ - // Add liquidity to UniswapV2-compatible protocol. - function _uniV2AddLiquidity( - address _router, - address _lpToken0, - address _lpToken1 - ) internal { - uint256 lpToken0Amount = ERC20(_lpToken0).balanceOf(address(this)); - uint256 lpToken1Amount = ERC20(_lpToken1).balanceOf(address(this)); - - if (lpToken0Amount > 0 && lpToken1Amount > 0) { - IUniswapRouterV2(_router).addLiquidity( - _lpToken0, - _lpToken1, - lpToken0Amount, - lpToken1Amount, - 0, - 0, - address(this), - block.timestamp + 60 - ); - } - } } diff --git a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol new file mode 100644 index 00000000..23d37b97 --- /dev/null +++ b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../../interfaces/vault/IWithRewards.sol"; +import { IEIP165 } from "../../../../interfaces/IEIP165.sol"; +import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; + +import { CompounderStrategyBase } from "../CompounderStrategyBase.sol"; + +import { IUniswapRouterV2 } from "../../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../../../interfaces/vault/IWithRewards.sol"; + +contract LiquidityPairBase is CompounderStrategyBase { + using Math for uint256; + + // Tokens used + address public lpPair; + address public lpToken0; + address public lpToken1; + + // Routes + address[] public nativeToLp0Route; + address[] public nativeToLp1Route; + + /*////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////*/ + + // Setup for routes and allowances in constructor. + function _setUp( + address[][] memory _rewardToNativeRoutes, + address[] memory _nativeToLp0Route, + address[] memory _nativeToLp1Route + ) internal virtual {} + + /*////////////////////////////////////////////////////////////// + COMPOUND LOGIC + //////////////////////////////////////////////////////////////*/ + + // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. + function _compound() internal virtual override { + _claimRewards(); + _swapRewardsToNative(); + _swapNativeToLpTokens(); + _addLiquidity(); + _deposit(); + } + + // Swap native tokens for lpTokens + function _swapNativeToLpTokens() internal virtual {} + + // Use lpTokens to create lpPair + function _addLiquidity() internal virtual {} + + /*////////////////////////////////////////////////////////////// + LIQUIDITY LOGIC + //////////////////////////////////////////////////////////////*/ + // Add liquidity to UniswapV2-compatible protocol. + function _uniV2AddLiquidity( + address _router, + address _lpToken0, + address _lpToken1 + ) internal { + uint256 lpToken0Amount = ERC20(_lpToken0).balanceOf(address(this)); + uint256 lpToken1Amount = ERC20(_lpToken1).balanceOf(address(this)); + + if (lpToken0Amount > 0 && lpToken1Amount > 0) { + IUniswapRouterV2(_router).addLiquidity( + _lpToken0, + _lpToken1, + lpToken0Amount, + lpToken1Amount, + 0, + 0, + address(this), + block.timestamp + 60 + ); + } + } +} diff --git a/packages/foundry/src/vault/strategy/SimpleSushiCompounder.sol b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol similarity index 66% rename from packages/foundry/src/vault/strategy/SimpleSushiCompounder.sol rename to packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol index da9da31c..ca06f3f9 100644 --- a/packages/foundry/src/vault/strategy/SimpleSushiCompounder.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol @@ -2,27 +2,24 @@ // Docgen-SOLC: 0.8.15 pragma solidity ^0.8.15; -import { StrategyBase, ERC20 } from "./StrategyBase2.sol"; -import { IUniswapRouterV2 } from "../../interfaces/external/uni/IUniswapRouterV2.sol"; -import { IUniswapV2Pair } from "../../interfaces/external/uni/IUniswapV2Pair.sol"; -import { IMiniChefV2 } from "../../interfaces/external/IMiniChefV2.sol"; -import { IRewarder } from "../../interfaces/external/IRewarder.sol"; +import { LiquidityPairBase, ERC20 } from "../../LiquidityPairBase.sol"; +import { IUniswapRouterV2 } from "../../../../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IUniswapV2Pair } from "../../../../../../interfaces/external/uni/IUniswapV2Pair.sol"; +import { IMiniChefV2 } from "../../../../../../interfaces/external/IMiniChefV2.sol"; +import { IRewarder } from "../../../../../../interfaces/external/IRewarder.sol"; -contract SimpleSushiCompounder is StrategyBase { +contract LiquidityPairCompounder is LiquidityPairBase { constructor( - bool _isLiquidityPair, address _native, address _lpPair, address _vault, address _strategist, address[] memory _protocolAddresses, uint256[] memory _protocolUints, - address[][] memory _rewardToNativeRoutes, + address[][] memory _rewardsToNativeRoutes, address[] memory _nativeToLp0Route, address[] memory _nativeToLp1Route ) public { - isAssetLiquidityPair = _isAssetLiquidityPair; - native = _native; lpPair = _lpPair; vault = _vault; @@ -31,9 +28,9 @@ contract SimpleSushiCompounder is StrategyBase { protocolAddresses = _protocolAddresses; protocolUints = _protocolUints; - rewardToNativeRoutes = _rewardToNativeRoutes; + rewardsToNativeRoutes = _rewardsToNativeRoutes; - _setUp(nativeToLp0Route, nativeToLp1Route, rewardToNativeRoutes); + _setUp(rewardsToNativeRoutes, nativeToLp0Route, nativeToLp1Route); } /*////////////////////////////////////////////////////////////// @@ -42,9 +39,9 @@ contract SimpleSushiCompounder is StrategyBase { // Setup for routes and allowances in constructor. function _setUp( + address[][] memory _rewardsToNativeRoutes, address[] memory _nativeToLp0Route, - address[] memory _nativeToLp1Route, - address[][] memory _rewardToNativeRoutes + address[] memory _nativeToLp1Route ) internal override { lpToken0 = IUniswapV2Pair(lpPair).token0(); if (_nativeToLp0Route[0] != native) revert InvalidRoute(); @@ -56,9 +53,10 @@ contract SimpleSushiCompounder is StrategyBase { if (_nativeToLp1Route[_nativeToLp1Route.length - 1] != lpToken1) revert InvalidRoute(); nativeToLp1Route = _nativeToLp1Route; - uint256 len = rewardToNativeRoutes.length; + uint256 len = _rewardsToNativeRoutes.length; for (uint256 i; i < len; ++i) { - rewardTokens[i] = rewardToNativeRoutes[i][0]; + if (_rewardsToNativeRoutes[i][_rewardsToNativeRoutes.length - 1] != native) revert InvalidRoute(); + rewardTokens[i] = rewardsToNativeRoutes[i][0]; } _giveInitialAllowances(); @@ -66,9 +64,9 @@ contract SimpleSushiCompounder is StrategyBase { // Give allowances for protocol deposit and rewardToken swaps. function _giveAllowances() internal override { - address minichef = protocolAddresses[0]; + address chef = protocolAddresses[0]; - ERC20(lpPair).approve(minichef, type(uint256).max); + ERC20(lpPair).approve(chef, type(uint256).max); ERC20(native).approve(router, type(uint256).max); ERC20(lpToken0).approve(router, type(uint256).max); ERC20(lpToken1).approve(router, type(uint256).max); @@ -80,18 +78,18 @@ contract SimpleSushiCompounder is StrategyBase { // Claim rewards from underlying protocol function _claimRewards() internal override { - address minichef = protocolAddresses[0]; + address chef = protocolAddresses[0]; uint256 pid = protocolUints[0]; - IMiniChefV2(minichef).harvest(pid, address(this)); + IMiniChefV2(chef).harvest(pid, address(this)); } // Swap all rewards to native token function _swapRewardsToNative() internal override { - uint256 len = rewardToNativeRoutes.length; + uint256 len = rewardsToNativeRoutes.length; for (uint256 i; i < len; ++i) { - address reward = rewardToNativeRoutes[i][0]; - address[] memory rewardRoute = rewardToNativeRoutes[i]; + address reward = rewardsToNativeRoutes[i][0]; + address[] memory rewardRoute = rewardsToNativeRoutes[i]; uint256 rewardAmount = ERC20(reward).balanceOf(address(this)); if (rewardAmount > 0) { _uniV2Swap(router, rewardRoute, rewardAmount); @@ -111,21 +109,21 @@ contract SimpleSushiCompounder is StrategyBase { } // redeposit lpPair into underlying protocol - function _redeposit() internal override { - address minichef = protocolAddresses[0]; + function _deposit() internal override { + address chef = protocolAddresses[0]; uint256 pid = protocolUints[0]; - IMiniChefV2(minichef).deposit(pid, ERC20(lpPair).balanceOf(address(this)), address(this)); + IMiniChefV2(chef).deposit(pid, ERC20(lpPair).balanceOf(address(this)), address(this)); } // Return available rewards for all rewardTokens. function rewardsAvailable() public override returns (uint256[] memory) { - address minichef = protocolAddresses[0]; + address chef = protocolAddresses[0]; uint256 pid = protocolUints[0]; - pendingRewards.push(IMiniChefV2(minichef).pendingSushi(pid, address(this))); + pendingRewards.push(IMiniChefV2(chef).pendingSushi(pid, address(this))); - address rewarder = IMiniChefV2(minichef).rewarder(pid); + address rewarder = IMiniChefV2(chef).rewarder(pid); (, uint256[] memory rewardAmounts) = IRewarder(rewarder).pendingTokens(pid, address(this), 0); diff --git a/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol b/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol new file mode 100644 index 00000000..889eb781 --- /dev/null +++ b/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../../interfaces/vault/IWithRewards.sol"; +import { IAdapter } from "../../../../interfaces/vault/IAdapter.sol"; + +import { IEIP165 } from "../../../../interfaces/IEIP165.sol"; +import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; + +import { CompounderStrategyBase } from "../CompounderStrategyBase.sol"; + +import { IUniswapRouterV2 } from "../../../../interfaces/external/uni/IUniswapRouterV2.sol"; + +contract SingleStakeBase is CompounderStrategyBase { + using Math for uint256; + + // Tokens used + address public assetToken; + + // Routes + address[] public nativeToAssetTokenRoute; + + /*////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////*/ + + // Setup for routes and allowances in constructor. + function _setUp( + address[][] memory _rewardToNativeRoutes, + address[] memory _nativeToAssetTokenRoute, + address _assetToken + ) internal virtual {} + + /*////////////////////////////////////////////////////////////// + HARVEST LOGIC + //////////////////////////////////////////////////////////////*/ + + // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. + function _compound() internal virtual override { + _claimRewards(); + _swapRewardsToNative(); + _swapNativeToAssetToken(); + _deposit(); + } + + // Swap native tokens for assetToken + function _swapNativeToAssetToken() internal virtual {} +} diff --git a/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol new file mode 100644 index 00000000..2ab1d5d1 --- /dev/null +++ b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { SingleStakeBase, ERC20 } from "../../SingleStakeBase.sol"; +import { IUniswapRouterV2 } from "../../../../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IUniswapV2Pair } from "../../../../../../interfaces/external/uni/IUniswapV2Pair.sol"; +import { IMiniChefV2 } from "../../../../../../interfaces/external/IMiniChefV2.sol"; +import { IRewarder } from "../../../../../../interfaces/external/IRewarder.sol"; + +contract StargateCompounder is SingleStakeBase { + constructor( + address _native, + address _assetToken, + address _vault, + address _strategist, + address[] memory _protocolAddresses, + uint256[] memory _protocolUints, + address[][] memory _rewardsToNativeRoutes, + address[] memory _nativeToAssetTokenRoute + ) public { + native = _native; + vault = _vault; + strategist = _strategist; + + protocolAddresses = _protocolAddresses; + protocolUints = _protocolUints; + + rewardsToNativeRoutes = _rewardsToNativeRoutes; + nativeToAssetTokenRoute = _nativeToAssetTokenRoute; + + _setUp(rewardsToNativeRoutes, nativeToAssetTokenRoute, _assetToken); + } + + /*////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////*/ + + // Setup for routes and allowances in constructor. + function _setUp( + address[][] memory _rewardToNativeRoutes, + address[] memory _nativeToAssetTokenRoute, + address _assetToken + ) internal override { + if (_nativeToAssetTokenRoute[0] != native) revert InvalidRoute(); + + assetToken = _assetToken; + + if (_nativeToAssetTokenRoute[_nativeToAssetTokenRoute.length - 1] != _assetToken) revert InvalidRoute(); + + _setRewardTokens(_rewardToNativeRoutes); + + _giveInitialAllowances(); + } + + // Give allowances for protocol deposit and rewardToken swaps. + function _giveAllowances() internal override { + address chef = protocolAddresses[0]; + + ERC20(assetToken).approve(chef, type(uint256).max); + } + + /*////////////////////////////////////////////////////////////// + HARVEST LOGIC + //////////////////////////////////////////////////////////////*/ + + // Claim rewards from underlying protocol + function _claimRewards() internal override { + address chef = protocolAddresses[0]; + uint256 pid = protocolUints[0]; + + IMiniChefV2(chef).harvest(pid, address(this)); + } + + // Swap all rewards to native token + function _swapRewardsToNative() internal override { + uint256 len = rewardsToNativeRoutes.length; + for (uint256 i; i < len; ++i) { + address reward = rewardsToNativeRoutes[i][0]; + address[] memory rewardRoute = rewardsToNativeRoutes[i]; + uint256 rewardAmount = ERC20(reward).balanceOf(address(this)); + if (rewardAmount > 0) { + _uniV2Swap(router, rewardRoute, rewardAmount); + } + } + } + + // Swap native tokens for lpTokens + function _swapNativeToAssetToken() internal override { + _uniV2Swap(router, nativeToAssetTokenRoute, ERC20(native).balanceOf(address(this))); + } + + // redeposit lpPair into underlying protocol + function _deposit() internal override { + address chef = protocolAddresses[0]; + uint256 pid = protocolUints[0]; + + IMiniChefV2(chef).deposit(pid, ERC20(assetToken).balanceOf(address(this)), address(this)); + } + + // Return available rewards for all rewardTokens. + function rewardsAvailable() public override returns (uint256[] memory) { + address chef = protocolAddresses[0]; + uint256 pid = protocolUints[0]; + + pendingRewards.push(IMiniChefV2(chef).pendingSushi(pid, address(this))); + + address rewarder = IMiniChefV2(chef).rewarder(pid); + + (, uint256[] memory rewardAmounts) = IRewarder(rewarder).pendingTokens(pid, address(this), 0); + + uint256 len = rewardAmounts.length; + for (uint256 i; i < len; ++i) { + pendingRewards.push(rewardAmounts[i]); + } + + return pendingRewards; + } +} From 17fd32ff54493a5b9d075c5ed92b4657568d6c89 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Wed, 22 Feb 2023 15:21:57 -0800 Subject: [PATCH 14/21] add stargateCompounder --- .../external/stargate/IStargateRewarder.sol | 8 ++++ .../external/stargate/IStargateRouter.sol | 10 +++++ .../external/{ => sushi}/IMiniChefV2.sol | 0 .../autocompouding/CompounderStrategyBase.sol | 6 +-- .../protocols/sushi/MiniChefV2Compounder.sol | 19 ++++---- .../protocols/stargate/StargateCompounder.sol | 43 ++++++++++--------- 6 files changed, 54 insertions(+), 32 deletions(-) create mode 100644 packages/foundry/src/interfaces/external/stargate/IStargateRewarder.sol create mode 100644 packages/foundry/src/interfaces/external/stargate/IStargateRouter.sol rename packages/foundry/src/interfaces/external/{ => sushi}/IMiniChefV2.sol (100%) diff --git a/packages/foundry/src/interfaces/external/stargate/IStargateRewarder.sol b/packages/foundry/src/interfaces/external/stargate/IStargateRewarder.sol new file mode 100644 index 00000000..39636524 --- /dev/null +++ b/packages/foundry/src/interfaces/external/stargate/IStargateRewarder.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IStargateRewarder { + function deposit(uint256 _pid, uint256 _amount) external; + + function pendingStargate(uint256 _pid, address _user) external view returns (uint256); +} diff --git a/packages/foundry/src/interfaces/external/stargate/IStargateRouter.sol b/packages/foundry/src/interfaces/external/stargate/IStargateRouter.sol new file mode 100644 index 00000000..79e99172 --- /dev/null +++ b/packages/foundry/src/interfaces/external/stargate/IStargateRouter.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IStargateRouter { + function addLiquidity( + uint256 _pid, + uint256 _amount, + address _to + ) external; +} diff --git a/packages/foundry/src/interfaces/external/IMiniChefV2.sol b/packages/foundry/src/interfaces/external/sushi/IMiniChefV2.sol similarity index 100% rename from packages/foundry/src/interfaces/external/IMiniChefV2.sol rename to packages/foundry/src/interfaces/external/sushi/IMiniChefV2.sol diff --git a/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol index b18509fd..11b78260 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol @@ -24,11 +24,11 @@ contract CompounderStrategyBase { address public strategist; // Router and Routes + address public swapRouter; /** * @dev rewardToken index must match respective index in rewardRoutes and pendingRewards. * @dev Routes follow this pattern: [rewardToken, ...hops, native] */ - address public router; address[][] public rewardsToNativeRoutes; // Data management @@ -65,7 +65,7 @@ contract CompounderStrategyBase { function giveRewardAllowances() public { uint256 len = rewardTokens.length; for (uint256 i; i < len; ++i) { - ERC20(rewardTokens[i]).approve(router, type(uint256).max); + ERC20(rewardTokens[i]).approve(swapRouter, type(uint256).max); } } @@ -179,6 +179,6 @@ contract CompounderStrategyBase { address[] memory _route, uint256 _amount ) internal { - IUniswapRouterV2(router).swapExactTokensForTokens(_amount, 0, _route, address(this), block.timestamp + 60); + IUniswapRouterV2(_router).swapExactTokensForTokens(_amount, 0, _route, address(this), block.timestamp + 60); } } diff --git a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol index ca06f3f9..4b7ce955 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol @@ -5,13 +5,14 @@ pragma solidity ^0.8.15; import { LiquidityPairBase, ERC20 } from "../../LiquidityPairBase.sol"; import { IUniswapRouterV2 } from "../../../../../../interfaces/external/uni/IUniswapRouterV2.sol"; import { IUniswapV2Pair } from "../../../../../../interfaces/external/uni/IUniswapV2Pair.sol"; -import { IMiniChefV2 } from "../../../../../../interfaces/external/IMiniChefV2.sol"; +import { IMiniChefV2 } from "../../../../../../interfaces/external/sushi/IMiniChefV2.sol"; import { IRewarder } from "../../../../../../interfaces/external/IRewarder.sol"; contract LiquidityPairCompounder is LiquidityPairBase { constructor( address _native, address _lpPair, + address _swapRouter, address _vault, address _strategist, address[] memory _protocolAddresses, @@ -22,6 +23,8 @@ contract LiquidityPairCompounder is LiquidityPairBase { ) public { native = _native; lpPair = _lpPair; + swapRouter = _swapRouter; + vault = _vault; strategist = _strategist; @@ -67,9 +70,9 @@ contract LiquidityPairCompounder is LiquidityPairBase { address chef = protocolAddresses[0]; ERC20(lpPair).approve(chef, type(uint256).max); - ERC20(native).approve(router, type(uint256).max); - ERC20(lpToken0).approve(router, type(uint256).max); - ERC20(lpToken1).approve(router, type(uint256).max); + ERC20(native).approve(swapRouter, type(uint256).max); + ERC20(lpToken0).approve(swapRouter, type(uint256).max); + ERC20(lpToken1).approve(swapRouter, type(uint256).max); } /*////////////////////////////////////////////////////////////// @@ -92,20 +95,20 @@ contract LiquidityPairCompounder is LiquidityPairBase { address[] memory rewardRoute = rewardsToNativeRoutes[i]; uint256 rewardAmount = ERC20(reward).balanceOf(address(this)); if (rewardAmount > 0) { - _uniV2Swap(router, rewardRoute, rewardAmount); + _uniV2Swap(swapRouter, rewardRoute, rewardAmount); } } } // Swap native tokens for lpTokens function _swapNativeToLpTokens() internal override { - _uniV2Swap(router, nativeToLp0Route, ERC20(native).balanceOf(address(this)) / 2); - _uniV2Swap(router, nativeToLp1Route, ERC20(native).balanceOf(address(this))); + _uniV2Swap(swapRouter, nativeToLp0Route, ERC20(native).balanceOf(address(this)) / 2); + _uniV2Swap(swapRouter, nativeToLp1Route, ERC20(native).balanceOf(address(this))); } // Use lpTokens to create lpPair function _addLiquidity() internal override { - _uniV2AddLiquidity(router, lpToken0, lpToken1); + _uniV2AddLiquidity(swapRouter, lpToken0, lpToken1); } // redeposit lpPair into underlying protocol diff --git a/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol index 2ab1d5d1..d225c36a 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol @@ -5,13 +5,15 @@ pragma solidity ^0.8.15; import { SingleStakeBase, ERC20 } from "../../SingleStakeBase.sol"; import { IUniswapRouterV2 } from "../../../../../../interfaces/external/uni/IUniswapRouterV2.sol"; import { IUniswapV2Pair } from "../../../../../../interfaces/external/uni/IUniswapV2Pair.sol"; -import { IMiniChefV2 } from "../../../../../../interfaces/external/IMiniChefV2.sol"; +import { IStargateRewarder } from "../../../../../../interfaces/external/stargate/IStargateRewarder.sol"; +import { IStargateRouter } from "../../../../../../interfaces/external/stargate/IStargateRouter.sol"; import { IRewarder } from "../../../../../../interfaces/external/IRewarder.sol"; contract StargateCompounder is SingleStakeBase { constructor( address _native, address _assetToken, + address _swapRouter, address _vault, address _strategist, address[] memory _protocolAddresses, @@ -20,6 +22,8 @@ contract StargateCompounder is SingleStakeBase { address[] memory _nativeToAssetTokenRoute ) public { native = _native; + swapRouter = _swapRouter; + vault = _vault; strategist = _strategist; @@ -55,9 +59,12 @@ contract StargateCompounder is SingleStakeBase { // Give allowances for protocol deposit and rewardToken swaps. function _giveAllowances() internal override { - address chef = protocolAddresses[0]; + address rewarder = protocolAddresses[0]; + address stargateRouter = protocolAddresses[1]; + address stargateWrappedAsset = protocolAddresses[2]; - ERC20(assetToken).approve(chef, type(uint256).max); + ERC20(assetToken).approve(stargateRouter, type(uint256).max); + ERC20(stargateWrappedAsset).approve(rewarder, type(uint256).max); } /*////////////////////////////////////////////////////////////// @@ -66,10 +73,10 @@ contract StargateCompounder is SingleStakeBase { // Claim rewards from underlying protocol function _claimRewards() internal override { - address chef = protocolAddresses[0]; + address rewarder = protocolAddresses[0]; uint256 pid = protocolUints[0]; - IMiniChefV2(chef).harvest(pid, address(this)); + IStargateRewarder(rewarder).deposit(pid, 0); } // Swap all rewards to native token @@ -80,39 +87,33 @@ contract StargateCompounder is SingleStakeBase { address[] memory rewardRoute = rewardsToNativeRoutes[i]; uint256 rewardAmount = ERC20(reward).balanceOf(address(this)); if (rewardAmount > 0) { - _uniV2Swap(router, rewardRoute, rewardAmount); + _uniV2Swap(swapRouter, rewardRoute, rewardAmount); } } } // Swap native tokens for lpTokens function _swapNativeToAssetToken() internal override { - _uniV2Swap(router, nativeToAssetTokenRoute, ERC20(native).balanceOf(address(this))); + _uniV2Swap(swapRouter, nativeToAssetTokenRoute, ERC20(native).balanceOf(address(this))); } - // redeposit lpPair into underlying protocol + // deposit asset into underlying protocol function _deposit() internal override { - address chef = protocolAddresses[0]; + address rewarder = protocolAddresses[0]; + address stargateRouter = protocolAddresses[1]; + address stargateWrappedAsset = protocolAddresses[2]; uint256 pid = protocolUints[0]; - IMiniChefV2(chef).deposit(pid, ERC20(assetToken).balanceOf(address(this)), address(this)); + IStargateRouter(stargateRouter).addLiquidity(pid, ERC20(assetToken).balanceOf(address(this)), address(this)); + IStargateRewarder(rewarder).deposit(pid, ERC20(stargateWrappedAsset).balanceOf(address(this))); } // Return available rewards for all rewardTokens. function rewardsAvailable() public override returns (uint256[] memory) { - address chef = protocolAddresses[0]; + address rewarder = protocolAddresses[0]; uint256 pid = protocolUints[0]; - pendingRewards.push(IMiniChefV2(chef).pendingSushi(pid, address(this))); - - address rewarder = IMiniChefV2(chef).rewarder(pid); - - (, uint256[] memory rewardAmounts) = IRewarder(rewarder).pendingTokens(pid, address(this), 0); - - uint256 len = rewardAmounts.length; - for (uint256 i; i < len; ++i) { - pendingRewards.push(rewardAmounts[i]); - } + pendingRewards.push(IStargateRewarder(rewarder).pendingStargate(pid, address(this))); return pendingRewards; } From 713857f665121b11da7e63d8ed55038306397107 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Wed, 22 Feb 2023 15:23:00 -0800 Subject: [PATCH 15/21] compound logic --- .../strategy/autocompouding/singleStake/SingleStakeBase.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol b/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol index 889eb781..68c5f86c 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol @@ -35,7 +35,7 @@ contract SingleStakeBase is CompounderStrategyBase { ) internal virtual {} /*////////////////////////////////////////////////////////////// - HARVEST LOGIC + COMPOUND LOGIC //////////////////////////////////////////////////////////////*/ // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. From 69fe5dcceacbe46088153780abb9669fd04c8647 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Wed, 22 Feb 2023 15:25:09 -0800 Subject: [PATCH 16/21] typo --- .../singleStake/protocols/stargate/StargateCompounder.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol index d225c36a..f2ce1889 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol @@ -68,7 +68,7 @@ contract StargateCompounder is SingleStakeBase { } /*////////////////////////////////////////////////////////////// - HARVEST LOGIC + COMPOUND LOGIC //////////////////////////////////////////////////////////////*/ // Claim rewards from underlying protocol From 9b1fa38e561077bf3a16d2ce0d683c91acbaa840 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Thu, 23 Feb 2023 11:15:22 -0800 Subject: [PATCH 17/21] add more autocompounding revisions --- .../autocompouding/CompounderStrategyBase.sol | 17 ++++--- .../liquidityPair/LiquidityPairBase.sol | 41 ++++++++++++++-- .../protocols/sushi/MiniChefV2Compounder.sol | 48 +++++++------------ .../singleStake/SingleStakeBase.sol | 23 ++++++++- .../protocols/stargate/StargateCompounder.sol | 30 +++++------- 5 files changed, 100 insertions(+), 59 deletions(-) diff --git a/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol index 11b78260..2e77d6ae 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol @@ -79,13 +79,14 @@ contract CompounderStrategyBase { HARVEST LOGIC //////////////////////////////////////////////////////////////*/ - // harvest rewards. + // Harvest rewards. function harvest() public virtual { if (_rewardsCheck() == true && _rewardRoutesCheck() == true) { _compound(); } lastHarvest = block.timestamp; + if (isVaultFunctional == false) isVaultFunctional = true; emit Harvest(); @@ -94,13 +95,13 @@ contract CompounderStrategyBase { // Logic to claim rewards, swap rewards to native, charge fees, swap native to deposit token, add liquidity (if necessary), and re-deposit. function _compound() internal virtual {} - // Swap all rewards to native token + // Swap all rewards to native token. function _swapRewardsToNative() internal virtual {} - // Claim rewards from underlying protocol + // Claim rewards from underlying protocol. function _claimRewards() internal virtual {} - // deposit lpPair into underlying protocol + // Deposit assetToken or lpPair into underlying protocol. function _deposit() internal virtual {} /*////////////////////////////////////////////////////////////// @@ -110,7 +111,7 @@ contract CompounderStrategyBase { // Return available rewards for all rewardTokens. function rewardsAvailable() public virtual returns (uint256[] memory) {} - // Set rewards tokens according to rewardToNativeRoutes + // Set rewards tokens according to rewardToNativeRoutes. function _setRewardTokens(address[][] memory _rewardsToNativeRoutes) internal virtual { uint256 len = _rewardsToNativeRoutes.length; for (uint256 i; i < len; ++i) { @@ -148,12 +149,14 @@ contract CompounderStrategyBase { } // Set rewardRoute at index. - function setRewardRoute(uint256 rewardIndex, address[] calldata route) public virtual { + function setRewardsToNativeRoute(uint256 rewardIndex, address[] calldata route) public virtual { rewardsToNativeRoutes[rewardIndex] = route; + + if (isVaultFunctional == true) isVaultFunctional = false; } // Get rewardRoute at index. - function getRewardRoute(uint256 rewardIndex) public view virtual returns (address[] memory) { + function getRewardsToNativeRoute(uint256 rewardIndex) public view virtual returns (address[] memory) { return rewardsToNativeRoutes[rewardIndex]; } diff --git a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol index 23d37b97..3b6c25a4 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol @@ -10,6 +10,7 @@ import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openze import { CompounderStrategyBase } from "../CompounderStrategyBase.sol"; +import { IUniswapV2Pair } from "../../../../interfaces/external/uni/IUniswapV2Pair.sol"; import { IUniswapRouterV2 } from "../../../../interfaces/external/uni/IUniswapRouterV2.sol"; import { IAdapter } from "../../../../interfaces/vault/IAdapter.sol"; import { IWithRewards } from "../../../../interfaces/vault/IWithRewards.sol"; @@ -32,10 +33,42 @@ contract LiquidityPairBase is CompounderStrategyBase { // Setup for routes and allowances in constructor. function _setUp( - address[][] memory _rewardToNativeRoutes, + address[][] memory _rewardsToNativeRoutes, address[] memory _nativeToLp0Route, address[] memory _nativeToLp1Route - ) internal virtual {} + ) internal virtual { + lpToken0 = IUniswapV2Pair(lpPair).token0(); + if (_nativeToLp0Route[0] != native) revert InvalidRoute(); + if (_nativeToLp0Route[_nativeToLp0Route.length - 1] != lpToken0) revert InvalidRoute(); + nativeToLp0Route = _nativeToLp0Route; + + lpToken1 = IUniswapV2Pair(lpPair).token0(); + if (_nativeToLp1Route[0] != native) revert InvalidRoute(); + if (_nativeToLp1Route[_nativeToLp1Route.length - 1] != lpToken1) revert InvalidRoute(); + nativeToLp1Route = _nativeToLp1Route; + + _setRewardTokens(_rewardsToNativeRoutes); + + _giveInitialAllowances(); + } + + /*////////////////////////////////////////////////////////////// + ROUTES + //////////////////////////////////////////////////////////////*/ + + // Set nativeToLp0Route. + function setNativeToLp0TokenRoute(address[] calldata route) public virtual { + nativeToLp0Route = route; + + if (isVaultFunctional == true) isVaultFunctional = false; + } + + // Set nativeToLp1Route at index. + function setNativeToLp1TokenRoute(address[] calldata route) public virtual { + nativeToLp1Route = route; + + if (isVaultFunctional == true) isVaultFunctional = false; + } /*////////////////////////////////////////////////////////////// COMPOUND LOGIC @@ -50,10 +83,10 @@ contract LiquidityPairBase is CompounderStrategyBase { _deposit(); } - // Swap native tokens for lpTokens + // Swap native tokens for lpTokens. function _swapNativeToLpTokens() internal virtual {} - // Use lpTokens to create lpPair + // Use lpTokens to create lpPair. function _addLiquidity() internal virtual {} /*////////////////////////////////////////////////////////////// diff --git a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol index 4b7ce955..c2b37e33 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol @@ -40,31 +40,6 @@ contract LiquidityPairCompounder is LiquidityPairBase { SETUP //////////////////////////////////////////////////////////////*/ - // Setup for routes and allowances in constructor. - function _setUp( - address[][] memory _rewardsToNativeRoutes, - address[] memory _nativeToLp0Route, - address[] memory _nativeToLp1Route - ) internal override { - lpToken0 = IUniswapV2Pair(lpPair).token0(); - if (_nativeToLp0Route[0] != native) revert InvalidRoute(); - if (_nativeToLp0Route[_nativeToLp0Route.length - 1] != lpToken0) revert InvalidRoute(); - nativeToLp0Route = _nativeToLp0Route; - - lpToken1 = IUniswapV2Pair(lpPair).token0(); - if (_nativeToLp1Route[0] != native) revert InvalidRoute(); - if (_nativeToLp1Route[_nativeToLp1Route.length - 1] != lpToken1) revert InvalidRoute(); - nativeToLp1Route = _nativeToLp1Route; - - uint256 len = _rewardsToNativeRoutes.length; - for (uint256 i; i < len; ++i) { - if (_rewardsToNativeRoutes[i][_rewardsToNativeRoutes.length - 1] != native) revert InvalidRoute(); - rewardTokens[i] = rewardsToNativeRoutes[i][0]; - } - - _giveInitialAllowances(); - } - // Give allowances for protocol deposit and rewardToken swaps. function _giveAllowances() internal override { address chef = protocolAddresses[0]; @@ -79,7 +54,7 @@ contract LiquidityPairCompounder is LiquidityPairBase { HARVEST LOGIC //////////////////////////////////////////////////////////////*/ - // Claim rewards from underlying protocol + // Claim rewards from underlying protocol. function _claimRewards() internal override { address chef = protocolAddresses[0]; uint256 pid = protocolUints[0]; @@ -87,7 +62,7 @@ contract LiquidityPairCompounder is LiquidityPairBase { IMiniChefV2(chef).harvest(pid, address(this)); } - // Swap all rewards to native token + // Swap all rewards to native token. function _swapRewardsToNative() internal override { uint256 len = rewardsToNativeRoutes.length; for (uint256 i; i < len; ++i) { @@ -100,18 +75,18 @@ contract LiquidityPairCompounder is LiquidityPairBase { } } - // Swap native tokens for lpTokens + // Swap native tokens for lpTokens. function _swapNativeToLpTokens() internal override { _uniV2Swap(swapRouter, nativeToLp0Route, ERC20(native).balanceOf(address(this)) / 2); _uniV2Swap(swapRouter, nativeToLp1Route, ERC20(native).balanceOf(address(this))); } - // Use lpTokens to create lpPair + // Use lpTokens to create lpPair. function _addLiquidity() internal override { _uniV2AddLiquidity(swapRouter, lpToken0, lpToken1); } - // redeposit lpPair into underlying protocol + // Deposit lpPair into underlying protocol. function _deposit() internal override { address chef = protocolAddresses[0]; uint256 pid = protocolUints[0]; @@ -137,4 +112,17 @@ contract LiquidityPairCompounder is LiquidityPairBase { return pendingRewards; } + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + // Calculate the total underlaying 'want' held by the strat. + function balanceOf() public view override returns (uint256) {} + + // Calculates how much 'want' this contract holds. + function balanceOfWant() public view override returns (uint256) {} + + // Calculates how much 'want' the strategy has working in the farm. + function balanceOfPool() public view override returns (uint256) {} } diff --git a/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol b/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol index 68c5f86c..99f52a55 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol @@ -32,7 +32,28 @@ contract SingleStakeBase is CompounderStrategyBase { address[][] memory _rewardToNativeRoutes, address[] memory _nativeToAssetTokenRoute, address _assetToken - ) internal virtual {} + ) internal virtual { + if (_nativeToAssetTokenRoute[0] != native) revert InvalidRoute(); + + assetToken = _assetToken; + + if (_nativeToAssetTokenRoute[_nativeToAssetTokenRoute.length - 1] != _assetToken) revert InvalidRoute(); + + _setRewardTokens(_rewardToNativeRoutes); + + _giveInitialAllowances(); + } + + /*////////////////////////////////////////////////////////////// + ROUTES + //////////////////////////////////////////////////////////////*/ + + // Set nativeToLp0Route. + function setNativeToAssetTokenRoute(address[] calldata route) public virtual { + nativeToAssetTokenRoute = route; + + if (isVaultFunctional == true) isVaultFunctional = false; + } /*////////////////////////////////////////////////////////////// COMPOUND LOGIC diff --git a/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol index f2ce1889..30b3555b 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol @@ -40,23 +40,6 @@ contract StargateCompounder is SingleStakeBase { SETUP //////////////////////////////////////////////////////////////*/ - // Setup for routes and allowances in constructor. - function _setUp( - address[][] memory _rewardToNativeRoutes, - address[] memory _nativeToAssetTokenRoute, - address _assetToken - ) internal override { - if (_nativeToAssetTokenRoute[0] != native) revert InvalidRoute(); - - assetToken = _assetToken; - - if (_nativeToAssetTokenRoute[_nativeToAssetTokenRoute.length - 1] != _assetToken) revert InvalidRoute(); - - _setRewardTokens(_rewardToNativeRoutes); - - _giveInitialAllowances(); - } - // Give allowances for protocol deposit and rewardToken swaps. function _giveAllowances() internal override { address rewarder = protocolAddresses[0]; @@ -117,4 +100,17 @@ contract StargateCompounder is SingleStakeBase { return pendingRewards; } + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + // Calculate the total underlaying 'want' held by the strat. + function balanceOf() public view override returns (uint256) {} + + // Calculates how much 'want' this contract holds. + function balanceOfWant() public view override returns (uint256) {} + + // Calculates how much 'want' the strategy has working in the farm. + function balanceOfPool() public view override returns (uint256) {} } From de886c82bc9a1aa56c2288a18ad06b8c466be3c1 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Fri, 3 Mar 2023 19:30:16 -0800 Subject: [PATCH 18/21] add trademodules and reorg --- .../interfaces/modules/IUniswapV2Module.sol | 16 ++++++ .../foundry/src/interfaces/vault/IAdapter.sol | 44 ++++++++------- .../autocompouding/CompounderStrategyBase.sol | 44 +++++++++------ .../liquidityPair/LiquidityPairBase.sol | 26 --------- .../protocols/sushi/MiniChefV2Compounder.sol | 56 ++++++++----------- .../protocols/stargate/StargateCompounder.sol | 53 ++++++------------ .../vault/strategy/trade/UniV2TradeModule.sol | 32 +++++++++++ 7 files changed, 139 insertions(+), 132 deletions(-) create mode 100644 packages/foundry/src/interfaces/modules/IUniswapV2Module.sol diff --git a/packages/foundry/src/interfaces/modules/IUniswapV2Module.sol b/packages/foundry/src/interfaces/modules/IUniswapV2Module.sol new file mode 100644 index 00000000..d596f60d --- /dev/null +++ b/packages/foundry/src/interfaces/modules/IUniswapV2Module.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IUniswapV2Module { + function swap( + address _router, + address[] memory _route, + uint256 _amount + ) external; + + function addLiquidity( + address _router, + address _lpToken0, + address _lpToken1 + ) external; +} diff --git a/packages/foundry/src/interfaces/vault/IAdapter.sol b/packages/foundry/src/interfaces/vault/IAdapter.sol index f5c122f9..dadcf0cc 100644 --- a/packages/foundry/src/interfaces/vault/IAdapter.sol +++ b/packages/foundry/src/interfaces/vault/IAdapter.sol @@ -3,39 +3,41 @@ pragma solidity ^0.8.15; -import {IOwned} from "../IOwned.sol"; -import {IERC4626} from "./IERC4626.sol"; -import {IPermit} from "../IPermit.sol"; -import {IPausable} from "../IPausable.sol"; +import { IOwned } from "../IOwned.sol"; +import { IERC4626 } from "./IERC4626.sol"; +import { IPermit } from "../IPermit.sol"; +import { IPausable } from "../IPausable.sol"; interface IAdapter is IERC4626, IOwned, IPermit, IPausable { - function strategy() external view returns (address); + function strategy() external view returns (address); - function strategyConfig() external view returns (bytes memory); + function strategyConfig() external view returns (bytes memory); - function strategyDeposit(uint256 assets, uint256 shares) external; + function strategyDeposit(uint256 assets, uint256 shares) external; - function strategyWithdraw(uint256 assets, uint256 shares) external; + function strategyWithdraw(uint256 assets, uint256 shares) external; - function supportsInterface(bytes4 interfaceId) external view returns (bool); + function claim() external; - function setPerformanceFee(uint256 fee) external; + function supportsInterface(bytes4 interfaceId) external view returns (bool); - function performanceFee() external view returns (uint256); + function setPerformanceFee(uint256 fee) external; - function highWaterMark() external view returns (uint256); + function performanceFee() external view returns (uint256); - function accruedPerformanceFee() external view returns (uint256); + function highWaterMark() external view returns (uint256); - function harvest() external; + function accruedPerformanceFee() external view returns (uint256); - function harvestCooldown() external view returns (uint256); + function harvest() external; - function setHarvestCooldown(uint256 harvestCooldown) external; + function harvestCooldown() external view returns (uint256); - function initialize( - bytes memory adapterBaseData, - address externalRegistry, - bytes memory adapterData - ) external; + function setHarvestCooldown(uint256 harvestCooldown) external; + + function initialize( + bytes memory adapterBaseData, + address externalRegistry, + bytes memory adapterData + ) external; } diff --git a/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol index 2e77d6ae..8f0b7cdb 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol @@ -23,8 +23,9 @@ contract CompounderStrategyBase { address public vault; address public strategist; - // Router and Routes - address public swapRouter; + // Swapping + address[] public tradeModules; + address[] public routers; /** * @dev rewardToken index must match respective index in rewardRoutes and pendingRewards. * @dev Routes follow this pattern: [rewardToken, ...hops, native] @@ -63,6 +64,8 @@ contract CompounderStrategyBase { // Give allowances for rewardToken swaps. function giveRewardAllowances() public { + address swapRouter = routers[0]; + uint256 len = rewardTokens.length; for (uint256 i; i < len; ++i) { ERC20(rewardTokens[i]).approve(swapRouter, type(uint256).max); @@ -96,13 +99,32 @@ contract CompounderStrategyBase { function _compound() internal virtual {} // Swap all rewards to native token. - function _swapRewardsToNative() internal virtual {} + function _swapRewardsToNative() internal virtual { + uint256 len = rewardsToNativeRoutes.length; + for (uint256 i; i < len; ++i) { + address reward = rewardsToNativeRoutes[i][0]; + address[] memory rewardRoute = rewardsToNativeRoutes[i]; + uint256 rewardAmount = ERC20(reward).balanceOf(address(this)); + if (rewardAmount > 0) { + _swapRewardsToNative(rewardRoute, rewardAmount); + } + } + } + + function _swapRewardsToNative(address[] memory _rewardRoute, uint256 rewardAmount) internal virtual {} // Claim rewards from underlying protocol. - function _claimRewards() internal virtual {} + function _claimRewards() internal virtual { + IAdapter(address(this)).claim(); + } // Deposit assetToken or lpPair into underlying protocol. - function _deposit() internal virtual {} + function _deposit() internal virtual { + uint256 assets = 1e18; + uint256 shares = 1e18; + + IAdapter(address(this)).strategyDeposit(assets, shares); + } /*////////////////////////////////////////////////////////////// REWARDS AND ROUTES @@ -172,16 +194,4 @@ contract CompounderStrategyBase { // Calculates how much 'want' the strategy has working in the farm. function balanceOfPool() public view virtual returns (uint256) {} - - /*////////////////////////////////////////////////////////////// - SWAPPING LOGIC - //////////////////////////////////////////////////////////////*/ - // Swap compatible with UniswapV2 interface. - function _uniV2Swap( - address _router, - address[] memory _route, - uint256 _amount - ) internal { - IUniswapRouterV2(_router).swapExactTokensForTokens(_amount, 0, _route, address(this), block.timestamp + 60); - } } diff --git a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol index 3b6c25a4..16bed389 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol @@ -88,30 +88,4 @@ contract LiquidityPairBase is CompounderStrategyBase { // Use lpTokens to create lpPair. function _addLiquidity() internal virtual {} - - /*////////////////////////////////////////////////////////////// - LIQUIDITY LOGIC - //////////////////////////////////////////////////////////////*/ - // Add liquidity to UniswapV2-compatible protocol. - function _uniV2AddLiquidity( - address _router, - address _lpToken0, - address _lpToken1 - ) internal { - uint256 lpToken0Amount = ERC20(_lpToken0).balanceOf(address(this)); - uint256 lpToken1Amount = ERC20(_lpToken1).balanceOf(address(this)); - - if (lpToken0Amount > 0 && lpToken1Amount > 0) { - IUniswapRouterV2(_router).addLiquidity( - _lpToken0, - _lpToken1, - lpToken0Amount, - lpToken1Amount, - 0, - 0, - address(this), - block.timestamp + 60 - ); - } - } } diff --git a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol index c2b37e33..63b6217e 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol @@ -7,12 +7,14 @@ import { IUniswapRouterV2 } from "../../../../../../interfaces/external/uni/IUni import { IUniswapV2Pair } from "../../../../../../interfaces/external/uni/IUniswapV2Pair.sol"; import { IMiniChefV2 } from "../../../../../../interfaces/external/sushi/IMiniChefV2.sol"; import { IRewarder } from "../../../../../../interfaces/external/IRewarder.sol"; +import { IUniswapV2Module } from "../../../../../../interfaces/modules/IUniswapV2Module.sol"; contract LiquidityPairCompounder is LiquidityPairBase { constructor( address _native, address _lpPair, - address _swapRouter, + address[] memory _tradeModules, + address[] memory _routers, address _vault, address _strategist, address[] memory _protocolAddresses, @@ -23,7 +25,9 @@ contract LiquidityPairCompounder is LiquidityPairBase { ) public { native = _native; lpPair = _lpPair; - swapRouter = _swapRouter; + + tradeModules = _tradeModules; + routers = _routers; vault = _vault; strategist = _strategist; @@ -42,56 +46,42 @@ contract LiquidityPairCompounder is LiquidityPairBase { // Give allowances for protocol deposit and rewardToken swaps. function _giveAllowances() internal override { + address uniV2Router = routers[0]; address chef = protocolAddresses[0]; ERC20(lpPair).approve(chef, type(uint256).max); - ERC20(native).approve(swapRouter, type(uint256).max); - ERC20(lpToken0).approve(swapRouter, type(uint256).max); - ERC20(lpToken1).approve(swapRouter, type(uint256).max); + ERC20(native).approve(uniV2Router, type(uint256).max); + ERC20(lpToken0).approve(uniV2Router, type(uint256).max); + ERC20(lpToken1).approve(uniV2Router, type(uint256).max); } /*////////////////////////////////////////////////////////////// HARVEST LOGIC //////////////////////////////////////////////////////////////*/ - // Claim rewards from underlying protocol. - function _claimRewards() internal override { - address chef = protocolAddresses[0]; - uint256 pid = protocolUints[0]; - - IMiniChefV2(chef).harvest(pid, address(this)); - } - // Swap all rewards to native token. - function _swapRewardsToNative() internal override { - uint256 len = rewardsToNativeRoutes.length; - for (uint256 i; i < len; ++i) { - address reward = rewardsToNativeRoutes[i][0]; - address[] memory rewardRoute = rewardsToNativeRoutes[i]; - uint256 rewardAmount = ERC20(reward).balanceOf(address(this)); - if (rewardAmount > 0) { - _uniV2Swap(swapRouter, rewardRoute, rewardAmount); - } - } + function _swapRewardsToNative(address[] memory _rewardRoute, uint256 _rewardAmount) internal override { + IUniswapV2Module uniV2Module = IUniswapV2Module(tradeModules[0]); + address uniV2Router = routers[0]; + + uniV2Module.swap(uniV2Router, _rewardRoute, _rewardAmount); } // Swap native tokens for lpTokens. function _swapNativeToLpTokens() internal override { - _uniV2Swap(swapRouter, nativeToLp0Route, ERC20(native).balanceOf(address(this)) / 2); - _uniV2Swap(swapRouter, nativeToLp1Route, ERC20(native).balanceOf(address(this))); + IUniswapV2Module uniV2Module = IUniswapV2Module(tradeModules[0]); + address uniV2Router = routers[0]; + + uniV2Module.swap(uniV2Router, nativeToLp0Route, ERC20(native).balanceOf(address(this)) / 2); + uniV2Module.swap(uniV2Router, nativeToLp1Route, ERC20(native).balanceOf(address(this))); } // Use lpTokens to create lpPair. function _addLiquidity() internal override { - _uniV2AddLiquidity(swapRouter, lpToken0, lpToken1); - } - - // Deposit lpPair into underlying protocol. - function _deposit() internal override { - address chef = protocolAddresses[0]; - uint256 pid = protocolUints[0]; + IUniswapV2Module uniV2Module = IUniswapV2Module(tradeModules[0]); + address uniV2Router = routers[0]; - IMiniChefV2(chef).deposit(pid, ERC20(lpPair).balanceOf(address(this)), address(this)); + uniV2Module.addLiquidity(uniV2Router, lpToken0, lpToken1); } // Return available rewards for all rewardTokens. diff --git a/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol index 30b3555b..5e80a0ab 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol @@ -8,12 +8,14 @@ import { IUniswapV2Pair } from "../../../../../../interfaces/external/uni/IUnisw import { IStargateRewarder } from "../../../../../../interfaces/external/stargate/IStargateRewarder.sol"; import { IStargateRouter } from "../../../../../../interfaces/external/stargate/IStargateRouter.sol"; import { IRewarder } from "../../../../../../interfaces/external/IRewarder.sol"; +import { IUniswapV2Module } from "../../../../../../interfaces/modules/IUniswapV2Module.sol"; contract StargateCompounder is SingleStakeBase { constructor( address _native, address _assetToken, - address _swapRouter, + address[] memory _tradeModules, + address[] memory _routers, address _vault, address _strategist, address[] memory _protocolAddresses, @@ -22,7 +24,9 @@ contract StargateCompounder is SingleStakeBase { address[] memory _nativeToAssetTokenRoute ) public { native = _native; - swapRouter = _swapRouter; + + tradeModules = _tradeModules; + routers = _routers; vault = _vault; strategist = _strategist; @@ -42,61 +46,40 @@ contract StargateCompounder is SingleStakeBase { // Give allowances for protocol deposit and rewardToken swaps. function _giveAllowances() internal override { - address rewarder = protocolAddresses[0]; + address stargateRewarder = protocolAddresses[0]; address stargateRouter = protocolAddresses[1]; address stargateWrappedAsset = protocolAddresses[2]; ERC20(assetToken).approve(stargateRouter, type(uint256).max); - ERC20(stargateWrappedAsset).approve(rewarder, type(uint256).max); + ERC20(stargateWrappedAsset).approve(stargateRewarder, type(uint256).max); } /*////////////////////////////////////////////////////////////// COMPOUND LOGIC //////////////////////////////////////////////////////////////*/ - // Claim rewards from underlying protocol - function _claimRewards() internal override { - address rewarder = protocolAddresses[0]; - uint256 pid = protocolUints[0]; - - IStargateRewarder(rewarder).deposit(pid, 0); - } - // Swap all rewards to native token - function _swapRewardsToNative() internal override { - uint256 len = rewardsToNativeRoutes.length; - for (uint256 i; i < len; ++i) { - address reward = rewardsToNativeRoutes[i][0]; - address[] memory rewardRoute = rewardsToNativeRoutes[i]; - uint256 rewardAmount = ERC20(reward).balanceOf(address(this)); - if (rewardAmount > 0) { - _uniV2Swap(swapRouter, rewardRoute, rewardAmount); - } - } + function _swapRewardsToNative(address[] memory _rewardRoute, uint256 _rewardAmount) internal override { + IUniswapV2Module uniV2Module = IUniswapV2Module(tradeModules[0]); + address uniV2Router = routers[0]; + + uniV2Module.swap(uniV2Router, _rewardRoute, _rewardAmount); } // Swap native tokens for lpTokens function _swapNativeToAssetToken() internal override { - _uniV2Swap(swapRouter, nativeToAssetTokenRoute, ERC20(native).balanceOf(address(this))); - } - - // deposit asset into underlying protocol - function _deposit() internal override { - address rewarder = protocolAddresses[0]; - address stargateRouter = protocolAddresses[1]; - address stargateWrappedAsset = protocolAddresses[2]; - uint256 pid = protocolUints[0]; + IUniswapV2Module uniV2Module = IUniswapV2Module(tradeModules[0]); + address uniV2Router = routers[0]; - IStargateRouter(stargateRouter).addLiquidity(pid, ERC20(assetToken).balanceOf(address(this)), address(this)); - IStargateRewarder(rewarder).deposit(pid, ERC20(stargateWrappedAsset).balanceOf(address(this))); + uniV2Module.swap(uniV2Router, nativeToAssetTokenRoute, ERC20(native).balanceOf(address(this))); } // Return available rewards for all rewardTokens. function rewardsAvailable() public override returns (uint256[] memory) { - address rewarder = protocolAddresses[0]; + IStargateRewarder stargateRewarder = IStargateRewarder(protocolAddresses[0]); uint256 pid = protocolUints[0]; - pendingRewards.push(IStargateRewarder(rewarder).pendingStargate(pid, address(this))); + pendingRewards.push(stargateRewarder.pendingStargate(pid, address(this))); return pendingRewards; } diff --git a/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol b/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol index 3761b25f..9d9f9001 100644 --- a/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol +++ b/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol @@ -36,4 +36,36 @@ contract UniV2TradeModule { IERC20(tradePath[len - 1]).transfer(msg.sender, amountsOut[len - 1]); return amountsOut[len - 1]; } + + // Swap compatible with UniswapV2 interface. + function swap( + address _router, + address[] memory _route, + uint256 _amount + ) external { + IUniswapRouterV2(_router).swapExactTokensForTokens(_amount, 0, _route, address(this), block.timestamp + 60); + } + + // Add liquidity to UniswapV2-compatible protocol. + function addLiquidity( + address _router, + address _lpToken0, + address _lpToken1 + ) external { + uint256 lpToken0Amount = IERC20(_lpToken0).balanceOf(address(this)); + uint256 lpToken1Amount = IERC20(_lpToken1).balanceOf(address(this)); + + if (lpToken0Amount > 0 && lpToken1Amount > 0) { + IUniswapRouterV2(_router).addLiquidity( + _lpToken0, + _lpToken1, + lpToken0Amount, + lpToken1Amount, + 0, + 0, + address(this), + block.timestamp + 60 + ); + } + } } From 97957cdbf50e9d4776ba6945d24a99c871697ee2 Mon Sep 17 00:00:00 2001 From: RedVeil Date: Sun, 5 Mar 2023 22:12:23 +0100 Subject: [PATCH 19/21] new Compounder version --- .../autocompouding/CompounderStrategyBase.sol | 2 +- .../compounder/BaseToAssetCompounder.sol | 164 ++++++++++++++ .../compounder/BaseToLpCompounder.sol | 201 ++++++++++++++++++ .../strategy/compounder/pool/UniV2Pool.sol | 43 ++++ .../compounder/router/UniV2Router.sol | 38 ++++ 5 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 packages/foundry/src/vault/strategy/compounder/BaseToAssetCompounder.sol create mode 100644 packages/foundry/src/vault/strategy/compounder/BaseToLpCompounder.sol create mode 100644 packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol create mode 100644 packages/foundry/src/vault/strategy/compounder/router/UniV2Router.sol diff --git a/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol index 8f0b7cdb..04b7894a 100644 --- a/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol +++ b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol @@ -115,7 +115,7 @@ contract CompounderStrategyBase { // Claim rewards from underlying protocol. function _claimRewards() internal virtual { - IAdapter(address(this)).claim(); + IWithRewards(address(this)).claim(); } // Deposit assetToken or lpPair into underlying protocol. diff --git a/packages/foundry/src/vault/strategy/compounder/BaseToAssetCompounder.sol b/packages/foundry/src/vault/strategy/compounder/BaseToAssetCompounder.sol new file mode 100644 index 00000000..a7433187 --- /dev/null +++ b/packages/foundry/src/vault/strategy/compounder/BaseToAssetCompounder.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { IEIP165 } from "../../../interfaces/IEIP165.sol"; +import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20, IERC20Upgradeable as IERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { StrategyBase } from "../StrategyBase.sol"; + +interface IRouter { + function trade( + address[] memory route, + uint256 amount, + uint256 minAmount + ) external returns (uint256); +} + +contract BaseToAssetCompounder is StrategyBase { + // Events + event Harvest(); + + // Errors + error InvalidRoute(); + error InvalidConfig(); + + function verifyAdapterCompatibility(bytes memory data) public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address[4][5] memory toBaseAssetRoutes, + address[4] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8 toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address[4][5], address[4], uint8[5], uint8, uint256[5]) + ); + + address asset = IAdapter(address(this)).asset(); + + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + + uint256 len = rewardTokens.length; + for (uint256 i; i < len; i++) { + if (toBaseAssetRoutes[i][0] != rewardTokens[i]) revert InvalidConfig(); + if (toBaseAssetRoutes[i][toBaseAssetLen[i] - 1] != baseAsset) revert InvalidConfig(); + } + + if (toAssetRoute[0] != baseAsset) revert InvalidConfig(); + if (toAssetRoute[toAssetLen - 1] != asset) revert InvalidConfig(); + } + + function setUp(bytes memory data) public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address[4][5] memory toBaseAssetRoutes, + address[4] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8 toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address[4][5], address[4], uint8[5], uint8, uint256[5]) + ); + + // Approve all rewardsToken for trading + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + uint256 len = rewardTokens.length; + for (uint256 i = 0; i < len; i++) { + IERC20(rewardTokens[i]).approve(toBaseAssetRouter, type(uint256).max); + } + IERC20(baseAsset).approve(toAssetRouter, type(uint256).max); + } + + /*////////////////////////////////////////////////////////////// + HARVEST LOGIC + //////////////////////////////////////////////////////////////*/ + + // Harvest rewards. + function harvest() public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address[4][5] memory toBaseAssetRoutes, + address[4] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8 toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address[4][5], address[4], uint8[5], uint8, uint256[5]) + ); + address asset = IAdapter(address(this)).asset(); + uint256 balBefore = IERC20(asset).balanceOf(address(this)); + + IWithRewards(address(this)).claim(); + + _swapRewardsToBase(toBaseAssetRouter, toBaseAssetRoutes, toBaseAssetLen, minTradeAmounts); + + _swapBaseToAsset(toAssetRouter, baseAsset, toAssetRoute, toAssetLen); + + uint256 balAfter = IERC20(asset).balanceOf(address(this)); + + IAdapter(address(this)).strategyDeposit(balAfter - balBefore, 0); + + emit Harvest(); + } + + // Swap all rewards to base asset. + function _swapRewardsToBase( + address toBaseAssetRouter, + address[4][5] memory toBaseAssetRoutes, + uint8[5] memory toBaseAssetLen, + uint256[5] memory minTradeAmounts + ) internal virtual { + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + + uint256 len = rewardTokens.length; + for (uint256 i = 0; i < len; i++) { + uint256 rewardAmount = IERC20(rewardTokens[i]).balanceOf(address(this)); + + if (rewardAmount > minTradeAmounts[i]) { + // clean up trade route + address[] memory route = new address[](toBaseAssetLen[i]); + for (uint256 idx; idx < toBaseAssetLen[i]; idx++) { + route[idx] = toBaseAssetRoutes[i][idx]; + } + + // route, amount, minOut + IRouter(toBaseAssetRouter).trade(route, rewardAmount, 0); + } + } + } + + function _swapBaseToAsset( + address toAssetRouter, + address baseAsset, + address[4] memory toAssetRoute, + uint8 toAssetLen + ) internal virtual { + uint256 amount = IERC20(baseAsset).balanceOf(address(this)); + + if (amount > 0) { + // clean up trade route + address[] memory route = new address[](toAssetLen); + for (uint256 i; i < toAssetLen; i++) { + route[i] = toAssetRoute[i]; + } + + // route, amount, minOut + IRouter(toAssetRouter).trade(route, amount, 0); + } + } +} diff --git a/packages/foundry/src/vault/strategy/compounder/BaseToLpCompounder.sol b/packages/foundry/src/vault/strategy/compounder/BaseToLpCompounder.sol new file mode 100644 index 00000000..a29c9cf2 --- /dev/null +++ b/packages/foundry/src/vault/strategy/compounder/BaseToLpCompounder.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { IEIP165 } from "../../../interfaces/IEIP165.sol"; +import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20, IERC20Upgradeable as IERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { StrategyBase } from "../StrategyBase.sol"; + +interface IRouter { + function trade( + address[] memory route, + uint256 amount, + uint256 minAmount + ) external returns (uint256); +} + +interface IPool { + function addLiquidity( + address lpToken, + address[2] memory underlyingTokens, + uint256 bal0, + uint256 bal1 + ) external returns (uint256); + + function underlyingTokens(address lpToken) external returns (address[2] memory); +} + +contract BaseToLpCompounder is StrategyBase { + // Events + event Harvest(); + + // Errors + error InvalidRoute(); + error InvalidConfig(); + + function verifyAdapterCompatibility(bytes memory data) public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address pool, + address[4][5] memory toBaseAssetRoutes, + address[4][2] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8[2] memory toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address, address[4][5], address[4][2], uint8[5], uint8[2], uint256[5]) + ); + + address asset = IAdapter(address(this)).asset(); + + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + + uint256 len = rewardTokens.length; + for (uint256 i; i < len; i++) { + if (toBaseAssetRoutes[i][0] != rewardTokens[i]) revert InvalidConfig(); + if (toBaseAssetRoutes[i][toBaseAssetLen[i] - 1] != baseAsset) revert InvalidConfig(); + } + + address[2] memory underlyingTokens = IPool(pool).underlyingTokens(IAdapter(address(this)).asset()); + + if (toAssetRoute[0][0] != baseAsset) revert InvalidConfig(); + if (toAssetRoute[1][0] != baseAsset) revert InvalidConfig(); + if (toAssetRoute[0][toAssetLen[0]] != underlyingTokens[0]) revert InvalidConfig(); + if (toAssetRoute[1][toAssetLen[1]] != underlyingTokens[1]) revert InvalidConfig(); + } + + function setUp(bytes memory data) public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address pool, + address[4][5] memory toBaseAssetRoutes, + address[4][2] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8[2] memory toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address, address[4][5], address[4][2], uint8[5], uint8[2], uint256[5]) + ); + + // Approve all rewardsToken for trading + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + uint256 len = rewardTokens.length; + for (uint256 i = 0; i < len; i++) { + IERC20(rewardTokens[i]).approve(toBaseAssetRouter, type(uint256).max); + } + IERC20(baseAsset).approve(toAssetRouter, type(uint256).max); + + address[2] memory underlyingTokens = IPool(pool).underlyingTokens(IAdapter(address(this)).asset()); + IERC20(underlyingTokens[0]).approve(pool, type(uint256).max); + IERC20(underlyingTokens[1]).approve(pool, type(uint256).max); + } + + /*////////////////////////////////////////////////////////////// + HARVEST LOGIC + //////////////////////////////////////////////////////////////*/ + + // Harvest rewards. + function harvest() public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address pool, + address[4][5] memory toBaseAssetRoutes, + address[4][2] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8[2] memory toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address, address[4][5], address[4][2], uint8[5], uint8[2], uint256[5]) + ); + + address asset = IAdapter(address(this)).asset(); + uint256 balBefore = IERC20(asset).balanceOf(address(this)); + + IWithRewards(address(this)).claim(); + + _swapRewardsToBase(toBaseAssetRouter, toBaseAssetRoutes, toBaseAssetLen, minTradeAmounts); + + uint256 baseBal = IERC20(baseAsset).balanceOf(address(this)); + uint256 allocation = baseBal / 2; + + _swapBaseToAsset(toAssetRouter, baseAsset, toAssetRoute[0], toAssetLen[0], allocation); + _swapBaseToAsset(toAssetRouter, baseAsset, toAssetRoute[1], toAssetLen[1], allocation); + + _addLiquidity(pool, asset); + + uint256 balAfter = IERC20(asset).balanceOf(address(this)); + + IAdapter(address(this)).strategyDeposit(balAfter - balBefore, 0); + + emit Harvest(); + } + + // Swap all rewards to base asset. + function _swapRewardsToBase( + address toBaseAssetRouter, + address[4][5] memory toBaseAssetRoutes, + uint8[5] memory toBaseAssetLen, + uint256[5] memory minTradeAmounts + ) internal virtual { + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + + uint256 len = rewardTokens.length; + for (uint256 i = 0; i < len; i++) { + uint256 rewardAmount = IERC20(rewardTokens[i]).balanceOf(address(this)); + + if (rewardAmount > minTradeAmounts[i]) { + // clean up trade route + address[] memory route = new address[](toBaseAssetLen[i]); + for (uint256 idx; idx < toBaseAssetLen[i]; idx++) { + route[idx] = toBaseAssetRoutes[i][idx]; + } + + // route, amount, minOut + IRouter(toBaseAssetRouter).trade(route, rewardAmount, 0); + } + } + } + + function _swapBaseToAsset( + address toAssetRouter, + address baseAsset, + address[4] memory toAssetRoute, + uint8 toAssetLen, + uint256 allocation + ) internal virtual { + if (allocation > 0) { + // clean up trade route + address[] memory route = new address[](toAssetLen); + for (uint256 i; i < toAssetLen; i++) { + route[i] = toAssetRoute[i]; + } + + // route, amount, minOut + IRouter(toAssetRouter).trade(route, allocation, 0); + } + } + + function _addLiquidity(address pool, address asset) internal { + address[2] memory underlyingTokens = IPool(pool).underlyingTokens(asset); + + uint256 bal0 = IERC20(underlyingTokens[0]).balanceOf(address(this)); + uint256 bal1 = IERC20(underlyingTokens[0]).balanceOf(address(this)); + + IPool(pool).addLiquidity(asset, underlyingTokens, bal0, bal1); + } +} diff --git a/packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol b/packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol new file mode 100644 index 00000000..563480b0 --- /dev/null +++ b/packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IUniswapRouterV2 } from "../../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; + +contract UniV2Router { + using SafeERC20 for IERC20; + + address router; + + constructor(address _router) { + router = _router; + } + + function addLiquidity( + address lpToken, + address[2] memory underlyingTokens, + uint256 bal0, + uint256 bal1 + ) external returns (uint256) { + IERC20(underlyingTokens[0]).transferFrom(msg.sender, address(this), bal0); + IERC20(underlyingTokens[1]).transferFrom(msg.sender, address(this), bal1); + + IUniswapRouterV2(router).addLiquidity( + underlyingTokens[0], + underlyingTokens[1], + bal0, + bal1, + 0, + 0, + address(this), + block.timestamp + ); + + uint256 bal = IERC20(lpToken).balanceOf(address(this)); + IERC20(lpToken).safeTransfer(msg.sender, bal); + return bal; + } +} diff --git a/packages/foundry/src/vault/strategy/compounder/router/UniV2Router.sol b/packages/foundry/src/vault/strategy/compounder/router/UniV2Router.sol new file mode 100644 index 00000000..9f1de946 --- /dev/null +++ b/packages/foundry/src/vault/strategy/compounder/router/UniV2Router.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IUniswapRouterV2 } from "../../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; + +contract UniV2Router { + using SafeERC20 for IERC20; + + address router; + + constructor(address _router) { + router = _router; + } + + function trade( + address[] memory route, + uint256 amount, + uint256 minAmount + ) external returns (uint256) { + IERC20(route[0]).transferFrom(msg.sender, address(this), amount); + + uint256[] memory amountsOut = IUniswapRouterV2(router).swapExactTokensForTokens( + amount, + minAmount, + route, + msg.sender, + block.timestamp + ); + + uint256 len = route.length; + IERC20(route[len - 1]).safeTransfer(msg.sender, amountsOut[len - 1]); + return amountsOut[len - 1]; + } +} From 9e7de9c9d0afbc4ee83b0303e1774be6b91a717d Mon Sep 17 00:00:00 2001 From: RedVeil Date: Sun, 12 Mar 2023 16:50:48 +0100 Subject: [PATCH 20/21] wip - test --- .../vault/adapter/abstracts/WithRewards.sol | 2 +- .../vault/adapter/convex/ConvexAdapter.sol | 2 +- .../test/utils/mocks/MockStrategyClaimer.sol | 28 +++++++++++++++ .../{ => adapter}/aaveV2/AaveV2Adapter.t.sol | 2 +- .../aaveV2/AaveV2TestConfigStorage.sol | 0 .../{ => adapter}/aaveV3/AaveV3Adapter.t.sol | 2 +- .../aaveV3/AaveV3TestConfigStorage.sol | 0 .../abstract/AbstractAdapterTest.sol | 10 +++--- .../abstract/ITestConfigStorage.sol | 0 .../abstract/PropertyTest.prop.sol | 2 +- .../{ => adapter}/beefy/BeefyAdapter.t.sol | 6 ++-- .../beefy/BeefyTestConfigStorage.sol | 0 .../compoundV2/CompoundV2Adapter.t.sol | 2 +- .../CompoundV2TestConfigStorage.sol | 0 .../{ => adapter}/convex/ConvexAdapter.t.sol | 35 ++++++++++++++++++- .../convex/ConvexTestConfigStorage.sol | 4 +-- .../{ => adapter}/yearn/YearnAdapter.t.sol | 2 +- .../yearn/YearnTestConfigStorage.sol | 0 .../compounder/BaseToAssetCompounder.t.sol | 13 +++++++ 19 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 packages/foundry/test/utils/mocks/MockStrategyClaimer.sol rename packages/foundry/test/vault/integration/{ => adapter}/aaveV2/AaveV2Adapter.t.sol (97%) rename packages/foundry/test/vault/integration/{ => adapter}/aaveV2/AaveV2TestConfigStorage.sol (100%) rename packages/foundry/test/vault/integration/{ => adapter}/aaveV3/AaveV3Adapter.t.sol (97%) rename packages/foundry/test/vault/integration/{ => adapter}/aaveV3/AaveV3TestConfigStorage.sol (100%) rename packages/foundry/test/vault/integration/{ => adapter}/abstract/AbstractAdapterTest.sol (98%) rename packages/foundry/test/vault/integration/{ => adapter}/abstract/ITestConfigStorage.sol (100%) rename packages/foundry/test/vault/integration/{ => adapter}/abstract/PropertyTest.prop.sol (99%) rename packages/foundry/test/vault/integration/{ => adapter}/beefy/BeefyAdapter.t.sol (96%) rename packages/foundry/test/vault/integration/{ => adapter}/beefy/BeefyTestConfigStorage.sol (100%) rename packages/foundry/test/vault/integration/{ => adapter}/compoundV2/CompoundV2Adapter.t.sol (98%) rename packages/foundry/test/vault/integration/{ => adapter}/compoundV2/CompoundV2TestConfigStorage.sol (100%) rename packages/foundry/test/vault/integration/{ => adapter}/convex/ConvexAdapter.t.sol (72%) rename packages/foundry/test/vault/integration/{ => adapter}/convex/ConvexTestConfigStorage.sol (88%) rename packages/foundry/test/vault/integration/{ => adapter}/yearn/YearnAdapter.t.sol (98%) rename packages/foundry/test/vault/integration/{ => adapter}/yearn/YearnTestConfigStorage.sol (100%) create mode 100644 packages/foundry/test/vault/integration/strategies/compounder/BaseToAssetCompounder.t.sol diff --git a/packages/foundry/src/vault/adapter/abstracts/WithRewards.sol b/packages/foundry/src/vault/adapter/abstracts/WithRewards.sol index e3b8b30a..b2099420 100644 --- a/packages/foundry/src/vault/adapter/abstracts/WithRewards.sol +++ b/packages/foundry/src/vault/adapter/abstracts/WithRewards.sol @@ -12,7 +12,7 @@ import { IAdapter } from "../../../interfaces/vault/IAdapter.sol"; contract WithRewards is EIP165, OnlyStrategy { function rewardTokens() external view virtual returns (address[] memory) {} - function claim() public virtual onlyStrategy {} + function claim() public virtual {} /*////////////////////////////////////////////////////////////// EIP-165 LOGIC diff --git a/packages/foundry/src/vault/adapter/convex/ConvexAdapter.sol b/packages/foundry/src/vault/adapter/convex/ConvexAdapter.sol index ed0ea8bb..74a43f96 100644 --- a/packages/foundry/src/vault/adapter/convex/ConvexAdapter.sol +++ b/packages/foundry/src/vault/adapter/convex/ConvexAdapter.sol @@ -125,7 +125,7 @@ contract ConvexAdapter is AdapterBase, WithRewards { error MiningNotActive(); /// @notice Claim liquidity mining rewards given that it's active - function claim() public override onlyStrategy { + function claim() public override { convexRewards.getReward(address(this), true); } diff --git a/packages/foundry/test/utils/mocks/MockStrategyClaimer.sol b/packages/foundry/test/utils/mocks/MockStrategyClaimer.sol new file mode 100644 index 00000000..edf1a19a --- /dev/null +++ b/packages/foundry/test/utils/mocks/MockStrategyClaimer.sol @@ -0,0 +1,28 @@ +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../src/interfaces/vault/IWithRewards.sol"; + + +contract MockStrategyClaimer { + event SelectorsVerified(); + event AdapterVerified(); + event StrategySetup(); + event StrategyExecuted(); + + function verifyAdapterSelectorCompatibility(bytes4[8] memory) public { + emit SelectorsVerified(); + } + + function verifyAdapterCompatibility(bytes memory) public { + emit AdapterVerified(); + } + + function setUp(bytes memory) public { + emit StrategySetup(); + } + + function harvest() public { + IWithRewards(address(this)).claim(); + emit StrategyExecuted(); + } +} diff --git a/packages/foundry/test/vault/integration/aaveV2/AaveV2Adapter.t.sol b/packages/foundry/test/vault/integration/adapter/aaveV2/AaveV2Adapter.t.sol similarity index 97% rename from packages/foundry/test/vault/integration/aaveV2/AaveV2Adapter.t.sol rename to packages/foundry/test/vault/integration/adapter/aaveV2/AaveV2Adapter.t.sol index a07bf6eb..7a0ce315 100644 --- a/packages/foundry/test/vault/integration/aaveV2/AaveV2Adapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/aaveV2/AaveV2Adapter.t.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { AaveV2Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ILendingPool, IAaveMining, IAToken, IProtocolDataProvider, DataTypes } from "../../../../src/vault/adapter/aave/aaveV2/AaveV2Adapter.sol"; +import { AaveV2Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ILendingPool, IAaveMining, IAToken, IProtocolDataProvider, DataTypes } from "../../../../../src/vault/adapter/aave/aaveV2/AaveV2Adapter.sol"; import { AaveV2TestConfigStorage, AaveV2TestConfig } from "./AaveV2TestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; diff --git a/packages/foundry/test/vault/integration/aaveV2/AaveV2TestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/aaveV2/AaveV2TestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/aaveV2/AaveV2TestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/aaveV2/AaveV2TestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/aaveV3/AaveV3Adapter.t.sol b/packages/foundry/test/vault/integration/adapter/aaveV3/AaveV3Adapter.t.sol similarity index 97% rename from packages/foundry/test/vault/integration/aaveV3/AaveV3Adapter.t.sol rename to packages/foundry/test/vault/integration/adapter/aaveV3/AaveV3Adapter.t.sol index 5ebf7e6f..d84af36d 100644 --- a/packages/foundry/test/vault/integration/aaveV3/AaveV3Adapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/aaveV3/AaveV3Adapter.t.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { AaveV3Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ILendingPool, IAaveIncentives, IAToken, IProtocolDataProvider, DataTypes } from "../../../../src/vault/adapter/aave/aaveV3/AaveV3Adapter.sol"; +import { AaveV3Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ILendingPool, IAaveIncentives, IAToken, IProtocolDataProvider, DataTypes } from "../../../../../src/vault/adapter/aave/aaveV3/AaveV3Adapter.sol"; import { AaveV3TestConfigStorage, AaveV3TestConfig } from "./AaveV3TestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; diff --git a/packages/foundry/test/vault/integration/aaveV3/AaveV3TestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/aaveV3/AaveV3TestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/aaveV3/AaveV3TestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/aaveV3/AaveV3TestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/abstract/AbstractAdapterTest.sol b/packages/foundry/test/vault/integration/adapter/abstract/AbstractAdapterTest.sol similarity index 98% rename from packages/foundry/test/vault/integration/abstract/AbstractAdapterTest.sol rename to packages/foundry/test/vault/integration/adapter/abstract/AbstractAdapterTest.sol index 10306295..e0443ba4 100644 --- a/packages/foundry/test/vault/integration/abstract/AbstractAdapterTest.sol +++ b/packages/foundry/test/vault/integration/adapter/abstract/AbstractAdapterTest.sol @@ -6,11 +6,11 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; import { PropertyTest } from "./PropertyTest.prop.sol"; -import { IAdapter, IERC4626 } from "../../../../src/interfaces/vault/IAdapter.sol"; -import { IStrategy } from "../../../../src/interfaces/vault/IStrategy.sol"; +import { IAdapter, IERC4626 } from "../../../../../src/interfaces/vault/IAdapter.sol"; +import { IStrategy } from "../../../../../src/interfaces/vault/IStrategy.sol"; import { IERC20Upgradeable as IERC20, IERC20MetadataUpgradeable as IERC20Metadata } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; import { ITestConfigStorage } from "./ITestConfigStorage.sol"; -import { MockStrategy } from "../../../utils/mocks/MockStrategy.sol"; +import { MockStrategy } from "../../../../utils/mocks/MockStrategy.sol"; import { Math } from "openzeppelin-contracts/utils/math/Math.sol"; import { Clones } from "openzeppelin-contracts/proxy/Clones.sol"; @@ -558,13 +558,13 @@ contract AbstractAdapterTest is PropertyTest { //////////////////////////////////////////////////////////////*/ // OPTIONAL - function testClaim() public virtual {} + function test__claim() public virtual {} /*////////////////////////////////////////////////////////////// PERMIT //////////////////////////////////////////////////////////////*/ - function testPermit() public { + function test__permit() public { uint256 privateKey = 0xBEEF; address owner = vm.addr(privateKey); diff --git a/packages/foundry/test/vault/integration/abstract/ITestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/abstract/ITestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/abstract/ITestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/abstract/ITestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/abstract/PropertyTest.prop.sol b/packages/foundry/test/vault/integration/adapter/abstract/PropertyTest.prop.sol similarity index 99% rename from packages/foundry/test/vault/integration/abstract/PropertyTest.prop.sol rename to packages/foundry/test/vault/integration/adapter/abstract/PropertyTest.prop.sol index b4550b03..f8bcf504 100644 --- a/packages/foundry/test/vault/integration/abstract/PropertyTest.prop.sol +++ b/packages/foundry/test/vault/integration/adapter/abstract/PropertyTest.prop.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.15; -import { EnhancedTest } from "../../../utils/EnhancedTest.sol"; +import { EnhancedTest } from "../../../../utils/EnhancedTest.sol"; import { IERC4626Upgradeable as IERC4626, IERC20Upgradeable as IERC20 } from "openzeppelin-contracts-upgradeable/interfaces/IERC4626Upgradeable.sol"; contract PropertyTest is EnhancedTest { diff --git a/packages/foundry/test/vault/integration/beefy/BeefyAdapter.t.sol b/packages/foundry/test/vault/integration/adapter/beefy/BeefyAdapter.t.sol similarity index 96% rename from packages/foundry/test/vault/integration/beefy/BeefyAdapter.t.sol rename to packages/foundry/test/vault/integration/adapter/beefy/BeefyAdapter.t.sol index 148e1560..51ba524b 100644 --- a/packages/foundry/test/vault/integration/beefy/BeefyAdapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/beefy/BeefyAdapter.t.sol @@ -5,11 +5,11 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { BeefyAdapter, SafeERC20, IERC20, IERC20Metadata, IBeefyVault, IBeefyBooster, IBeefyBalanceCheck } from "../../../../src/vault/adapter/beefy/BeefyAdapter.sol"; +import { BeefyAdapter, SafeERC20, IERC20, IERC20Metadata, IBeefyVault, IBeefyBooster, IBeefyBalanceCheck } from "../../../../../src/vault/adapter/beefy/BeefyAdapter.sol"; import { BeefyTestConfigStorage, BeefyTestConfig } from "./BeefyTestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter, Math } from "../abstract/AbstractAdapterTest.sol"; -import { IPermissionRegistry, Permission } from "../../../../src/interfaces/vault/IPermissionRegistry.sol"; -import { PermissionRegistry } from "../../../../src/vault/PermissionRegistry.sol"; +import { IPermissionRegistry, Permission } from "../../../../../src/interfaces/vault/IPermissionRegistry.sol"; +import { PermissionRegistry } from "../../../../../src/vault/PermissionRegistry.sol"; contract BeefyAdapterTest is AbstractAdapterTest { using Math for uint256; diff --git a/packages/foundry/test/vault/integration/beefy/BeefyTestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/beefy/BeefyTestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/beefy/BeefyTestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/beefy/BeefyTestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/compoundV2/CompoundV2Adapter.t.sol b/packages/foundry/test/vault/integration/adapter/compoundV2/CompoundV2Adapter.t.sol similarity index 98% rename from packages/foundry/test/vault/integration/compoundV2/CompoundV2Adapter.t.sol rename to packages/foundry/test/vault/integration/adapter/compoundV2/CompoundV2Adapter.t.sol index 36fcb8b8..20b47f23 100644 --- a/packages/foundry/test/vault/integration/compoundV2/CompoundV2Adapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/compoundV2/CompoundV2Adapter.t.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { CompoundV2Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ICToken, IComptroller } from "../../../../src/vault/adapter/compound/compoundV2/CompoundV2Adapter.sol"; +import { CompoundV2Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ICToken, IComptroller } from "../../../../../src/vault/adapter/compound/compoundV2/CompoundV2Adapter.sol"; import { CompoundV2TestConfigStorage, CompoundV2TestConfig } from "./CompoundV2TestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; diff --git a/packages/foundry/test/vault/integration/compoundV2/CompoundV2TestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/compoundV2/CompoundV2TestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/compoundV2/CompoundV2TestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/compoundV2/CompoundV2TestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/convex/ConvexAdapter.t.sol b/packages/foundry/test/vault/integration/adapter/convex/ConvexAdapter.t.sol similarity index 72% rename from packages/foundry/test/vault/integration/convex/ConvexAdapter.t.sol rename to packages/foundry/test/vault/integration/adapter/convex/ConvexAdapter.t.sol index 4a8ea268..6f982402 100644 --- a/packages/foundry/test/vault/integration/convex/ConvexAdapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/convex/ConvexAdapter.t.sol @@ -3,9 +3,11 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { ConvexAdapter, SafeERC20, IERC20, IERC20Metadata, Math, IConvexBooster, IConvexRewards } from "../../../../src/vault/adapter/convex/ConvexAdapter.sol"; +import { ConvexAdapter, SafeERC20, IERC20, IERC20Metadata, Math, IConvexBooster, IConvexRewards, IWithRewards } from "../../../../../src/vault/adapter/convex/ConvexAdapter.sol"; import { ConvexTestConfigStorage, ConvexTestConfig } from "./ConvexTestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; +import { Clones } from "openzeppelin-contracts/proxy/Clones.sol"; +import { MockStrategyClaimer } from "../../../../utils/mocks/MockStrategyClaimer.sol"; contract ConvexAdapterTest is AbstractAdapterTest { using Math for uint256; @@ -88,4 +90,35 @@ contract ConvexAdapterTest is AbstractAdapterTest { assertEq(asset.allowance(address(adapter), address(convexBooster)), type(uint256).max, "allowance"); } + + /*////////////////////////////////////////////////////////////// + CLAIM + //////////////////////////////////////////////////////////////*/ + + // OPTIONAL + function test__claim() public override { + uint256 forkId = vm.createSelectFork(vm.rpcUrl("mainnet"), 15717986); + vm.selectFork(forkId); + + testConfigStorage = ITestConfigStorage(address(new ConvexTestConfigStorage())); + + _setUpTest(testConfigStorage.getTestConfig(0)); + + adapter = IAdapter(Clones.clone(address(new ConvexAdapter()))); + adapter.initialize( + abi.encode(asset, address(this), strategy, 0, sigs, ""), //address(new MockStrategyClaimer()) + externalRegistry, + testConfigStorage.getTestConfig(0) + ); + + _mintFor(defaultAmount * 10000, bob); + + vm.prank(bob); + adapter.deposit(defaultAmount * 10000, bob); + + vm.warp(100 days); + + vm.prank(bob); + IWithRewards(address(adapter)).claim(); + } } diff --git a/packages/foundry/test/vault/integration/convex/ConvexTestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/convex/ConvexTestConfigStorage.sol similarity index 88% rename from packages/foundry/test/vault/integration/convex/ConvexTestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/convex/ConvexTestConfigStorage.sol index 2114bd4d..c3fdd5f2 100644 --- a/packages/foundry/test/vault/integration/convex/ConvexTestConfigStorage.sol +++ b/packages/foundry/test/vault/integration/adapter/convex/ConvexTestConfigStorage.sol @@ -13,8 +13,8 @@ contract ConvexTestConfigStorage is ITestConfigStorage { ConvexTestConfig[] internal testConfigs; constructor() { - // Mainnet - wETH - testConfigs.push(ConvexTestConfig(61)); + // Mainnet - MIM + 3CRV + testConfigs.push(ConvexTestConfig(40)); } function getTestConfig(uint256 i) public view returns (bytes memory) { diff --git a/packages/foundry/test/vault/integration/yearn/YearnAdapter.t.sol b/packages/foundry/test/vault/integration/adapter/yearn/YearnAdapter.t.sol similarity index 98% rename from packages/foundry/test/vault/integration/yearn/YearnAdapter.t.sol rename to packages/foundry/test/vault/integration/adapter/yearn/YearnAdapter.t.sol index 2292ac4e..3b620310 100644 --- a/packages/foundry/test/vault/integration/yearn/YearnAdapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/yearn/YearnAdapter.t.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { YearnAdapter, SafeERC20, IERC20, IERC20Metadata, Math, VaultAPI, IYearnRegistry } from "../../../../src/vault/adapter/yearn/YearnAdapter.sol"; +import { YearnAdapter, SafeERC20, IERC20, IERC20Metadata, Math, VaultAPI, IYearnRegistry } from "../../../../../src/vault/adapter/yearn/YearnAdapter.sol"; import { YearnTestConfigStorage, YearnTestConfig } from "./YearnTestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; diff --git a/packages/foundry/test/vault/integration/yearn/YearnTestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/yearn/YearnTestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/yearn/YearnTestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/yearn/YearnTestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/strategies/compounder/BaseToAssetCompounder.t.sol b/packages/foundry/test/vault/integration/strategies/compounder/BaseToAssetCompounder.t.sol new file mode 100644 index 00000000..0fad96ce --- /dev/null +++ b/packages/foundry/test/vault/integration/strategies/compounder/BaseToAssetCompounder.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { Test } from "forge-std/Test.sol"; + + +contract BaseToAssetCompounderTest is Test { + function setUp() public { + + } +} \ No newline at end of file From fda47c0ac588f8b564f7bf0742f31bc5ff0fda6d Mon Sep 17 00:00:00 2001 From: RedVeil Date: Mon, 20 Mar 2023 11:49:12 +0100 Subject: [PATCH 21/21] wip - CurveMetapool --- .../external/curve/ICurveMetapool.sol | 71 +++++++++++++++++++ .../compounder/pool/CurveMetapool.sol | 29 ++++++++ .../strategy/compounder/pool/UniV2Pool.sol | 9 +-- 3 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 packages/foundry/src/interfaces/external/curve/ICurveMetapool.sol create mode 100644 packages/foundry/src/vault/strategy/compounder/pool/CurveMetapool.sol diff --git a/packages/foundry/src/interfaces/external/curve/ICurveMetapool.sol b/packages/foundry/src/interfaces/external/curve/ICurveMetapool.sol new file mode 100644 index 00000000..c16279c5 --- /dev/null +++ b/packages/foundry/src/interfaces/external/curve/ICurveMetapool.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +interface ICurveMetapool { + function get_virtual_price() external view returns (uint256); + + function add_liquidity(uint256[2] calldata amounts, uint256 min_mint_amounts) external returns (uint256); + + function add_liquidity( + uint256[2] calldata _amounts, + uint256 _min_mint_amounts, + address _receiver + ) external returns (uint256); + + function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amounts) external returns (uint256); + + function add_liquidity( + uint256[3] calldata _amounts, + uint256 _min_mint_amounts, + address _receiver + ) external returns (uint256); + + function add_liquidity(uint256[4] calldata amounts, uint256 min_mint_amounts) external; + + function add_liquidity( + uint256[4] calldata _amounts, + uint256 _min_mint_amounts, + address _receiver + ) external returns (uint256); + + function remove_liquidity_one_coin( + uint256 amount, + int128 i, + uint256 min_underlying_amount + ) external; + + function remove_liquidity_one_coin( + uint256 amount, + int128 i, + uint256 min_underlying_amount, + bool donate_dust + ) external; + + function calc_withdraw_one_coin(uint256 _token_amount, int128 i) external view returns (uint256); + + //Some pools use exchange (sUsd,3crv)... + function exchange( + uint256 i, + uint256 j, + uint256 dx, + uint256 min_dy + ) external returns (uint256); + + //...And some others use exchange_underlying (mim,3crv) + function exchange_underlying( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy + ) external returns (uint256); + + function lp_price() external view returns (uint256); + + function price_oracle() external view returns (uint256); + + function balances(uint256 i) external view returns (uint256); + + function remove_liquidity(uint256 amount, uint256[4] memory min_amounts) external; +} diff --git a/packages/foundry/src/vault/strategy/compounder/pool/CurveMetapool.sol b/packages/foundry/src/vault/strategy/compounder/pool/CurveMetapool.sol new file mode 100644 index 00000000..250e8510 --- /dev/null +++ b/packages/foundry/src/vault/strategy/compounder/pool/CurveMetapool.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { ICurveMetapool } from "../../../../interfaces/external/curve/ICurveMetapool.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; + +contract CurveMetapool { + using SafeERC20 for IERC20; + + function addLiquidity( + address router, + address lpToken, + address[2] memory underlyingTokens, + uint256 bal0, + uint256 bal1 + ) external returns (uint256) { + IERC20(underlyingTokens[0]).transferFrom(msg.sender, address(this), bal0); + IERC20(underlyingTokens[1]).transferFrom(msg.sender, address(this), bal1); + + ICurveMetapool(router).addLiquidity([bal0, 0], 0); + + uint256 bal = IERC20(lpToken).balanceOf(address(this)); + IERC20(lpToken).safeTransfer(msg.sender, bal); + return bal; + } +} diff --git a/packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol b/packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol index 563480b0..ccdb6c80 100644 --- a/packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol +++ b/packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol @@ -7,16 +7,11 @@ import { IUniswapRouterV2 } from "../../../../interfaces/external/uni/IUniswapRo import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; -contract UniV2Router { +contract UniV2Pool { using SafeERC20 for IERC20; - address router; - - constructor(address _router) { - router = _router; - } - function addLiquidity( + address router, address lpToken, address[2] memory underlyingTokens, uint256 bal0,