Skip to content

Commit

Permalink
Modify routing logic
Browse files Browse the repository at this point in the history
With changes in this commit the vault shares will be held by the main
Acre contract.
  • Loading branch information
nkuba committed Dec 13, 2023
1 parent f8c0d2d commit b402c49
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 76 deletions.
13 changes: 13 additions & 0 deletions core/contracts/Acre.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.21;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "./Dispatcher.sol";

Expand All @@ -18,6 +19,10 @@ import "./Dispatcher.sol";
/// burning of shares (stBTC), which are represented as standard ERC20
/// tokens, providing a seamless exchange with tBTC tokens.
contract Acre is ERC4626, Ownable {
using SafeERC20 for IERC20;

error CallerNotDispatcher();

event StakeReferral(bytes32 indexed referral, uint256 assets);

Dispatcher public dispatcher;
Expand Down Expand Up @@ -53,10 +58,18 @@ contract Acre is ERC4626, Ownable {
function upgradeDispatcher(Dispatcher _newDispatcher) public onlyOwner {
if (address(dispatcher) != address(0)) {
IERC20(asset()).approve(address(dispatcher), 0);
// TODO: Remove dispatcher's approvals for the vaults.
}

dispatcher = _newDispatcher;

IERC20(asset()).approve(address(dispatcher), type(uint256).max);
}

function approveVaultSharesForDispatcher(address vault, uint256 amount) external {
if (msg.sender != address(dispatcher)) revert CallerNotDispatcher();

// TODO: Emit event
require(IERC20(vault).approve(address(dispatcher), amount), "allowance setting failed");
}
}
71 changes: 59 additions & 12 deletions core/contracts/Dispatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "./Router.sol";
import "./Acre.sol";

/// a given vault and back. Vaults supply yield strategies with TBTC that
/// generate yield for Bitcoin holders.
contract Dispatcher is Ownable {
contract Dispatcher is Router, Ownable {
using SafeERC20 for IERC20;


error VaultAlreadyAuthorized();
error VaultUnauthorized();

Expand All @@ -20,6 +22,7 @@ contract Dispatcher is Ownable {
}

Acre acre;
IERC20 tbtc;

/// @notice Authorized Yield Vaults that implement ERC4626 standard. These
/// vaults deposit assets to yield strategies, e.g. Uniswap V3
Expand All @@ -33,8 +36,9 @@ contract Dispatcher is Ownable {
event VaultAuthorized(address indexed vault);
event VaultDeauthorized(address indexed vault);

constructor(Acre _acre) Ownable(msg.sender) {
constructor(Acre _acre, IERC20 _tbtc) Ownable(msg.sender) {
acre = _acre;
tbtc = _tbtc;
}

/// @notice Adds a vault to the list of authorized vaults.
Expand All @@ -47,13 +51,15 @@ contract Dispatcher is Ownable {
vaults.push(vault);
vaultsInfo[vault].authorized = true;

acre.approveVaultSharesForDispatcher(vault, type(uint256).max);

emit VaultAuthorized(vault);
}

/// @notice Removes a vault from the list of authorized vaults.
/// @param vault Address of the vault to remove.
function deauthorizeVault(address vault) external onlyOwner {
if (!vaultsInfo[vault].authorized) {
if (!isVaultAuthorized(vault)) {
revert VaultUnauthorized();
}

Expand All @@ -68,26 +74,67 @@ contract Dispatcher is Ownable {
}
}

acre.approveVaultSharesForDispatcher(vault, 0);

emit VaultDeauthorized(vault);
}

function isVaultAuthorized(address vault) public view returns (bool){
return vaultsInfo[vault].authorized;
}

function getVaults() external view returns (address[] memory) {
return vaults;
}

return address(acre);

// TODO: Add access restriction
function depositToVault(
IERC4626 vault,
uint256 amount,
uint256 minSharesOut
) public returns (uint256 sharesOut) {
if (!isVaultAuthorized(address(vault))) {
revert VaultUnauthorized();
}

require(vault.asset() == address(tbtc), "vault asset is not tbtc");

IERC20(tbtc).safeTransferFrom(address(acre), address(this), amount);
IERC20(tbtc).approve(address(vault), amount);

Router.deposit(vault, address(acre), amount, minSharesOut);
}

function sharesHolder() public virtual override returns (address){
return address(this);
// TODO: Add access restriction
function withdrawFromVault(
IERC4626 vault,
uint256 amount,
uint256 maxSharesOut
) public returns (uint256 sharesOut) {
if (!isVaultAuthorized(address(vault))) {
revert VaultUnauthorized();
}

uint256 shares = vault.previewWithdraw(amount);

IERC20(vault).safeTransferFrom(address(acre), address(this), shares);
IERC20(vault).approve(address(vault), shares);

Router.withdraw(vault, address(acre), amount, maxSharesOut);
}

function migrateShares(IERC4626[] calldata _vaults) public onlyOwner {
address newDispatcher = address(acre.dispatcher());
// TODO: Add access restriction
function redeemFromVault(
IERC4626 vault,
uint256 shares,
uint256 minAmountOut
) public returns (uint256 amountOut) {
IERC20(vault).safeTransferFrom(address(acre), address(this), shares);
IERC20(vault).approve(address(vault), shares);

require(newDispatcher != address(0), "new dispatcher address cannot be zero address");
Router.redeem(vault, address(acre), shares, minAmountOut);
}

for (uint i=0; i<_vaults.length; i++) {
_vaults[i].transfer(newDispatcher, _vaults[i].balanceOf(address(this)));
}
// TODO: Add function to withdrawMax
}
39 changes: 21 additions & 18 deletions core/contracts/Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
pragma solidity ^0.8.21;

import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./Acre.sol";


// TODO: Consider deploying ERC4626RouterBase from the ERC4626 Alliance.
// TODO: Think about adding reentrancy guard
// TODO: Add ACL

abstract contract Router {
using SafeERC20 for IERC20;

Expand All @@ -26,41 +24,46 @@ abstract contract Router {
/// @notice thrown when amount of shares received is above the max set by caller
error MaxSharesError();


function assetsHolder() public virtual returns (address);
function sharesHolder() public virtual returns (address);

// Copied from ERC4626RouterBase
// Differences:
// - internal instead of public
function deposit(
IERC4626 vault,
address to,
uint256 amount,
uint256 minSharesOut
) public returns (uint256 sharesOut) {
IERC20(vault.asset()).safeTransferFrom(assetsHolder(), address(this), amount);

IERC20(vault.asset()).approve(address(vault), amount);

if ((sharesOut = vault.deposit(amount, sharesHolder())) < minSharesOut) {
) internal virtual returns (uint256 sharesOut) {
if ((sharesOut = vault.deposit(amount, to)) < minSharesOut) {
revert MinSharesError();
}
}


// Copied from ERC4626RouterBase
// Difference:
// - internal instead of public
// - use address(this) as owner instead of msg.sender
function withdraw(
IERC4626 vault,
address to,
uint256 amount,
uint256 maxSharesOut
) public returns (uint256 sharesOut) {
if ((sharesOut = vault.withdraw(amount, assetsHolder(), sharesHolder())) > maxSharesOut) {
) internal virtual returns (uint256 sharesOut) {
if ((sharesOut = vault.withdraw(amount, to, address(this))) > maxSharesOut) {
revert MaxSharesError();
}
}

// Copied from ERC4626RouterBase
// Difference:
// - internal instead of public
// - use address(this) as owner instead of msg.sender
function redeem(
IERC4626 vault,
address to,
uint256 shares,
uint256 minAmountOut
) public returns (uint256 amountOut) {
if ((amountOut = vault.redeem(shares, assetsHolder(), sharesHolder())) < minAmountOut) {
) internal virtual returns (uint256 amountOut) {
if ((amountOut = vault.redeem(shares, to, address(this))) < minAmountOut) {
revert MinAmountError();
}
}
Expand Down
22 changes: 0 additions & 22 deletions core/deploy/02_deploy_acre_router.ts

This file was deleted.

15 changes: 13 additions & 2 deletions core/deploy/02_deploy_dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,30 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const { deployer } = await getNamedAccounts()

const acre = await deployments.get("Acre")
// const router = await deployments.get("Router")
const tbtc = await deployments.get("TBTC")

await deployments.deploy("Dispatcher", {
from: deployer,
args: [acre.address],
args: [acre.address, tbtc.address],
log: true,
waitConfirmations: 1,
})

const dispatcher = await deployments.get("Dispatcher")

await deployments.execute(
"Acre",
{ from: deployer, log: true, waitConfirmations: 1 },
"upgradeDispatcher",
dispatcher.address,
)

// TODO: Add Etherscan verification
// TODO: Add Tenderly verification
}

export default func

func.tags = ["Dispatcher"]
func.dependencies = ["Acre"]
func.dependencies = ["Acre", "TBTC"]
22 changes: 0 additions & 22 deletions core/deploy/22_transfer_ownership_acre_router.ts

This file was deleted.

1 change: 1 addition & 0 deletions core/deploy/22_transfer_ownership_dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
export default func

func.tags = ["TransferOwnershipDispatcher"]
func.dependencies = ["Dispatcher"]
Loading

0 comments on commit b402c49

Please sign in to comment.