From f1f439d45b07e5da0ce1ac8450bbebae57ad1fc9 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 8 Dec 2023 17:37:02 +0100 Subject: [PATCH] Adding deposit and redeem functionality Renamed AcreRouter to Dispatcher and introduced an abstract contract called Router that will handle moving funds between Acre and Vaults. In order to deposit / redeem assets, Acre contract should approve assets for Dispatcher to spend and call depositToVault or redeemFromVault functions. These Acre's functions can be also called by a bot for rebalancing purposes. --- .../{AcreRouter.sol => Dispatcher.sol} | 44 ++++++++++++-- core/contracts/Router.sol | 57 +++++++++++++++++++ 2 files changed, 97 insertions(+), 4 deletions(-) rename core/contracts/{AcreRouter.sol => Dispatcher.sol} (60%) create mode 100644 core/contracts/Router.sol diff --git a/core/contracts/AcreRouter.sol b/core/contracts/Dispatcher.sol similarity index 60% rename from core/contracts/AcreRouter.sol rename to core/contracts/Dispatcher.sol index c7224e324..425d55ef2 100644 --- a/core/contracts/AcreRouter.sol +++ b/core/contracts/Dispatcher.sol @@ -2,16 +2,25 @@ pragma solidity ^0.8.21; import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import "./Router.sol"; -/// @title AcreRouter -/// @notice AcreRouter is a contract that routes TBTC from stBTC (Acre) to +/// @title Dispatcher +/// @notice Dispatcher is a contract that routes tBTC from stBTC (Acre) to /// a given vault and back. Vaults supply yield strategies with TBTC that /// generate yield for Bitcoin holders. -contract AcreRouter is Ownable { +/// TODO: add more description around vaults. +contract Dispatcher is Router, Ownable { + using SafeERC20 for IERC20; + struct Vault { bool approved; } + IERC20 public immutable stBTC; // Acre contract + IERC20 public immutable tBTC; + /// @notice Approved vaults within the Yiern Modules that implement ERC4626 /// standard. These vaults deposit assets to yield strategies, e.g. /// Uniswap V3 WBTC/TBTC pool. Vault can be a part of Acre ecosystem @@ -24,7 +33,10 @@ contract AcreRouter is Ownable { event VaultAdded(address indexed vault); event VaultRemoved(address indexed vault); - constructor() Ownable(msg.sender) {} + constructor(IERC20 _stBTC, IERC20 _tBTC) Ownable(msg.sender) { + stBTC = _stBTC; + tBTC = _tBTC; + } /// @notice Adds a vault to the list of approved vaults. /// @param vault Address of the vault to add. @@ -59,4 +71,28 @@ contract AcreRouter is Ownable { function vaultsLength() external view returns (uint256) { return vaults.length; } + + // TODO: add documentation + function depositToVault( + address vault, + uint256 amount, + uint256 minSharesOut + ) public { + require(msg.sender == address(stBTC), "stBTC only"); + require(vaultsInfo[vault].approved, "Vault is not approved"); + + deposit(IERC4626(vault), address(stBTC), amount, minSharesOut); + } + + // TODO: add documentation + function redeemFromVault( + address vault, + uint256 shares, + uint256 minAssetsOut + ) public { + require(msg.sender == address(stBTC), "stBTC only"); + require(vaultsInfo[vault].approved, "Vault is not approved"); + + redeem(IERC4626(vault), address(stBTC), shares, minAssetsOut); + } } diff --git a/core/contracts/Router.sol b/core/contracts/Router.sol new file mode 100644 index 000000000..7a965d264 --- /dev/null +++ b/core/contracts/Router.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/interfaces/IERC20.sol"; +import "@openzeppelin/contracts/interfaces/IERC4626.sol"; + +/// TODO: Add documentation +abstract contract Router { + using SafeERC20 for IERC20; + + /// @notice Routes funds from stBTC (Acre) to a vault. The amount of tBTC to + /// Shares of deposited tBTC are minted to the stBTC contract. + /// @param vault Address of the vault to route the funds to. + /// @param to Address of the receiver of the shares. + /// @param amount Amount of tBTC to deposit. + /// @param minSharesOut Minimum amount of shares to receive. + function deposit( + IERC4626 vault, + address to, + uint256 amount, + uint256 minSharesOut + ) public returns (uint256 sharesOut) { + IERC20(vault.asset()).safeTransferFrom( + msg.sender, + address(this), + amount + ); + IERC20(vault.asset()).safeIncreaseAllowance(address(vault), amount); + if ((sharesOut = vault.deposit(amount, to)) < minSharesOut) { + revert("Not enough shares received"); + } + } + + /// @notice Redeem tBTC from a vault and approve tokens to be transferred + /// by stBTC (Acre) + /// @param vault Address of the vault to collect the assets from. + /// @param to Address of the receiver of the assets. + /// @param shares Amount of shares to collect. Shares are the internal representation + /// of the underlying asset in the vault. Concrete amount of the + /// underlying asset is calculated by calling `convertToAssets` on + /// the vault and the shares are burned. + /// @param minAssetsOut Minimum amount of TBTC to receive. + function redeem( + IERC4626 vault, + address to, + uint256 shares, + uint256 minAssetsOut + ) public returns (uint256 assetsOut) { + if ( + (assetsOut = vault.redeem(shares, address(this), to)) < minAssetsOut + ) { + revert("Not enough assets received"); + } + IERC20(vault.asset()).safeIncreaseAllowance(to, assetsOut); + } +}