From 9ea322c7b5ffdfaf66d624ade73721ec1ad6473e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 20 Nov 2023 16:48:24 +0100 Subject: [PATCH 01/21] Adding Router contract placeholder --- core/contracts/Router.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/contracts/Router.sol diff --git a/core/contracts/Router.sol b/core/contracts/Router.sol new file mode 100644 index 000000000..e69de29bb From 9acd4c28ff5fe8a221b906386ecd40c6a0de8fb0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 22 Nov 2023 12:50:31 +0100 Subject: [PATCH 02/21] Adding solmate lib 6.2.0 This lib adds solmate lib that is used in ERC4626 contracts. --- core/package.json | 3 +++ core/yarn.lock | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/core/package.json b/core/package.json index 570eebfbf..9a4abc768 100644 --- a/core/package.json +++ b/core/package.json @@ -27,6 +27,9 @@ "lint:config:fix": "prettier --write '**/*.@(json)'", "test": "hardhat test" }, + "dependencies": { + "solmate": "^6.2.0" + }, "devDependencies": { "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", "@nomicfoundation/hardhat-ethers": "^3.0.0", diff --git a/core/yarn.lock b/core/yarn.lock index cf702692a..655df8d09 100644 --- a/core/yarn.lock +++ b/core/yarn.lock @@ -5406,6 +5406,11 @@ solidity-coverage@^0.8.0: shelljs "^0.8.3" web3-utils "^1.3.6" +solmate@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/solmate/-/solmate-6.2.0.tgz#edd29b5f3d6faafafdcf65fe4d1d959b4841cfa8" + integrity sha512-AM38ioQ2P8zRsA42zenb9or6OybRjOLXIu3lhIT8rhddUuduCt76pUEuLxOIg9GByGojGz+EbpFdCB6B+QZVVA== + source-map-support@^0.5.13: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" From de39aa294ae07a6900c766465f6a2a9020decc44 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 22 Nov 2023 12:54:34 +0100 Subject: [PATCH 03/21] Copying ERC4626 related contracts from ERC4626-Alliance Only some of the contracts were copied and pasted just for Acre needs. In Acre project we use HH with yarn manager and this is why the path to solmate's library in node_modules had to be changed. Added only `/src` to the path. The rest of the code was unchanged and is based on 643cd04 commit from the following repo: https://github.com/ERC4626-Alliance/ERC4626-Contracts --- core/contracts/lib/ERC4626RouterBase.sol | 62 ++++++++++ core/contracts/lib/external/Multicall.sol | 29 +++++ .../lib/external/PeripheryPayments.sol | 76 ++++++++++++ core/contracts/lib/external/SelfPermit.sol | 62 ++++++++++ .../interfaces/IERC20PermitAllowed.sol | 29 +++++ .../lib/external/interfaces/IMulticall.sol | 14 +++ .../lib/external/interfaces/ISelfPermit.sol | 78 ++++++++++++ core/contracts/lib/interfaces/IERC4626.sol | 115 ++++++++++++++++++ .../lib/interfaces/IERC4626RouterBase.sol | 105 ++++++++++++++++ core/contracts/lib/interfaces/IxERC4626.sol | 57 +++++++++ core/contracts/lib/xERC4626.sol | 98 +++++++++++++++ 11 files changed, 725 insertions(+) create mode 100644 core/contracts/lib/ERC4626RouterBase.sol create mode 100644 core/contracts/lib/external/Multicall.sol create mode 100644 core/contracts/lib/external/PeripheryPayments.sol create mode 100644 core/contracts/lib/external/SelfPermit.sol create mode 100644 core/contracts/lib/external/interfaces/IERC20PermitAllowed.sol create mode 100644 core/contracts/lib/external/interfaces/IMulticall.sol create mode 100644 core/contracts/lib/external/interfaces/ISelfPermit.sol create mode 100644 core/contracts/lib/interfaces/IERC4626.sol create mode 100644 core/contracts/lib/interfaces/IERC4626RouterBase.sol create mode 100644 core/contracts/lib/interfaces/IxERC4626.sol create mode 100644 core/contracts/lib/xERC4626.sol diff --git a/core/contracts/lib/ERC4626RouterBase.sol b/core/contracts/lib/ERC4626RouterBase.sol new file mode 100644 index 000000000..087b39048 --- /dev/null +++ b/core/contracts/lib/ERC4626RouterBase.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +import {IERC4626, IERC4626RouterBase, ERC20} from "./interfaces/IERC4626RouterBase.sol"; +import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; + +import {SelfPermit} from "./external/SelfPermit.sol"; +import {Multicall} from "./external/Multicall.sol"; +import {PeripheryPayments, IWETH9} from "./external/PeripheryPayments.sol"; + +/// @title ERC4626 Router Base Contract +abstract contract ERC4626RouterBase is IERC4626RouterBase, SelfPermit, Multicall, PeripheryPayments { + using SafeTransferLib for ERC20; + + /// @inheritdoc IERC4626RouterBase + function mint( + IERC4626 vault, + address to, + uint256 shares, + uint256 maxAmountIn + ) public payable virtual override returns (uint256 amountIn) { + if ((amountIn = vault.mint(shares, to)) > maxAmountIn) { + revert MaxAmountError(); + } + } + + /// @inheritdoc IERC4626RouterBase + function deposit( + IERC4626 vault, + address to, + uint256 amount, + uint256 minSharesOut + ) public payable virtual override returns (uint256 sharesOut) { + if ((sharesOut = vault.deposit(amount, to)) < minSharesOut) { + revert MinSharesError(); + } + } + + /// @inheritdoc IERC4626RouterBase + function withdraw( + IERC4626 vault, + address to, + uint256 amount, + uint256 maxSharesOut + ) public payable virtual override returns (uint256 sharesOut) { + if ((sharesOut = vault.withdraw(amount, to, msg.sender)) > maxSharesOut) { + revert MaxSharesError(); + } + } + + /// @inheritdoc IERC4626RouterBase + function redeem( + IERC4626 vault, + address to, + uint256 shares, + uint256 minAmountOut + ) public payable virtual override returns (uint256 amountOut) { + if ((amountOut = vault.redeem(shares, to, msg.sender)) < minAmountOut) { + revert MinAmountError(); + } + } +} diff --git a/core/contracts/lib/external/Multicall.sol b/core/contracts/lib/external/Multicall.sol new file mode 100644 index 000000000..bbc42b061 --- /dev/null +++ b/core/contracts/lib/external/Multicall.sol @@ -0,0 +1,29 @@ +// forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/Multicall.sol + +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.6; + +import './interfaces/IMulticall.sol'; + +/// @title Multicall +/// @notice Enables calling multiple methods in a single call to the contract +abstract contract Multicall is IMulticall { + /// @inheritdoc IMulticall + function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} \ No newline at end of file diff --git a/core/contracts/lib/external/PeripheryPayments.sol b/core/contracts/lib/external/PeripheryPayments.sol new file mode 100644 index 000000000..b3185d6c6 --- /dev/null +++ b/core/contracts/lib/external/PeripheryPayments.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +import "solmate/src/utils/SafeTransferLib.sol"; + +/** + @title Periphery Payments + @notice Immutable state used by periphery contracts + Largely Forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/PeripheryPayments.sol + Changes: + * no interface + * no inheritdoc + * add immutable WETH9 in constructor instead of PeripheryImmutableState + * receive from any address + * Solmate interfaces and transfer lib + * casting + * add approve, wrapWETH9 and pullToken +*/ +abstract contract PeripheryPayments { + using SafeTransferLib for *; + + IWETH9 public immutable WETH9; + + constructor(IWETH9 _WETH9) { + WETH9 = _WETH9; + } + + receive() external payable {} + + function approve(ERC20 token, address to, uint256 amount) public payable { + token.safeApprove(to, amount); + } + + function unwrapWETH9(uint256 amountMinimum, address recipient) public payable { + uint256 balanceWETH9 = WETH9.balanceOf(address(this)); + require(balanceWETH9 >= amountMinimum, 'Insufficient WETH9'); + + if (balanceWETH9 > 0) { + WETH9.withdraw(balanceWETH9); + recipient.safeTransferETH(balanceWETH9); + } + } + + function wrapWETH9() public payable { + if (address(this).balance > 0) WETH9.deposit{value: address(this).balance}(); // wrap everything + } + + function pullToken(ERC20 token, uint256 amount, address recipient) public payable { + token.safeTransferFrom(msg.sender, recipient, amount); + } + + function sweepToken( + ERC20 token, + uint256 amountMinimum, + address recipient + ) public payable { + uint256 balanceToken = token.balanceOf(address(this)); + require(balanceToken >= amountMinimum, 'Insufficient token'); + + if (balanceToken > 0) { + token.safeTransfer(recipient, balanceToken); + } + } + + function refundETH() external payable { + if (address(this).balance > 0) SafeTransferLib.safeTransferETH(msg.sender, address(this).balance); + } +} + +abstract contract IWETH9 is ERC20 { + /// @notice Deposit ether to get wrapped ether + function deposit() external payable virtual; + + /// @notice Withdraw wrapped ether to get ether + function withdraw(uint256) external virtual; +} \ No newline at end of file diff --git a/core/contracts/lib/external/SelfPermit.sol b/core/contracts/lib/external/SelfPermit.sol new file mode 100644 index 000000000..5e82c1d96 --- /dev/null +++ b/core/contracts/lib/external/SelfPermit.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; + +import './interfaces/ISelfPermit.sol'; +import './interfaces/IERC20PermitAllowed.sol'; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +/// @dev These functions are expected to be embedded in multicalls to allow EOAs to approve a contract and call a function +/// that requires an approval in a single transaction. +abstract contract SelfPermit is ISelfPermit { + /// @inheritdoc ISelfPermit + function selfPermit( + address token, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public payable override { + ERC20(token).permit(msg.sender, address(this), value, deadline, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitIfNecessary( + address token, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable override { + if (ERC20(token).allowance(msg.sender, address(this)) < value) selfPermit(token, value, deadline, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitAllowed( + address token, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) public payable override { + IERC20PermitAllowed(token).permit(msg.sender, address(this), nonce, expiry, true, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitAllowedIfNecessary( + address token, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external payable override { + if (ERC20(token).allowance(msg.sender, address(this)) < type(uint256).max) + selfPermitAllowed(token, nonce, expiry, v, r, s); + } +} diff --git a/core/contracts/lib/external/interfaces/IERC20PermitAllowed.sol b/core/contracts/lib/external/interfaces/IERC20PermitAllowed.sol new file mode 100644 index 000000000..4ae9cd032 --- /dev/null +++ b/core/contracts/lib/external/interfaces/IERC20PermitAllowed.sol @@ -0,0 +1,29 @@ +// forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/external/IERC20PermitAllowed.sol + +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Interface for permit +/// @notice Interface used by DAI/CHAI for permit +interface IERC20PermitAllowed { + /// @notice Approve the spender to spend some tokens via the holder signature + /// @dev This is the permit interface used by DAI and CHAI + /// @param holder The address of the token holder, the token owner + /// @param spender The address of the token spender + /// @param nonce The holder's nonce, increases at each call to permit + /// @param expiry The timestamp at which the permit is no longer valid + /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0 + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function permit( + address holder, + address spender, + uint256 nonce, + uint256 expiry, + bool allowed, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} \ No newline at end of file diff --git a/core/contracts/lib/external/interfaces/IMulticall.sol b/core/contracts/lib/external/interfaces/IMulticall.sol new file mode 100644 index 000000000..2ded0a35a --- /dev/null +++ b/core/contracts/lib/external/interfaces/IMulticall.sol @@ -0,0 +1,14 @@ +// forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/IMulticall.sol + +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +/// @title Multicall interface +/// @notice Enables calling multiple methods in a single call to the contract +interface IMulticall { + /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + /// @dev The `msg.value` should not be trusted for any method callable from multicall. + /// @param data The encoded function data for each of the calls to make to this contract + /// @return results The results from each of the calls passed in via data + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); +} diff --git a/core/contracts/lib/external/interfaces/ISelfPermit.sol b/core/contracts/lib/external/interfaces/ISelfPermit.sol new file mode 100644 index 000000000..5953ea62f --- /dev/null +++ b/core/contracts/lib/external/interfaces/ISelfPermit.sol @@ -0,0 +1,78 @@ +// forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/ISelfPermit.sol + +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +interface ISelfPermit { + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermit( + address token, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; + + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// Can be used instead of #selfPermit to prevent calls from failing due to a frontrun of a call to #selfPermit + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitIfNecessary( + address token, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowed( + address token, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// Can be used instead of #selfPermitAllowed to prevent calls from failing due to a frontrun of a call to #selfPermitAllowed. + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowedIfNecessary( + address token, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; +} diff --git a/core/contracts/lib/interfaces/IERC4626.sol b/core/contracts/lib/interfaces/IERC4626.sol new file mode 100644 index 000000000..bfbc1f1e3 --- /dev/null +++ b/core/contracts/lib/interfaces/IERC4626.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; + +/// @title ERC4626 interface +/// See: https://eips.ethereum.org/EIPS/eip-4626 +abstract contract IERC4626 is ERC20 { + /*//////////////////////////////////////////////////////// + Events + ////////////////////////////////////////////////////////*/ + + /// @notice `sender` has exchanged `assets` for `shares`, + /// and transferred those `shares` to `receiver`. + event Deposit(address indexed sender, address indexed receiver, uint256 assets, uint256 shares); + + /// @notice `sender` has exchanged `shares` for `assets`, + /// and transferred those `assets` to `receiver`. + event Withdraw(address indexed sender, address indexed receiver, uint256 assets, uint256 shares); + + /*//////////////////////////////////////////////////////// + Vault properties + ////////////////////////////////////////////////////////*/ + + /// @notice The address of the underlying ERC20 token used for + /// the Vault for accounting, depositing, and withdrawing. + function asset() external view virtual returns (address asset); + + /// @notice Total amount of the underlying asset that + /// is "managed" by Vault. + function totalAssets() external view virtual returns (uint256 totalAssets); + + /*//////////////////////////////////////////////////////// + Deposit/Withdrawal Logic + ////////////////////////////////////////////////////////*/ + + /// @notice Mints `shares` Vault shares to `receiver` by + /// depositing exactly `assets` of underlying tokens. + function deposit(uint256 assets, address receiver) external virtual returns (uint256 shares); + + /// @notice Mints exactly `shares` Vault shares to `receiver` + /// by depositing `assets` of underlying tokens. + function mint(uint256 shares, address receiver) external virtual returns (uint256 assets); + + /// @notice Redeems `shares` from `owner` and sends `assets` + /// of underlying tokens to `receiver`. + function withdraw( + uint256 assets, + address receiver, + address owner + ) external virtual returns (uint256 shares); + + /// @notice Redeems `shares` from `owner` and sends `assets` + /// of underlying tokens to `receiver`. + function redeem( + uint256 shares, + address receiver, + address owner + ) external virtual returns (uint256 assets); + + /*//////////////////////////////////////////////////////// + Vault Accounting Logic + ////////////////////////////////////////////////////////*/ + + /// @notice The amount of shares that the vault would + /// exchange for the amount of assets provided, in an + /// ideal scenario where all the conditions are met. + function convertToShares(uint256 assets) external view virtual returns (uint256 shares); + + /// @notice The amount of assets that the vault would + /// exchange for the amount of shares provided, in an + /// ideal scenario where all the conditions are met. + function convertToAssets(uint256 shares) external view virtual returns (uint256 assets); + + /// @notice Total number of underlying assets that can + /// be deposited by `owner` into the Vault, where `owner` + /// corresponds to the input parameter `receiver` of a + /// `deposit` call. + function maxDeposit(address owner) external view virtual returns (uint256 maxAssets); + + /// @notice Allows an on-chain or off-chain user to simulate + /// the effects of their deposit at the current block, given + /// current on-chain conditions. + function previewDeposit(uint256 assets) external view virtual returns (uint256 shares); + + /// @notice Total number of underlying shares that can be minted + /// for `owner`, where `owner` corresponds to the input + /// parameter `receiver` of a `mint` call. + function maxMint(address owner) external view virtual returns (uint256 maxShares); + + /// @notice Allows an on-chain or off-chain user to simulate + /// the effects of their mint at the current block, given + /// current on-chain conditions. + function previewMint(uint256 shares) external view virtual returns (uint256 assets); + + /// @notice Total number of underlying assets that can be + /// withdrawn from the Vault by `owner`, where `owner` + /// corresponds to the input parameter of a `withdraw` call. + function maxWithdraw(address owner) external view virtual returns (uint256 maxAssets); + + /// @notice Allows an on-chain or off-chain user to simulate + /// the effects of their withdrawal at the current block, + /// given current on-chain conditions. + function previewWithdraw(uint256 assets) external view virtual returns (uint256 shares); + + /// @notice Total number of underlying shares that can be + /// redeemed from the Vault by `owner`, where `owner` corresponds + /// to the input parameter of a `redeem` call. + function maxRedeem(address owner) external view virtual returns (uint256 maxShares); + + /// @notice Allows an on-chain or off-chain user to simulate + /// the effects of their redeemption at the current block, + /// given current on-chain conditions. + function previewRedeem(uint256 shares) external view virtual returns (uint256 assets); +} diff --git a/core/contracts/lib/interfaces/IERC4626RouterBase.sol b/core/contracts/lib/interfaces/IERC4626RouterBase.sol new file mode 100644 index 000000000..588c1ed42 --- /dev/null +++ b/core/contracts/lib/interfaces/IERC4626RouterBase.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +import "./IERC4626.sol"; + +/** + @title ERC4626Router Base Interface + @notice A canonical router between ERC4626 Vaults https://eips.ethereum.org/EIPS/eip-4626 + + The base router is a multicall style router inspired by Uniswap v3 with built-in features for permit, WETH9 wrap/unwrap, and ERC20 token pulling/sweeping/approving. + It includes methods for the four mutable ERC4626 functions deposit/mint/withdraw/redeem as well. + + These can all be arbitrarily composed using the multicall functionality of the router. + + NOTE the router is capable of pulling any approved token from your wallet. This is only possible when your address is msg.sender, but regardless be careful when interacting with the router or ERC4626 Vaults. + The router makes no special considerations for unique ERC20 implementations such as fee on transfer. + There are no built in protections for unexpected behavior beyond enforcing the minSharesOut is received. + */ +interface IERC4626RouterBase { + /************************** Errors **************************/ + + /// @notice thrown when amount of assets received is below the min set by caller + error MinAmountError(); + + /// @notice thrown when amount of shares received is below the min set by caller + error MinSharesError(); + + /// @notice thrown when amount of assets received is above the max set by caller + error MaxAmountError(); + + /// @notice thrown when amount of shares received is above the max set by caller + error MaxSharesError(); + + /************************** Mint **************************/ + + /** + @notice mint `shares` from an ERC4626 vault. + @param vault The ERC4626 vault to mint shares from. + @param to The destination of ownership shares. + @param shares The amount of shares to mint from `vault`. + @param maxAmountIn The max amount of assets used to mint. + @return amountIn the amount of assets used to mint by `to`. + @dev throws MaxAmountError + */ + function mint( + IERC4626 vault, + address to, + uint256 shares, + uint256 maxAmountIn + ) external payable returns (uint256 amountIn); + + /************************** Deposit **************************/ + + /** + @notice deposit `amount` to an ERC4626 vault. + @param vault The ERC4626 vault to deposit assets to. + @param to The destination of ownership shares. + @param amount The amount of assets to deposit to `vault`. + @param minSharesOut The min amount of `vault` shares received by `to`. + @return sharesOut the amount of shares received by `to`. + @dev throws MinSharesError + */ + function deposit( + IERC4626 vault, + address to, + uint256 amount, + uint256 minSharesOut + ) external payable returns (uint256 sharesOut); + + /************************** Withdraw **************************/ + + /** + @notice withdraw `amount` from an ERC4626 vault. + @param vault The ERC4626 vault to withdraw assets from. + @param to The destination of assets. + @param amount The amount of assets to withdraw from vault. + @param minSharesOut The min amount of shares received by `to`. + @return sharesOut the amount of shares received by `to`. + @dev throws MaxSharesError + */ + function withdraw( + IERC4626 vault, + address to, + uint256 amount, + uint256 minSharesOut + ) external payable returns (uint256 sharesOut); + + /************************** Redeem **************************/ + + /** + @notice redeem `shares` shares from an ERC4626 vault. + @param vault The ERC4626 vault to redeem shares from. + @param to The destination of assets. + @param shares The amount of shares to redeem from vault. + @param minAmountOut The min amount of assets received by `to`. + @return amountOut the amount of assets received by `to`. + @dev throws MinAmountError + */ + function redeem( + IERC4626 vault, + address to, + uint256 shares, + uint256 minAmountOut + ) external payable returns (uint256 amountOut); +} diff --git a/core/contracts/lib/interfaces/IxERC4626.sol b/core/contracts/lib/interfaces/IxERC4626.sol new file mode 100644 index 000000000..725fa76c6 --- /dev/null +++ b/core/contracts/lib/interfaces/IxERC4626.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +// Rewards logic inspired by xERC20 (https://github.com/ZeframLou/playpen/blob/main/src/xERC20.sol) + +pragma solidity ^0.8.0; + +import "solmate/src/mixins/ERC4626.sol"; +import "solmate/src/utils/SafeCastLib.sol"; + +/** + @title An xERC4626 Single Staking Contract Interface + @notice This contract allows users to autocompound rewards denominated in an underlying reward token. + It is fully compatible with [ERC4626](https://eips.ethereum.org/EIPS/eip-4626) allowing for DeFi composability. + It maintains balances using internal accounting to prevent instantaneous changes in the exchange rate. + NOTE: an exception is at contract creation, when a reward cycle begins before the first deposit. After the first deposit, exchange rate updates smoothly. + + Operates on "cycles" which distribute the rewards surplus over the internal balance to users linearly over the remainder of the cycle window. +*/ +interface IxERC4626 { + /*//////////////////////////////////////////////////////// + Custom Errors + ////////////////////////////////////////////////////////*/ + + /// @dev thrown when syncing before cycle ends. + error SyncError(); + + /*//////////////////////////////////////////////////////// + Events + ////////////////////////////////////////////////////////*/ + + /// @dev emit every time a new rewards cycle starts + event NewRewardsCycle(uint32 indexed cycleEnd, uint256 rewardAmount); + + /*//////////////////////////////////////////////////////// + View Methods + ////////////////////////////////////////////////////////*/ + + /// @notice the maximum length of a rewards cycle + function rewardsCycleLength() external view returns (uint32); + + /// @notice the effective start of the current cycle + /// NOTE: This will likely be after `rewardsCycleEnd - rewardsCycleLength` as this is set as block.timestamp of the last `syncRewards` call. + function lastSync() external view returns (uint32); + + /// @notice the end of the current cycle. Will always be evenly divisible by `rewardsCycleLength`. + function rewardsCycleEnd() external view returns (uint32); + + /// @notice the amount of rewards distributed in a the most recent cycle + function lastRewardAmount() external view returns (uint192); + + /*//////////////////////////////////////////////////////// + State Changing Methods + ////////////////////////////////////////////////////////*/ + + /// @notice Distributes rewards to xERC4626 holders. + /// All surplus `asset` balance of the contract over the internal balance becomes queued for the next cycle. + function syncRewards() external; +} diff --git a/core/contracts/lib/xERC4626.sol b/core/contracts/lib/xERC4626.sol new file mode 100644 index 000000000..bf0d4e661 --- /dev/null +++ b/core/contracts/lib/xERC4626.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +// Rewards logic inspired by xERC20 (https://github.com/ZeframLou/playpen/blob/main/src/xERC20.sol) + +pragma solidity ^0.8.0; + +import "solmate/src/mixins/ERC4626.sol"; +import "solmate/src/utils/SafeCastLib.sol"; + +import "./interfaces/IxERC4626.sol"; + +/** + @title An xERC4626 Single Staking Contract + @notice This contract allows users to autocompound rewards denominated in an underlying reward token. + It is fully compatible with [ERC4626](https://eips.ethereum.org/EIPS/eip-4626) allowing for DeFi composability. + It maintains balances using internal accounting to prevent instantaneous changes in the exchange rate. + NOTE: an exception is at contract creation, when a reward cycle begins before the first deposit. After the first deposit, exchange rate updates smoothly. + + Operates on "cycles" which distribute the rewards surplus over the internal balance to users linearly over the remainder of the cycle window. +*/ +abstract contract xERC4626 is IxERC4626, ERC4626 { + using SafeCastLib for *; + + /// @notice the maximum length of a rewards cycle + uint32 public immutable rewardsCycleLength; + + /// @notice the effective start of the current cycle + uint32 public lastSync; + + /// @notice the end of the current cycle. Will always be evenly divisible by `rewardsCycleLength`. + uint32 public rewardsCycleEnd; + + /// @notice the amount of rewards distributed in a the most recent cycle. + uint192 public lastRewardAmount; + + uint256 internal storedTotalAssets; + + constructor(uint32 _rewardsCycleLength) { + rewardsCycleLength = _rewardsCycleLength; + // seed initial rewardsCycleEnd + rewardsCycleEnd = (block.timestamp.safeCastTo32() / rewardsCycleLength) * rewardsCycleLength; + } + + /// @notice Compute the amount of tokens available to share holders. + /// Increases linearly during a reward distribution period from the sync call, not the cycle start. + function totalAssets() public view override returns (uint256) { + // cache global vars + uint256 storedTotalAssets_ = storedTotalAssets; + uint192 lastRewardAmount_ = lastRewardAmount; + uint32 rewardsCycleEnd_ = rewardsCycleEnd; + uint32 lastSync_ = lastSync; + + if (block.timestamp >= rewardsCycleEnd_) { + // no rewards or rewards fully unlocked + // entire reward amount is available + return storedTotalAssets_ + lastRewardAmount_; + } + + // rewards not fully unlocked + // add unlocked rewards to stored total + uint256 unlockedRewards = (lastRewardAmount_ * (block.timestamp - lastSync_)) / (rewardsCycleEnd_ - lastSync_); + return storedTotalAssets_ + unlockedRewards; + } + + // Update storedTotalAssets on withdraw/redeem + function beforeWithdraw(uint256 amount, uint256 shares) internal virtual override { + super.beforeWithdraw(amount, shares); + storedTotalAssets -= amount; + } + + // Update storedTotalAssets on deposit/mint + function afterDeposit(uint256 amount, uint256 shares) internal virtual override { + storedTotalAssets += amount; + super.afterDeposit(amount, shares); + } + + /// @notice Distributes rewards to xERC4626 holders. + /// All surplus `asset` balance of the contract over the internal balance becomes queued for the next cycle. + function syncRewards() public virtual { + uint192 lastRewardAmount_ = lastRewardAmount; + uint32 timestamp = block.timestamp.safeCastTo32(); + + if (timestamp < rewardsCycleEnd) revert SyncError(); + + uint256 storedTotalAssets_ = storedTotalAssets; + uint256 nextRewards = asset.balanceOf(address(this)) - storedTotalAssets_ - lastRewardAmount_; + + storedTotalAssets = storedTotalAssets_ + lastRewardAmount_; // SSTORE + + uint32 end = ((timestamp + rewardsCycleLength) / rewardsCycleLength) * rewardsCycleLength; + + // Combined single SSTORE + lastRewardAmount = nextRewards.safeCastTo192(); + lastSync = timestamp; + rewardsCycleEnd = end; + + emit NewRewardsCycle(end, nextRewards); + } +} From 1d4c88efebcbeb611af80257e036fae34ac4a2d0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 22 Nov 2023 12:59:50 +0100 Subject: [PATCH 04/21] Adding support for Solidity 0.8.10. Used by ERC4626 contracts --- core/hardhat.config.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/hardhat.config.ts b/core/hardhat.config.ts index 64e14bce6..d8538ed77 100644 --- a/core/hardhat.config.ts +++ b/core/hardhat.config.ts @@ -15,6 +15,15 @@ const config: HardhatUserConfig = { }, }, }, + { + version: "0.8.10", + settings: { + optimizer: { + enabled: true, + runs: 1000, + }, + }, + }, ], }, From 0a795c3b691491ec46fe6272ec2b1f9aef6488b2 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 22 Nov 2023 13:11:40 +0100 Subject: [PATCH 05/21] Adding annotation that these contracts were copied from ERC4626-Alliance repo --- core/contracts/lib/ERC4626RouterBase.sol | 4 ++++ core/contracts/lib/external/Multicall.sol | 4 ++++ core/contracts/lib/external/PeripheryPayments.sol | 4 ++++ core/contracts/lib/external/SelfPermit.sol | 4 ++++ .../contracts/lib/external/interfaces/IERC20PermitAllowed.sol | 4 ++++ core/contracts/lib/external/interfaces/IMulticall.sol | 4 ++++ core/contracts/lib/external/interfaces/ISelfPermit.sol | 4 ++++ core/contracts/lib/interfaces/IERC4626.sol | 4 ++++ core/contracts/lib/interfaces/IERC4626RouterBase.sol | 4 ++++ core/contracts/lib/interfaces/IxERC4626.sol | 3 +++ core/contracts/lib/xERC4626.sol | 3 +++ 11 files changed, 42 insertions(+) diff --git a/core/contracts/lib/ERC4626RouterBase.sol b/core/contracts/lib/ERC4626RouterBase.sol index 087b39048..1126b29a9 100644 --- a/core/contracts/lib/ERC4626RouterBase.sol +++ b/core/contracts/lib/ERC4626RouterBase.sol @@ -1,4 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later + +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity 0.8.10; import {IERC4626, IERC4626RouterBase, ERC20} from "./interfaces/IERC4626RouterBase.sol"; diff --git a/core/contracts/lib/external/Multicall.sol b/core/contracts/lib/external/Multicall.sol index bbc42b061..04392462a 100644 --- a/core/contracts/lib/external/Multicall.sol +++ b/core/contracts/lib/external/Multicall.sol @@ -1,6 +1,10 @@ // forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/Multicall.sol // SPDX-License-Identifier: GPL-2.0-or-later + +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity >=0.7.6; import './interfaces/IMulticall.sol'; diff --git a/core/contracts/lib/external/PeripheryPayments.sol b/core/contracts/lib/external/PeripheryPayments.sol index b3185d6c6..58803152e 100644 --- a/core/contracts/lib/external/PeripheryPayments.sol +++ b/core/contracts/lib/external/PeripheryPayments.sol @@ -1,4 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later + +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity >=0.7.5; import "solmate/src/utils/SafeTransferLib.sol"; diff --git a/core/contracts/lib/external/SelfPermit.sol b/core/contracts/lib/external/SelfPermit.sol index 5e82c1d96..6151db148 100644 --- a/core/contracts/lib/external/SelfPermit.sol +++ b/core/contracts/lib/external/SelfPermit.sol @@ -1,4 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later + +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity >=0.5.0; import {ERC20} from "solmate/src/tokens/ERC20.sol"; diff --git a/core/contracts/lib/external/interfaces/IERC20PermitAllowed.sol b/core/contracts/lib/external/interfaces/IERC20PermitAllowed.sol index 4ae9cd032..9b991b6f0 100644 --- a/core/contracts/lib/external/interfaces/IERC20PermitAllowed.sol +++ b/core/contracts/lib/external/interfaces/IERC20PermitAllowed.sol @@ -1,6 +1,10 @@ // forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/external/IERC20PermitAllowed.sol // SPDX-License-Identifier: GPL-2.0-or-later + +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity >=0.5.0; /// @title Interface for permit diff --git a/core/contracts/lib/external/interfaces/IMulticall.sol b/core/contracts/lib/external/interfaces/IMulticall.sol index 2ded0a35a..4a58f8e0a 100644 --- a/core/contracts/lib/external/interfaces/IMulticall.sol +++ b/core/contracts/lib/external/interfaces/IMulticall.sol @@ -1,6 +1,10 @@ // forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/IMulticall.sol // SPDX-License-Identifier: GPL-2.0-or-later + +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity >=0.7.5; /// @title Multicall interface diff --git a/core/contracts/lib/external/interfaces/ISelfPermit.sol b/core/contracts/lib/external/interfaces/ISelfPermit.sol index 5953ea62f..c9815b6d9 100644 --- a/core/contracts/lib/external/interfaces/ISelfPermit.sol +++ b/core/contracts/lib/external/interfaces/ISelfPermit.sol @@ -1,6 +1,10 @@ // forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/ISelfPermit.sol // SPDX-License-Identifier: GPL-2.0-or-later + +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity >=0.7.5; /// @title Self Permit diff --git a/core/contracts/lib/interfaces/IERC4626.sol b/core/contracts/lib/interfaces/IERC4626.sol index bfbc1f1e3..bf287736a 100644 --- a/core/contracts/lib/interfaces/IERC4626.sol +++ b/core/contracts/lib/interfaces/IERC4626.sol @@ -1,4 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later + +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity 0.8.10; import {ERC20} from "solmate/src/tokens/ERC20.sol"; diff --git a/core/contracts/lib/interfaces/IERC4626RouterBase.sol b/core/contracts/lib/interfaces/IERC4626RouterBase.sol index 588c1ed42..4154b664b 100644 --- a/core/contracts/lib/interfaces/IERC4626RouterBase.sol +++ b/core/contracts/lib/interfaces/IERC4626RouterBase.sol @@ -1,4 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later + +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity 0.8.10; import "./IERC4626.sol"; diff --git a/core/contracts/lib/interfaces/IxERC4626.sol b/core/contracts/lib/interfaces/IxERC4626.sol index 725fa76c6..4d4b3fd0c 100644 --- a/core/contracts/lib/interfaces/IxERC4626.sol +++ b/core/contracts/lib/interfaces/IxERC4626.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT // Rewards logic inspired by xERC20 (https://github.com/ZeframLou/playpen/blob/main/src/xERC20.sol) +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity ^0.8.0; import "solmate/src/mixins/ERC4626.sol"; diff --git a/core/contracts/lib/xERC4626.sol b/core/contracts/lib/xERC4626.sol index bf0d4e661..edface342 100644 --- a/core/contracts/lib/xERC4626.sol +++ b/core/contracts/lib/xERC4626.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT // Rewards logic inspired by xERC20 (https://github.com/ZeframLou/playpen/blob/main/src/xERC20.sol) +// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on +// the project commit 643cd04 from Apr 20, 2022 + pragma solidity ^0.8.0; import "solmate/src/mixins/ERC4626.sol"; From af2c73b30c7357cef41ca91b5537779b841e3294 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 22 Nov 2023 13:14:41 +0100 Subject: [PATCH 06/21] Ignoring styling of added Solidity ERC4626 lib contracts --- core/.prettierignore | 1 + core/.solhintignore | 1 + 2 files changed, 2 insertions(+) diff --git a/core/.prettierignore b/core/.prettierignore index 0f586e83a..392688f22 100644 --- a/core/.prettierignore +++ b/core/.prettierignore @@ -4,3 +4,4 @@ deployments/ export.json export/ typechain/ +contracts/lib/ diff --git a/core/.solhintignore b/core/.solhintignore index c2658d7d1..50783f83e 100644 --- a/core/.solhintignore +++ b/core/.solhintignore @@ -1 +1,2 @@ node_modules/ +contracts/lib/ From 12a29ea940ef0d9eea7a80f7f5596b5a3bb8da68 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 22 Nov 2023 15:26:47 +0100 Subject: [PATCH 07/21] Moving erc4626 contracts under lib/erc4626 dir --- core/contracts/lib/{ => erc4626}/ERC4626RouterBase.sol | 0 core/contracts/lib/{ => erc4626}/external/Multicall.sol | 0 core/contracts/lib/{ => erc4626}/external/PeripheryPayments.sol | 0 core/contracts/lib/{ => erc4626}/external/SelfPermit.sol | 0 .../lib/{ => erc4626}/external/interfaces/IERC20PermitAllowed.sol | 0 .../lib/{ => erc4626}/external/interfaces/IMulticall.sol | 0 .../lib/{ => erc4626}/external/interfaces/ISelfPermit.sol | 0 core/contracts/lib/{ => erc4626}/interfaces/IERC4626.sol | 0 .../contracts/lib/{ => erc4626}/interfaces/IERC4626RouterBase.sol | 0 core/contracts/lib/{ => erc4626}/interfaces/IxERC4626.sol | 0 core/contracts/lib/{ => erc4626}/xERC4626.sol | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename core/contracts/lib/{ => erc4626}/ERC4626RouterBase.sol (100%) rename core/contracts/lib/{ => erc4626}/external/Multicall.sol (100%) rename core/contracts/lib/{ => erc4626}/external/PeripheryPayments.sol (100%) rename core/contracts/lib/{ => erc4626}/external/SelfPermit.sol (100%) rename core/contracts/lib/{ => erc4626}/external/interfaces/IERC20PermitAllowed.sol (100%) rename core/contracts/lib/{ => erc4626}/external/interfaces/IMulticall.sol (100%) rename core/contracts/lib/{ => erc4626}/external/interfaces/ISelfPermit.sol (100%) rename core/contracts/lib/{ => erc4626}/interfaces/IERC4626.sol (100%) rename core/contracts/lib/{ => erc4626}/interfaces/IERC4626RouterBase.sol (100%) rename core/contracts/lib/{ => erc4626}/interfaces/IxERC4626.sol (100%) rename core/contracts/lib/{ => erc4626}/xERC4626.sol (100%) diff --git a/core/contracts/lib/ERC4626RouterBase.sol b/core/contracts/lib/erc4626/ERC4626RouterBase.sol similarity index 100% rename from core/contracts/lib/ERC4626RouterBase.sol rename to core/contracts/lib/erc4626/ERC4626RouterBase.sol diff --git a/core/contracts/lib/external/Multicall.sol b/core/contracts/lib/erc4626/external/Multicall.sol similarity index 100% rename from core/contracts/lib/external/Multicall.sol rename to core/contracts/lib/erc4626/external/Multicall.sol diff --git a/core/contracts/lib/external/PeripheryPayments.sol b/core/contracts/lib/erc4626/external/PeripheryPayments.sol similarity index 100% rename from core/contracts/lib/external/PeripheryPayments.sol rename to core/contracts/lib/erc4626/external/PeripheryPayments.sol diff --git a/core/contracts/lib/external/SelfPermit.sol b/core/contracts/lib/erc4626/external/SelfPermit.sol similarity index 100% rename from core/contracts/lib/external/SelfPermit.sol rename to core/contracts/lib/erc4626/external/SelfPermit.sol diff --git a/core/contracts/lib/external/interfaces/IERC20PermitAllowed.sol b/core/contracts/lib/erc4626/external/interfaces/IERC20PermitAllowed.sol similarity index 100% rename from core/contracts/lib/external/interfaces/IERC20PermitAllowed.sol rename to core/contracts/lib/erc4626/external/interfaces/IERC20PermitAllowed.sol diff --git a/core/contracts/lib/external/interfaces/IMulticall.sol b/core/contracts/lib/erc4626/external/interfaces/IMulticall.sol similarity index 100% rename from core/contracts/lib/external/interfaces/IMulticall.sol rename to core/contracts/lib/erc4626/external/interfaces/IMulticall.sol diff --git a/core/contracts/lib/external/interfaces/ISelfPermit.sol b/core/contracts/lib/erc4626/external/interfaces/ISelfPermit.sol similarity index 100% rename from core/contracts/lib/external/interfaces/ISelfPermit.sol rename to core/contracts/lib/erc4626/external/interfaces/ISelfPermit.sol diff --git a/core/contracts/lib/interfaces/IERC4626.sol b/core/contracts/lib/erc4626/interfaces/IERC4626.sol similarity index 100% rename from core/contracts/lib/interfaces/IERC4626.sol rename to core/contracts/lib/erc4626/interfaces/IERC4626.sol diff --git a/core/contracts/lib/interfaces/IERC4626RouterBase.sol b/core/contracts/lib/erc4626/interfaces/IERC4626RouterBase.sol similarity index 100% rename from core/contracts/lib/interfaces/IERC4626RouterBase.sol rename to core/contracts/lib/erc4626/interfaces/IERC4626RouterBase.sol diff --git a/core/contracts/lib/interfaces/IxERC4626.sol b/core/contracts/lib/erc4626/interfaces/IxERC4626.sol similarity index 100% rename from core/contracts/lib/interfaces/IxERC4626.sol rename to core/contracts/lib/erc4626/interfaces/IxERC4626.sol diff --git a/core/contracts/lib/xERC4626.sol b/core/contracts/lib/erc4626/xERC4626.sol similarity index 100% rename from core/contracts/lib/xERC4626.sol rename to core/contracts/lib/erc4626/xERC4626.sol From 6fe3fc4dbd9ecd193e86e66936b1541efb700920 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 23 Nov 2023 16:27:59 +0100 Subject: [PATCH 08/21] Adding initial modifiers and data structres for allocators We will have a set of Allocator conract addresses. Only Acre manager can add or remove an allocator address. Only owner can add a manager. Added `allocate` and `collect` function placeholder for further development. --- core/contracts/AcreRouter.sol | 93 +++++++++++++++++++++++++++++++++++ core/contracts/Router.sol | 0 2 files changed, 93 insertions(+) create mode 100644 core/contracts/AcreRouter.sol delete mode 100644 core/contracts/Router.sol diff --git a/core/contracts/AcreRouter.sol b/core/contracts/AcreRouter.sol new file mode 100644 index 000000000..2d4cc65e9 --- /dev/null +++ b/core/contracts/AcreRouter.sol @@ -0,0 +1,93 @@ +pragma solidity ^0.8.10; + +import {ERC4626RouterBase} from "./lib/erc4626/ERC4626RouterBase.sol"; +import {PeripheryPayments, IWETH9} from "./lib/erc4626/external/PeripheryPayments.sol"; +import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {Owned} from "solmate/src/auth/Owned.sol"; +import {IERC4626} from "./lib/erc4626/interfaces/IERC4626.sol"; + +// TODO: add description +contract AcreRouter is ERC4626RouterBase, Owned { + using SafeTransferLib for ERC20; + + ERC20 public immutable stBTC; + + /// @notice Approved allocators which essentially are the ERC4626 vaults that + /// deposit funds to yield strategies, e.g. Uniswap V3 WBTC/TBTC pool. + /// Each Allocator contract is managed by a Yield Manager. From Acre"s + /// perspective, the Allocator contract can be a part of an external + /// Yield Module and does not care how the yield is generated. + address[] public allocators; + + mapping(address => bool) public isAllocator; + + /// @notice Indicates if the given address is an Acre Manager. Only Acre Manager + /// can set or remove allocators. + address public acreManager; + + event AllocatorAdded(address indexed allocator); + event AllocatorRemoved(address indexed allocator); + + modifier onlyAcreManager() { + require(msg.sender == acreManager, "Caller is not an Acre Manager"); + _; + } + + constructor( + IWETH9 weth, + ERC20 _stBTC + ) Owned(msg.sender) PeripheryPayments(weth) { + stBTC = _stBTC; + } + + function setAcreManager(address _acreManager) external onlyOwner { + acreManager = _acreManager; + } + + function addAllocator(address allocator) external onlyAcreManager { + require(!isAllocator[allocator], "Allocator already exists"); + allocators.push(allocator); + isAllocator[allocator] = true; + emit AllocatorAdded(allocator); + } + + function removeAllocator(address allocator) external onlyAcreManager { + require(isAllocator[allocator], "This address is not a minter"); + + delete isAllocator[allocator]; + + for (uint256 i = 0; i < allocators.length; i++) { + if (allocators[i] == allocator) { + allocators[i] = allocators[allocators.length - 1]; + // slither-disable-next-line costly-loop + allocators.pop(); + break; + } + } + + emit AllocatorRemoved(allocator); + } + + /// @notice Allocates funds from stBTC (Acre) to an allocator + function allocate(address allocator, uint256 amount) public { + require(msg.sender == address(stBTC), "stBTC must be a caller"); + + if (!isAllocator[allocator]) { + revert("Allocator is not approved"); + } + + // TODO: implement allocation logic to an allocator + } + + /// @notice Collects funds from an allocator and sends them to stBTC (Acre) + function collect(address allocator, uint256 amount) public { + require(msg.sender == address(stBTC), "stBTC must be a caller"); + + if (!isAllocator[allocator]) { + revert("Allocator is not approved"); + } + + // TODO: implement collection logic from an allocator + } +} diff --git a/core/contracts/Router.sol b/core/contracts/Router.sol deleted file mode 100644 index e69de29bb..000000000 From 65678bdc141f8ca1d09511fd314e63d8aeceb9f0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 23 Nov 2023 16:35:09 +0100 Subject: [PATCH 09/21] For solidity>7.0 visibility for contracts can be ignored --- core/.solhint.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/.solhint.json b/core/.solhint.json index 7afe6405c..99b128dc9 100644 --- a/core/.solhint.json +++ b/core/.solhint.json @@ -1,5 +1,7 @@ { "extends": "thesis", "plugins": [], - "rules": {} + "rules": { + "func-visibility": ["warn", { "ignoreConstructors": true }] + } } From 2fc9357a3437d71c6bb35ec1f63d5fd366f6999d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 24 Nov 2023 11:52:12 +0100 Subject: [PATCH 10/21] Removing components of ERC4626RouterBase --- .../lib/erc4626/ERC4626RouterBase.sol | 66 ---------- .../lib/erc4626/external/Multicall.sol | 33 ----- .../erc4626/external/PeripheryPayments.sol | 80 ------------ .../lib/erc4626/external/SelfPermit.sol | 66 ---------- .../interfaces/IERC20PermitAllowed.sol | 33 ----- .../external/interfaces/IMulticall.sol | 18 --- .../external/interfaces/ISelfPermit.sol | 82 ------------ .../lib/erc4626/interfaces/IERC4626.sol | 119 ------------------ .../erc4626/interfaces/IERC4626RouterBase.sol | 109 ---------------- 9 files changed, 606 deletions(-) delete mode 100644 core/contracts/lib/erc4626/ERC4626RouterBase.sol delete mode 100644 core/contracts/lib/erc4626/external/Multicall.sol delete mode 100644 core/contracts/lib/erc4626/external/PeripheryPayments.sol delete mode 100644 core/contracts/lib/erc4626/external/SelfPermit.sol delete mode 100644 core/contracts/lib/erc4626/external/interfaces/IERC20PermitAllowed.sol delete mode 100644 core/contracts/lib/erc4626/external/interfaces/IMulticall.sol delete mode 100644 core/contracts/lib/erc4626/external/interfaces/ISelfPermit.sol delete mode 100644 core/contracts/lib/erc4626/interfaces/IERC4626.sol delete mode 100644 core/contracts/lib/erc4626/interfaces/IERC4626RouterBase.sol diff --git a/core/contracts/lib/erc4626/ERC4626RouterBase.sol b/core/contracts/lib/erc4626/ERC4626RouterBase.sol deleted file mode 100644 index 1126b29a9..000000000 --- a/core/contracts/lib/erc4626/ERC4626RouterBase.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on -// the project commit 643cd04 from Apr 20, 2022 - -pragma solidity 0.8.10; - -import {IERC4626, IERC4626RouterBase, ERC20} from "./interfaces/IERC4626RouterBase.sol"; -import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; - -import {SelfPermit} from "./external/SelfPermit.sol"; -import {Multicall} from "./external/Multicall.sol"; -import {PeripheryPayments, IWETH9} from "./external/PeripheryPayments.sol"; - -/// @title ERC4626 Router Base Contract -abstract contract ERC4626RouterBase is IERC4626RouterBase, SelfPermit, Multicall, PeripheryPayments { - using SafeTransferLib for ERC20; - - /// @inheritdoc IERC4626RouterBase - function mint( - IERC4626 vault, - address to, - uint256 shares, - uint256 maxAmountIn - ) public payable virtual override returns (uint256 amountIn) { - if ((amountIn = vault.mint(shares, to)) > maxAmountIn) { - revert MaxAmountError(); - } - } - - /// @inheritdoc IERC4626RouterBase - function deposit( - IERC4626 vault, - address to, - uint256 amount, - uint256 minSharesOut - ) public payable virtual override returns (uint256 sharesOut) { - if ((sharesOut = vault.deposit(amount, to)) < minSharesOut) { - revert MinSharesError(); - } - } - - /// @inheritdoc IERC4626RouterBase - function withdraw( - IERC4626 vault, - address to, - uint256 amount, - uint256 maxSharesOut - ) public payable virtual override returns (uint256 sharesOut) { - if ((sharesOut = vault.withdraw(amount, to, msg.sender)) > maxSharesOut) { - revert MaxSharesError(); - } - } - - /// @inheritdoc IERC4626RouterBase - function redeem( - IERC4626 vault, - address to, - uint256 shares, - uint256 minAmountOut - ) public payable virtual override returns (uint256 amountOut) { - if ((amountOut = vault.redeem(shares, to, msg.sender)) < minAmountOut) { - revert MinAmountError(); - } - } -} diff --git a/core/contracts/lib/erc4626/external/Multicall.sol b/core/contracts/lib/erc4626/external/Multicall.sol deleted file mode 100644 index 04392462a..000000000 --- a/core/contracts/lib/erc4626/external/Multicall.sol +++ /dev/null @@ -1,33 +0,0 @@ -// forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/Multicall.sol - -// SPDX-License-Identifier: GPL-2.0-or-later - -// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on -// the project commit 643cd04 from Apr 20, 2022 - -pragma solidity >=0.7.6; - -import './interfaces/IMulticall.sol'; - -/// @title Multicall -/// @notice Enables calling multiple methods in a single call to the contract -abstract contract Multicall is IMulticall { - /// @inheritdoc IMulticall - function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) { - results = new bytes[](data.length); - for (uint256 i = 0; i < data.length; i++) { - (bool success, bytes memory result) = address(this).delegatecall(data[i]); - - if (!success) { - // Next 5 lines from https://ethereum.stackexchange.com/a/83577 - if (result.length < 68) revert(); - assembly { - result := add(result, 0x04) - } - revert(abi.decode(result, (string))); - } - - results[i] = result; - } - } -} \ No newline at end of file diff --git a/core/contracts/lib/erc4626/external/PeripheryPayments.sol b/core/contracts/lib/erc4626/external/PeripheryPayments.sol deleted file mode 100644 index 58803152e..000000000 --- a/core/contracts/lib/erc4626/external/PeripheryPayments.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on -// the project commit 643cd04 from Apr 20, 2022 - -pragma solidity >=0.7.5; - -import "solmate/src/utils/SafeTransferLib.sol"; - -/** - @title Periphery Payments - @notice Immutable state used by periphery contracts - Largely Forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/PeripheryPayments.sol - Changes: - * no interface - * no inheritdoc - * add immutable WETH9 in constructor instead of PeripheryImmutableState - * receive from any address - * Solmate interfaces and transfer lib - * casting - * add approve, wrapWETH9 and pullToken -*/ -abstract contract PeripheryPayments { - using SafeTransferLib for *; - - IWETH9 public immutable WETH9; - - constructor(IWETH9 _WETH9) { - WETH9 = _WETH9; - } - - receive() external payable {} - - function approve(ERC20 token, address to, uint256 amount) public payable { - token.safeApprove(to, amount); - } - - function unwrapWETH9(uint256 amountMinimum, address recipient) public payable { - uint256 balanceWETH9 = WETH9.balanceOf(address(this)); - require(balanceWETH9 >= amountMinimum, 'Insufficient WETH9'); - - if (balanceWETH9 > 0) { - WETH9.withdraw(balanceWETH9); - recipient.safeTransferETH(balanceWETH9); - } - } - - function wrapWETH9() public payable { - if (address(this).balance > 0) WETH9.deposit{value: address(this).balance}(); // wrap everything - } - - function pullToken(ERC20 token, uint256 amount, address recipient) public payable { - token.safeTransferFrom(msg.sender, recipient, amount); - } - - function sweepToken( - ERC20 token, - uint256 amountMinimum, - address recipient - ) public payable { - uint256 balanceToken = token.balanceOf(address(this)); - require(balanceToken >= amountMinimum, 'Insufficient token'); - - if (balanceToken > 0) { - token.safeTransfer(recipient, balanceToken); - } - } - - function refundETH() external payable { - if (address(this).balance > 0) SafeTransferLib.safeTransferETH(msg.sender, address(this).balance); - } -} - -abstract contract IWETH9 is ERC20 { - /// @notice Deposit ether to get wrapped ether - function deposit() external payable virtual; - - /// @notice Withdraw wrapped ether to get ether - function withdraw(uint256) external virtual; -} \ No newline at end of file diff --git a/core/contracts/lib/erc4626/external/SelfPermit.sol b/core/contracts/lib/erc4626/external/SelfPermit.sol deleted file mode 100644 index 6151db148..000000000 --- a/core/contracts/lib/erc4626/external/SelfPermit.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on -// the project commit 643cd04 from Apr 20, 2022 - -pragma solidity >=0.5.0; - -import {ERC20} from "solmate/src/tokens/ERC20.sol"; - -import './interfaces/ISelfPermit.sol'; -import './interfaces/IERC20PermitAllowed.sol'; - -/// @title Self Permit -/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route -/// @dev These functions are expected to be embedded in multicalls to allow EOAs to approve a contract and call a function -/// that requires an approval in a single transaction. -abstract contract SelfPermit is ISelfPermit { - /// @inheritdoc ISelfPermit - function selfPermit( - address token, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public payable override { - ERC20(token).permit(msg.sender, address(this), value, deadline, v, r, s); - } - - /// @inheritdoc ISelfPermit - function selfPermitIfNecessary( - address token, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external payable override { - if (ERC20(token).allowance(msg.sender, address(this)) < value) selfPermit(token, value, deadline, v, r, s); - } - - /// @inheritdoc ISelfPermit - function selfPermitAllowed( - address token, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) public payable override { - IERC20PermitAllowed(token).permit(msg.sender, address(this), nonce, expiry, true, v, r, s); - } - - /// @inheritdoc ISelfPermit - function selfPermitAllowedIfNecessary( - address token, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) external payable override { - if (ERC20(token).allowance(msg.sender, address(this)) < type(uint256).max) - selfPermitAllowed(token, nonce, expiry, v, r, s); - } -} diff --git a/core/contracts/lib/erc4626/external/interfaces/IERC20PermitAllowed.sol b/core/contracts/lib/erc4626/external/interfaces/IERC20PermitAllowed.sol deleted file mode 100644 index 9b991b6f0..000000000 --- a/core/contracts/lib/erc4626/external/interfaces/IERC20PermitAllowed.sol +++ /dev/null @@ -1,33 +0,0 @@ -// forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/external/IERC20PermitAllowed.sol - -// SPDX-License-Identifier: GPL-2.0-or-later - -// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on -// the project commit 643cd04 from Apr 20, 2022 - -pragma solidity >=0.5.0; - -/// @title Interface for permit -/// @notice Interface used by DAI/CHAI for permit -interface IERC20PermitAllowed { - /// @notice Approve the spender to spend some tokens via the holder signature - /// @dev This is the permit interface used by DAI and CHAI - /// @param holder The address of the token holder, the token owner - /// @param spender The address of the token spender - /// @param nonce The holder's nonce, increases at each call to permit - /// @param expiry The timestamp at which the permit is no longer valid - /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0 - /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` - /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` - /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` - function permit( - address holder, - address spender, - uint256 nonce, - uint256 expiry, - bool allowed, - uint8 v, - bytes32 r, - bytes32 s - ) external; -} \ No newline at end of file diff --git a/core/contracts/lib/erc4626/external/interfaces/IMulticall.sol b/core/contracts/lib/erc4626/external/interfaces/IMulticall.sol deleted file mode 100644 index 4a58f8e0a..000000000 --- a/core/contracts/lib/erc4626/external/interfaces/IMulticall.sol +++ /dev/null @@ -1,18 +0,0 @@ -// forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/IMulticall.sol - -// SPDX-License-Identifier: GPL-2.0-or-later - -// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on -// the project commit 643cd04 from Apr 20, 2022 - -pragma solidity >=0.7.5; - -/// @title Multicall interface -/// @notice Enables calling multiple methods in a single call to the contract -interface IMulticall { - /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed - /// @dev The `msg.value` should not be trusted for any method callable from multicall. - /// @param data The encoded function data for each of the calls to make to this contract - /// @return results The results from each of the calls passed in via data - function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); -} diff --git a/core/contracts/lib/erc4626/external/interfaces/ISelfPermit.sol b/core/contracts/lib/erc4626/external/interfaces/ISelfPermit.sol deleted file mode 100644 index c9815b6d9..000000000 --- a/core/contracts/lib/erc4626/external/interfaces/ISelfPermit.sol +++ /dev/null @@ -1,82 +0,0 @@ -// forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/ISelfPermit.sol - -// SPDX-License-Identifier: GPL-2.0-or-later - -// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on -// the project commit 643cd04 from Apr 20, 2022 - -pragma solidity >=0.7.5; - -/// @title Self Permit -/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route -interface ISelfPermit { - /// @notice Permits this contract to spend a given token from `msg.sender` - /// @dev The `owner` is always msg.sender and the `spender` is always address(this). - /// @param token The address of the token spent - /// @param value The amount that can be spent of token - /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp - /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` - /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` - /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` - function selfPermit( - address token, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external payable; - - /// @notice Permits this contract to spend a given token from `msg.sender` - /// @dev The `owner` is always msg.sender and the `spender` is always address(this). - /// Can be used instead of #selfPermit to prevent calls from failing due to a frontrun of a call to #selfPermit - /// @param token The address of the token spent - /// @param value The amount that can be spent of token - /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp - /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` - /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` - /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` - function selfPermitIfNecessary( - address token, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external payable; - - /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter - /// @dev The `owner` is always msg.sender and the `spender` is always address(this) - /// @param token The address of the token spent - /// @param nonce The current nonce of the owner - /// @param expiry The timestamp at which the permit is no longer valid - /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` - /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` - /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` - function selfPermitAllowed( - address token, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) external payable; - - /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter - /// @dev The `owner` is always msg.sender and the `spender` is always address(this) - /// Can be used instead of #selfPermitAllowed to prevent calls from failing due to a frontrun of a call to #selfPermitAllowed. - /// @param token The address of the token spent - /// @param nonce The current nonce of the owner - /// @param expiry The timestamp at which the permit is no longer valid - /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` - /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` - /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` - function selfPermitAllowedIfNecessary( - address token, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) external payable; -} diff --git a/core/contracts/lib/erc4626/interfaces/IERC4626.sol b/core/contracts/lib/erc4626/interfaces/IERC4626.sol deleted file mode 100644 index bf287736a..000000000 --- a/core/contracts/lib/erc4626/interfaces/IERC4626.sol +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on -// the project commit 643cd04 from Apr 20, 2022 - -pragma solidity 0.8.10; - -import {ERC20} from "solmate/src/tokens/ERC20.sol"; - -/// @title ERC4626 interface -/// See: https://eips.ethereum.org/EIPS/eip-4626 -abstract contract IERC4626 is ERC20 { - /*//////////////////////////////////////////////////////// - Events - ////////////////////////////////////////////////////////*/ - - /// @notice `sender` has exchanged `assets` for `shares`, - /// and transferred those `shares` to `receiver`. - event Deposit(address indexed sender, address indexed receiver, uint256 assets, uint256 shares); - - /// @notice `sender` has exchanged `shares` for `assets`, - /// and transferred those `assets` to `receiver`. - event Withdraw(address indexed sender, address indexed receiver, uint256 assets, uint256 shares); - - /*//////////////////////////////////////////////////////// - Vault properties - ////////////////////////////////////////////////////////*/ - - /// @notice The address of the underlying ERC20 token used for - /// the Vault for accounting, depositing, and withdrawing. - function asset() external view virtual returns (address asset); - - /// @notice Total amount of the underlying asset that - /// is "managed" by Vault. - function totalAssets() external view virtual returns (uint256 totalAssets); - - /*//////////////////////////////////////////////////////// - Deposit/Withdrawal Logic - ////////////////////////////////////////////////////////*/ - - /// @notice Mints `shares` Vault shares to `receiver` by - /// depositing exactly `assets` of underlying tokens. - function deposit(uint256 assets, address receiver) external virtual returns (uint256 shares); - - /// @notice Mints exactly `shares` Vault shares to `receiver` - /// by depositing `assets` of underlying tokens. - function mint(uint256 shares, address receiver) external virtual returns (uint256 assets); - - /// @notice Redeems `shares` from `owner` and sends `assets` - /// of underlying tokens to `receiver`. - function withdraw( - uint256 assets, - address receiver, - address owner - ) external virtual returns (uint256 shares); - - /// @notice Redeems `shares` from `owner` and sends `assets` - /// of underlying tokens to `receiver`. - function redeem( - uint256 shares, - address receiver, - address owner - ) external virtual returns (uint256 assets); - - /*//////////////////////////////////////////////////////// - Vault Accounting Logic - ////////////////////////////////////////////////////////*/ - - /// @notice The amount of shares that the vault would - /// exchange for the amount of assets provided, in an - /// ideal scenario where all the conditions are met. - function convertToShares(uint256 assets) external view virtual returns (uint256 shares); - - /// @notice The amount of assets that the vault would - /// exchange for the amount of shares provided, in an - /// ideal scenario where all the conditions are met. - function convertToAssets(uint256 shares) external view virtual returns (uint256 assets); - - /// @notice Total number of underlying assets that can - /// be deposited by `owner` into the Vault, where `owner` - /// corresponds to the input parameter `receiver` of a - /// `deposit` call. - function maxDeposit(address owner) external view virtual returns (uint256 maxAssets); - - /// @notice Allows an on-chain or off-chain user to simulate - /// the effects of their deposit at the current block, given - /// current on-chain conditions. - function previewDeposit(uint256 assets) external view virtual returns (uint256 shares); - - /// @notice Total number of underlying shares that can be minted - /// for `owner`, where `owner` corresponds to the input - /// parameter `receiver` of a `mint` call. - function maxMint(address owner) external view virtual returns (uint256 maxShares); - - /// @notice Allows an on-chain or off-chain user to simulate - /// the effects of their mint at the current block, given - /// current on-chain conditions. - function previewMint(uint256 shares) external view virtual returns (uint256 assets); - - /// @notice Total number of underlying assets that can be - /// withdrawn from the Vault by `owner`, where `owner` - /// corresponds to the input parameter of a `withdraw` call. - function maxWithdraw(address owner) external view virtual returns (uint256 maxAssets); - - /// @notice Allows an on-chain or off-chain user to simulate - /// the effects of their withdrawal at the current block, - /// given current on-chain conditions. - function previewWithdraw(uint256 assets) external view virtual returns (uint256 shares); - - /// @notice Total number of underlying shares that can be - /// redeemed from the Vault by `owner`, where `owner` corresponds - /// to the input parameter of a `redeem` call. - function maxRedeem(address owner) external view virtual returns (uint256 maxShares); - - /// @notice Allows an on-chain or off-chain user to simulate - /// the effects of their redeemption at the current block, - /// given current on-chain conditions. - function previewRedeem(uint256 shares) external view virtual returns (uint256 assets); -} diff --git a/core/contracts/lib/erc4626/interfaces/IERC4626RouterBase.sol b/core/contracts/lib/erc4626/interfaces/IERC4626RouterBase.sol deleted file mode 100644 index 4154b664b..000000000 --- a/core/contracts/lib/erc4626/interfaces/IERC4626RouterBase.sol +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on -// the project commit 643cd04 from Apr 20, 2022 - -pragma solidity 0.8.10; - -import "./IERC4626.sol"; - -/** - @title ERC4626Router Base Interface - @notice A canonical router between ERC4626 Vaults https://eips.ethereum.org/EIPS/eip-4626 - - The base router is a multicall style router inspired by Uniswap v3 with built-in features for permit, WETH9 wrap/unwrap, and ERC20 token pulling/sweeping/approving. - It includes methods for the four mutable ERC4626 functions deposit/mint/withdraw/redeem as well. - - These can all be arbitrarily composed using the multicall functionality of the router. - - NOTE the router is capable of pulling any approved token from your wallet. This is only possible when your address is msg.sender, but regardless be careful when interacting with the router or ERC4626 Vaults. - The router makes no special considerations for unique ERC20 implementations such as fee on transfer. - There are no built in protections for unexpected behavior beyond enforcing the minSharesOut is received. - */ -interface IERC4626RouterBase { - /************************** Errors **************************/ - - /// @notice thrown when amount of assets received is below the min set by caller - error MinAmountError(); - - /// @notice thrown when amount of shares received is below the min set by caller - error MinSharesError(); - - /// @notice thrown when amount of assets received is above the max set by caller - error MaxAmountError(); - - /// @notice thrown when amount of shares received is above the max set by caller - error MaxSharesError(); - - /************************** Mint **************************/ - - /** - @notice mint `shares` from an ERC4626 vault. - @param vault The ERC4626 vault to mint shares from. - @param to The destination of ownership shares. - @param shares The amount of shares to mint from `vault`. - @param maxAmountIn The max amount of assets used to mint. - @return amountIn the amount of assets used to mint by `to`. - @dev throws MaxAmountError - */ - function mint( - IERC4626 vault, - address to, - uint256 shares, - uint256 maxAmountIn - ) external payable returns (uint256 amountIn); - - /************************** Deposit **************************/ - - /** - @notice deposit `amount` to an ERC4626 vault. - @param vault The ERC4626 vault to deposit assets to. - @param to The destination of ownership shares. - @param amount The amount of assets to deposit to `vault`. - @param minSharesOut The min amount of `vault` shares received by `to`. - @return sharesOut the amount of shares received by `to`. - @dev throws MinSharesError - */ - function deposit( - IERC4626 vault, - address to, - uint256 amount, - uint256 minSharesOut - ) external payable returns (uint256 sharesOut); - - /************************** Withdraw **************************/ - - /** - @notice withdraw `amount` from an ERC4626 vault. - @param vault The ERC4626 vault to withdraw assets from. - @param to The destination of assets. - @param amount The amount of assets to withdraw from vault. - @param minSharesOut The min amount of shares received by `to`. - @return sharesOut the amount of shares received by `to`. - @dev throws MaxSharesError - */ - function withdraw( - IERC4626 vault, - address to, - uint256 amount, - uint256 minSharesOut - ) external payable returns (uint256 sharesOut); - - /************************** Redeem **************************/ - - /** - @notice redeem `shares` shares from an ERC4626 vault. - @param vault The ERC4626 vault to redeem shares from. - @param to The destination of assets. - @param shares The amount of shares to redeem from vault. - @param minAmountOut The min amount of assets received by `to`. - @return amountOut the amount of assets received by `to`. - @dev throws MinAmountError - */ - function redeem( - IERC4626 vault, - address to, - uint256 shares, - uint256 minAmountOut - ) external payable returns (uint256 amountOut); -} From 5bd254a15a0e8264b8486cc6334e5a1346d478c7 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 24 Nov 2023 12:47:56 +0100 Subject: [PATCH 11/21] Adding IERC4626 interface --- .../lib/erc4626/interfaces/IERC4626.sol | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 core/contracts/lib/erc4626/interfaces/IERC4626.sol diff --git a/core/contracts/lib/erc4626/interfaces/IERC4626.sol b/core/contracts/lib/erc4626/interfaces/IERC4626.sol new file mode 100644 index 000000000..bfbc1f1e3 --- /dev/null +++ b/core/contracts/lib/erc4626/interfaces/IERC4626.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; + +/// @title ERC4626 interface +/// See: https://eips.ethereum.org/EIPS/eip-4626 +abstract contract IERC4626 is ERC20 { + /*//////////////////////////////////////////////////////// + Events + ////////////////////////////////////////////////////////*/ + + /// @notice `sender` has exchanged `assets` for `shares`, + /// and transferred those `shares` to `receiver`. + event Deposit(address indexed sender, address indexed receiver, uint256 assets, uint256 shares); + + /// @notice `sender` has exchanged `shares` for `assets`, + /// and transferred those `assets` to `receiver`. + event Withdraw(address indexed sender, address indexed receiver, uint256 assets, uint256 shares); + + /*//////////////////////////////////////////////////////// + Vault properties + ////////////////////////////////////////////////////////*/ + + /// @notice The address of the underlying ERC20 token used for + /// the Vault for accounting, depositing, and withdrawing. + function asset() external view virtual returns (address asset); + + /// @notice Total amount of the underlying asset that + /// is "managed" by Vault. + function totalAssets() external view virtual returns (uint256 totalAssets); + + /*//////////////////////////////////////////////////////// + Deposit/Withdrawal Logic + ////////////////////////////////////////////////////////*/ + + /// @notice Mints `shares` Vault shares to `receiver` by + /// depositing exactly `assets` of underlying tokens. + function deposit(uint256 assets, address receiver) external virtual returns (uint256 shares); + + /// @notice Mints exactly `shares` Vault shares to `receiver` + /// by depositing `assets` of underlying tokens. + function mint(uint256 shares, address receiver) external virtual returns (uint256 assets); + + /// @notice Redeems `shares` from `owner` and sends `assets` + /// of underlying tokens to `receiver`. + function withdraw( + uint256 assets, + address receiver, + address owner + ) external virtual returns (uint256 shares); + + /// @notice Redeems `shares` from `owner` and sends `assets` + /// of underlying tokens to `receiver`. + function redeem( + uint256 shares, + address receiver, + address owner + ) external virtual returns (uint256 assets); + + /*//////////////////////////////////////////////////////// + Vault Accounting Logic + ////////////////////////////////////////////////////////*/ + + /// @notice The amount of shares that the vault would + /// exchange for the amount of assets provided, in an + /// ideal scenario where all the conditions are met. + function convertToShares(uint256 assets) external view virtual returns (uint256 shares); + + /// @notice The amount of assets that the vault would + /// exchange for the amount of shares provided, in an + /// ideal scenario where all the conditions are met. + function convertToAssets(uint256 shares) external view virtual returns (uint256 assets); + + /// @notice Total number of underlying assets that can + /// be deposited by `owner` into the Vault, where `owner` + /// corresponds to the input parameter `receiver` of a + /// `deposit` call. + function maxDeposit(address owner) external view virtual returns (uint256 maxAssets); + + /// @notice Allows an on-chain or off-chain user to simulate + /// the effects of their deposit at the current block, given + /// current on-chain conditions. + function previewDeposit(uint256 assets) external view virtual returns (uint256 shares); + + /// @notice Total number of underlying shares that can be minted + /// for `owner`, where `owner` corresponds to the input + /// parameter `receiver` of a `mint` call. + function maxMint(address owner) external view virtual returns (uint256 maxShares); + + /// @notice Allows an on-chain or off-chain user to simulate + /// the effects of their mint at the current block, given + /// current on-chain conditions. + function previewMint(uint256 shares) external view virtual returns (uint256 assets); + + /// @notice Total number of underlying assets that can be + /// withdrawn from the Vault by `owner`, where `owner` + /// corresponds to the input parameter of a `withdraw` call. + function maxWithdraw(address owner) external view virtual returns (uint256 maxAssets); + + /// @notice Allows an on-chain or off-chain user to simulate + /// the effects of their withdrawal at the current block, + /// given current on-chain conditions. + function previewWithdraw(uint256 assets) external view virtual returns (uint256 shares); + + /// @notice Total number of underlying shares that can be + /// redeemed from the Vault by `owner`, where `owner` corresponds + /// to the input parameter of a `redeem` call. + function maxRedeem(address owner) external view virtual returns (uint256 maxShares); + + /// @notice Allows an on-chain or off-chain user to simulate + /// the effects of their redeemption at the current block, + /// given current on-chain conditions. + function previewRedeem(uint256 shares) external view virtual returns (uint256 assets); +} From e5e31d6dc998403212ad1ccdcf9296093be6ac79 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 15 Nov 2023 21:24:45 +0100 Subject: [PATCH 12/21] Update pre-commit hooks for `core` workspace Define pre-commit hooks for core package. Co-authored-by: Rafal Czajkowski --- .pre-commit-config.yaml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c700c60d4..a08515b37 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,25 @@ repos: - repo: local hooks: - - id: lint-sol + # Core + - id: core-lint-sol name: "lint core sol" - entry: /usr/bin/env bash -c "cd core/ && npm run lint:sol" - files: '\.sol$' + entry: /usr/bin/env bash -c "npm --prefix ./core/ run lint:sol" + files: ^core/ + types: [solidity] language: script description: "Checks solidity code according to the package's linter configuration" - - id: lint-js + - id: core-lint-js name: "lint core ts/js" - entry: /usr/bin/env bash -c "cd core/ && npm run lint:js" - files: '\.(ts|js)$' + entry: /usr/bin/env bash -c "npm --prefix ./core/ run lint:js" + files: ^core/ + types_or: [ts, javascript] language: script description: "Checks TS/JS code according to the package's linter configuration" - - id: lint-config + - id: core-lint-config name: "lint core json/yaml" - entry: /usr/bin/env bash -c "cd core/ && npm run lint:config" - files: '\.(json|yaml)$' + entry: /usr/bin/env bash -c "npm --prefix ./core/ run lint:config" + files: ^core/ + types_or: [json, yaml] language: script description: "Checks JSON/YAML code according to the package's linter configuration" From fcea06fd448cbddcbe8e02799f4a29c84b0a90e0 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 15 Nov 2023 21:29:22 +0100 Subject: [PATCH 13/21] Add pre-commit hooks for website workspace Co-authored-by: Rafal Czajkowski --- .pre-commit-config.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a08515b37..7e0cf2b8f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,3 +23,18 @@ repos: types_or: [json, yaml] language: script description: "Checks JSON/YAML code according to the package's linter configuration" + # Website + - id: website-lint-js + name: "lint website ts/js" + entry: /usr/bin/env bash -c "npm --prefix ./website/ run lint:js" + files: ^website/ + types_or: [ts, tsx, javascript, jsx] + language: script + description: "Checks TS/JS code according to the package's linter configuration" + - id: website-lint-config + name: "lint website json/yaml" + entry: /usr/bin/env bash -c "npm --prefix ./website/ run lint:config" + files: ^website/ + types_or: [json, yaml] + language: script + description: "Checks JSON/YAML code according to the package's linter configuration" From 8db37ed28f674ae6c0b2dc527eb9f4b4c6f70a5d Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 23 Nov 2023 12:09:53 +0100 Subject: [PATCH 14/21] Add pre-commit hooks for dapp workspace --- .pre-commit-config.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7e0cf2b8f..e55db32ed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,6 +23,21 @@ repos: types_or: [json, yaml] language: script description: "Checks JSON/YAML code according to the package's linter configuration" + # dApp + - id: dapp-lint-js + name: "lint dapp ts/js" + entry: /usr/bin/env bash -c "npm --prefix ./dapp/ run lint:js" + files: ^dapp/ + types_or: [ts, tsx, javascript, jsx] + language: script + description: "Checks TS/JS code according to the package's linter configuration" + - id: dapp-lint-config + name: "lint dapp json/yaml" + entry: /usr/bin/env bash -c "npm --prefix ./dapp/ run lint:config" + files: ^dapp/ + types_or: [json, yaml] + language: script + description: "Checks JSON/YAML code according to the package's linter configuration" # Website - id: website-lint-js name: "lint website ts/js" From ca0fa0a9f3d4c185280251bb8da782133bbcb8a1 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 23 Nov 2023 12:11:17 +0100 Subject: [PATCH 15/21] Auto-fix dapp files formatting --- dapp/manifest-ledger-live-app.json | 47 +++++++++++++----------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/dapp/manifest-ledger-live-app.json b/dapp/manifest-ledger-live-app.json index a6ed3d306..a03b1bb09 100644 --- a/dapp/manifest-ledger-live-app.json +++ b/dapp/manifest-ledger-live-app.json @@ -1,30 +1,23 @@ { - "id": "acre", - "name": "ACRE", - "url": "http://localhost:5173/", - "homepageUrl": "http://localhost:5173/", - "icon": "http://localhost:5173/acre.svg", - "platform": "all", - "apiVersion": "2.0.0", - "manifestVersion": "1", - "branch": "stable", - "categories": [ - "buy" - ], - "currencies": [ - "bitcoin", - "bitcoin_testnet" - ], - "content": { - "shortDescription": { - "en": "Bitcoin Liquid Staking" - }, - "description": { - "en": "Bitcoin Liquid Staking" - } + "id": "acre", + "name": "ACRE", + "url": "http://localhost:5173/", + "homepageUrl": "http://localhost:5173/", + "icon": "http://localhost:5173/acre.svg", + "platform": "all", + "apiVersion": "2.0.0", + "manifestVersion": "1", + "branch": "stable", + "categories": ["buy"], + "currencies": ["bitcoin", "bitcoin_testnet"], + "content": { + "shortDescription": { + "en": "Bitcoin Liquid Staking" }, - "permissions": [], - "domains": [ - "http://*" - ] + "description": { + "en": "Bitcoin Liquid Staking" + } + }, + "permissions": [], + "domains": ["http://*"] } From 60edc3fbc38b63023d2c2f2775b57bb01c4cb87d Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 23 Nov 2023 12:13:36 +0100 Subject: [PATCH 16/21] Add auto-fix commit to git blame ignore revs --- .git-blame-ignore-revs | 2 ++ core/.git-blame-ignore-revs | 0 2 files changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs delete mode 100644 core/.git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..b4f1cac41 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Auto-fix linting +d2a058fe6cfbab6f82d0d977d1b2d8bd9f494df1 diff --git a/core/.git-blame-ignore-revs b/core/.git-blame-ignore-revs deleted file mode 100644 index e69de29bb..000000000 From 238f2abffbbb42fb722562cf6793f2a50f9a5ca3 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 23 Nov 2023 12:20:59 +0100 Subject: [PATCH 17/21] Add info about testing pre-commit hooks config When configuring the hooks we may want to test them before pushing. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 431a793c6..de2f62834 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,15 @@ To setup the hooks follow the steps: ```sh pre-commit install ``` + +#### Testing + +To test the pre-commit hooks configuration you can invoke them with one of the +commands: +```sh +# Execute hooks for all files: +pre-commit run --all-files + +# Execute hooks for specific files (e.g. Acre.sol): +pre-commit run --files ./core/contracts/Acre.sol +``` From e0ba0ac5020034e4c40dbbb116bde65ac637991b Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Fri, 17 Nov 2023 15:00:35 +0100 Subject: [PATCH 18/21] Add basic GH workflow for website workspace This is an initial implementation of the CI process to check formatting in `website` workspace. Co-authored-by: Rafal Czajkowski --- .github/workflows/website.yaml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/website.yaml diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml new file mode 100644 index 000000000..e1b62779d --- /dev/null +++ b/.github/workflows/website.yaml @@ -0,0 +1,32 @@ +name: Website + +on: + push: + branches: + - main + paths: + - "website/**" + pull_request: + +defaults: + run: + working-directory: ./website + +jobs: + website-format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: "website/.nvmrc" + cache: "yarn" + cache-dependency-path: "website/yarn.lock" + + - name: Install Dependencies + run: yarn install --prefer-offline --frozen-lockfile + + - name: Format + run: yarn format From ef5929d9281ca034989d10fdec4781fb6cffd0b1 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 23 Nov 2023 12:27:23 +0100 Subject: [PATCH 19/21] Add basic GH workflow for dapp workspace This is an initial implementation of the CI process to check formatting in `dapp` workspace. --- .github/workflows/dapp.yaml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/dapp.yaml diff --git a/.github/workflows/dapp.yaml b/.github/workflows/dapp.yaml new file mode 100644 index 000000000..a06389992 --- /dev/null +++ b/.github/workflows/dapp.yaml @@ -0,0 +1,32 @@ +name: dApp + +on: + push: + branches: + - main + paths: + - "dapp/**" + pull_request: + +defaults: + run: + working-directory: ./dapp + +jobs: + dapp-format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: "dapp/.nvmrc" + cache: "yarn" + cache-dependency-path: "dapp/yarn.lock" + + - name: Install Dependencies + run: yarn install --prefer-offline --frozen-lockfile + + - name: Format + run: yarn format From 06edcd262deb361a93f5598069ed0da262cb0908 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 23 Nov 2023 12:34:04 +0100 Subject: [PATCH 20/21] Add build execution in dapp and website CI --- .github/workflows/dapp.yaml | 18 ++++++++++++++++++ .github/workflows/website.yaml | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/.github/workflows/dapp.yaml b/.github/workflows/dapp.yaml index a06389992..81dc9c31c 100644 --- a/.github/workflows/dapp.yaml +++ b/.github/workflows/dapp.yaml @@ -30,3 +30,21 @@ jobs: - name: Format run: yarn format + + dapp-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: "dapp/.nvmrc" + cache: "yarn" + cache-dependency-path: "dapp/yarn.lock" + + - name: Install Dependencies + run: yarn install --prefer-offline --frozen-lockfile + + - name: Build + run: yarn build diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index e1b62779d..89583fbb3 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -30,3 +30,21 @@ jobs: - name: Format run: yarn format + + website-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: "website/.nvmrc" + cache: "yarn" + cache-dependency-path: "website/yarn.lock" + + - name: Install Dependencies + run: yarn install --prefer-offline --frozen-lockfile + + - name: Build + run: yarn build From b7aee18c080069ba7a3bcab90f42a10e01813337 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 24 Nov 2023 23:04:55 +0100 Subject: [PATCH 21/21] Drafting allocate and collect functions for Acre Router --- core/contracts/AcreRouter.sol | 60 +++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/core/contracts/AcreRouter.sol b/core/contracts/AcreRouter.sol index 2d4cc65e9..b7928c035 100644 --- a/core/contracts/AcreRouter.sol +++ b/core/contracts/AcreRouter.sol @@ -1,17 +1,16 @@ -pragma solidity ^0.8.10; +pragma solidity 0.8.20; -import {ERC4626RouterBase} from "./lib/erc4626/ERC4626RouterBase.sol"; -import {PeripheryPayments, IWETH9} from "./lib/erc4626/external/PeripheryPayments.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {ERC4626} from "solmate/src/mixins/ERC4626.sol"; import {Owned} from "solmate/src/auth/Owned.sol"; -import {IERC4626} from "./lib/erc4626/interfaces/IERC4626.sol"; // TODO: add description -contract AcreRouter is ERC4626RouterBase, Owned { +contract AcreRouter is Owned { using SafeTransferLib for ERC20; ERC20 public immutable stBTC; + ERC20 public immutable tBTC; /// @notice Approved allocators which essentially are the ERC4626 vaults that /// deposit funds to yield strategies, e.g. Uniswap V3 WBTC/TBTC pool. @@ -19,30 +18,31 @@ contract AcreRouter is ERC4626RouterBase, Owned { /// perspective, the Allocator contract can be a part of an external /// Yield Module and does not care how the yield is generated. address[] public allocators; - mapping(address => bool) public isAllocator; - /// @notice Indicates if the given address is an Acre Manager. Only Acre Manager - /// can set or remove allocators. + /// @notice Acre Manager address. Only Acre Manager can set or remove + /// Strategy Allocators. address public acreManager; event AllocatorAdded(address indexed allocator); event AllocatorRemoved(address indexed allocator); + event AcreManagerSet(address indexed manager); + modifier onlyAcreManager() { require(msg.sender == acreManager, "Caller is not an Acre Manager"); _; } - constructor( - IWETH9 weth, - ERC20 _stBTC - ) Owned(msg.sender) PeripheryPayments(weth) { + constructor(ERC20 _stBTC, ERC20 _tBTC) Owned(msg.sender) { stBTC = _stBTC; + tBTC = _tBTC; } - function setAcreManager(address _acreManager) external onlyOwner { - acreManager = _acreManager; + function setAcreManager(address manager) external onlyOwner { + require(manager != address(0), "Zero address"); + acreManager = manager; + emit AcreManagerSet(manager); } function addAllocator(address allocator) external onlyAcreManager { @@ -53,14 +53,13 @@ contract AcreRouter is ERC4626RouterBase, Owned { } function removeAllocator(address allocator) external onlyAcreManager { - require(isAllocator[allocator], "This address is not a minter"); + require(isAllocator[allocator], "Not an allocator"); delete isAllocator[allocator]; for (uint256 i = 0; i < allocators.length; i++) { if (allocators[i] == allocator) { allocators[i] = allocators[allocators.length - 1]; - // slither-disable-next-line costly-loop allocators.pop(); break; } @@ -69,25 +68,38 @@ contract AcreRouter is ERC4626RouterBase, Owned { emit AllocatorRemoved(allocator); } - /// @notice Allocates funds from stBTC (Acre) to an allocator + /// @notice Routes funds from stBTC (Acre) to a given allocator + /// @param allocator Address of the allocator to route the funds to. + /// @param amount Amount of TBTC to allocate. function allocate(address allocator, uint256 amount) public { - require(msg.sender == address(stBTC), "stBTC must be a caller"); - + require(msg.sender == address(stBTC), "stBTC only"); if (!isAllocator[allocator]) { revert("Allocator is not approved"); } - // TODO: implement allocation logic to an allocator + tBTC.safeTransferFrom(msg.sender, address(this), amount); + tBTC.safeApprove(allocator, amount); + // TODO: implement protection from the inflation attack / slippage + ERC4626(allocator).deposit(amount, address(this)); } - /// @notice Collects funds from an allocator and sends them to stBTC (Acre) - function collect(address allocator, uint256 amount) public { - require(msg.sender == address(stBTC), "stBTC must be a caller"); + /// @notice Collects TBTC from an allocator and approves them to be collected + /// by stBTC (Acre) + /// @param allocator Address of the allocator to collect the assets from. + /// @param shares Amount of shares to collect. Shares are the internal representation + /// of the underlying asset in the allocator. Concrete amount of the + /// underlying asset is calculated by calling `convertToAssets` on + /// the allocator and the shares are burned. + function collect(address allocator, uint256 shares) public { + require(msg.sender == address(stBTC), "stBTC only"); if (!isAllocator[allocator]) { revert("Allocator is not approved"); } - // TODO: implement collection logic from an allocator + // TODO: implement protection from the inflation attack / slippage + // TODO: use IERC4626 interface + uint256 assets = ERC4626(allocator).redeem(shares, address(this), address(this)); + tBTC.safeApprove(address(stBTC), assets); } }