Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deposit to ERC4626 Vaults #71

Merged
merged 36 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
dfec9bf
Adding deposit and redeem functionality
dimpar Dec 8, 2023
54a1562
Resolved conflicts with the base branched
dimpar Dec 12, 2023
1166e5e
Drafting integration flows for assets
dimpar Dec 12, 2023
f60a634
Merge remote-tracking branch 'origin' into deposit-redeem-to-vaults
dimpar Dec 13, 2023
1df0676
Allocate tBTC to Yield Modules
dimpar Dec 27, 2023
e6c6222
Adding maintainer modifier and smaller refactorings
dimpar Dec 28, 2023
3cb3bdf
Adding maintainer as a named account
dimpar Dec 28, 2023
1101708
Updating deployment scripts
dimpar Dec 28, 2023
6ec7df5
Updating unit tests and adding some integration tests for assets allo…
dimpar Dec 28, 2023
3837758
Merge remote-tracking branch 'origin' into deposit-redeem-to-vaults
dimpar Dec 28, 2023
fcac792
Resetting approval for the old dispatcher
dimpar Dec 29, 2023
5313aa6
Skipping deployment of TestERC4626 Vault for Mainnet
dimpar Dec 29, 2023
e7f48cb
Renames and smaller refactorings
dimpar Dec 29, 2023
899bb70
Merge remote-tracking branch 'origin' into deposit-redeem-to-vaults
dimpar Dec 29, 2023
10b0806
Slithering
dimpar Dec 29, 2023
ea667ed
Adding TODO to revoke share tokens approval from the old dispatcher …
dimpar Dec 29, 2023
12b9d5f
Simplifying tBTC token approvals by using OZ forceApprove
dimpar Dec 29, 2023
ed7297b
Merge remote-tracking branch 'origin' into deposit-redeem-to-vaults
dimpar Dec 29, 2023
6d0d574
Refactoring skip flag for Mainnet
dimpar Dec 29, 2023
c0d9147
Adding event when updating Dispatcher plus smaller comments
dimpar Dec 29, 2023
c73202e
Updating dispatcher in 'before' instead of 'it'
dimpar Dec 29, 2023
39ff3d7
Refactorings in Dispatcher
dimpar Dec 29, 2023
659556b
Extracting function calls under tests to before clause
dimpar Dec 29, 2023
147f9cc
Adding tests for deployment scripts around dispatcher and maintainer
dimpar Jan 2, 2024
241a504
Extracting common code to before clause
dimpar Jan 2, 2024
591447a
Moving depositToVault tests under Dipatcher.test.ts + smaller cleanups
dimpar Jan 2, 2024
c18d23b
Reorder of the functions in Dispatcher.sol
dimpar Jan 2, 2024
9e6c630
Adding withArgs checks for CustomErrors validations
dimpar Jan 2, 2024
19e7890
Adding NatSpec comments
dimpar Jan 3, 2024
ce9e973
Smaller docs improvements
dimpar Jan 3, 2024
a59d6f4
Removing allowStubs tag check. Not needed.
dimpar Jan 3, 2024
4bd6ca9
Function reorder. External goes above public
dimpar Jan 3, 2024
73ccd1c
getVaults() making public instead of external
dimpar Jan 3, 2024
2ef73ab
Smaller cleanups, renames and adding an additional test
dimpar Jan 3, 2024
324d71e
Merge remote-tracking branch 'origin' into deposit-redeem-to-vaults
dimpar Jan 3, 2024
023f1d0
Cleanups across Solidity contracts and tests
dimpar Jan 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions core/contracts/Acre.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
pragma solidity ^0.8.21;

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

/// @title Acre
/// @notice This contract implements the ERC-4626 tokenized vault standard. By
Expand All @@ -14,12 +17,20 @@ import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
/// of yield-bearing vaults. This contract facilitates the minting and
/// burning of shares (stBTC), which are represented as standard ERC20
/// tokens, providing a seamless exchange with tBTC tokens.
contract Acre is ERC4626 {
contract Acre is ERC4626, Ownable {
using SafeERC20 for IERC20;

Dispatcher public dispatcher;
nkuba marked this conversation as resolved.
Show resolved Hide resolved

event StakeReferral(bytes32 indexed referral, uint256 assets);

error ZeroAddress();
error DispatcherNotSet();
error ApproveFailed();

constructor(
IERC20 tbtc
) ERC4626(tbtc) ERC20("Acre Staked Bitcoin", "stBTC") {}
) ERC4626(tbtc) ERC20("Acre Staked Bitcoin", "stBTC") Ownable(msg.sender) {}

/// @notice Stakes a given amount of tBTC token and mints shares to a
/// receiver.
Expand All @@ -43,4 +54,33 @@ contract Acre is ERC4626 {

return shares;
}

/// @notice Updates the dispatcher contract and gives it an unlimited
/// allowance to transfer staked tBTC.
/// @param _dispatcher Address of the new dispatcher contract.
function updateDispatcher(Dispatcher _dispatcher) external onlyOwner {
nkuba marked this conversation as resolved.
Show resolved Hide resolved
if (address(_dispatcher) == address(0)) {
nkuba marked this conversation as resolved.
Show resolved Hide resolved
revert ZeroAddress();
}

address oldDispatcher = address(dispatcher);
dispatcher = _dispatcher;
nkuba marked this conversation as resolved.
Show resolved Hide resolved

if (oldDispatcher != address(0)) {
// Setting allowance to zero for the old dispatcher
bool approvedToZero = IERC20(asset()).approve(oldDispatcher, 0);
if (!approvedToZero) {
revert ApproveFailed();
}
nkuba marked this conversation as resolved.
Show resolved Hide resolved
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
}

// Setting allowance to max for the new dispatcher
bool approvedToMax = IERC20(asset()).approve(
address(dispatcher),
type(uint256).max
);
if (!approvedToMax) {
revert ApproveFailed();
}
nkuba marked this conversation as resolved.
Show resolved Hide resolved
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
}
}
78 changes: 73 additions & 5 deletions core/contracts/Dispatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@
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";
import "./Acre.sol";

/// @title Dispatcher
/// @notice Dispatcher is a contract that routes TBTC from stBTC (Acre) to
/// @notice Dispatcher is a contract that routes tBTC from Acre (stBTC) to
/// a given vault and back. Vaults supply yield strategies with TBTC that
nkuba marked this conversation as resolved.
Show resolved Hide resolved
/// generate yield for Bitcoin holders.
contract Dispatcher is Ownable {
error VaultAlreadyAuthorized();
error VaultUnauthorized();
contract Dispatcher is Router, Ownable {
using SafeERC20 for IERC20;

struct VaultInfo {
bool authorized;
}

Acre public immutable acre; // Depositing tBTC into Acre returns stBTC.
IERC20 public immutable tBTC;
nkuba marked this conversation as resolved.
Show resolved Hide resolved

address public maintainer;

/// @notice Authorized Yield Vaults 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 or can be
Expand All @@ -26,8 +34,29 @@ contract Dispatcher is Ownable {

event VaultAuthorized(address indexed vault);
event VaultDeauthorized(address indexed vault);
event DepositAllocated(
address indexed vault,
uint256 amount,
uint256 minSharesOut
nkuba marked this conversation as resolved.
Show resolved Hide resolved
);
event MaintainerUpdated(address indexed maintainer);

error VaultAlreadyAuthorized();
error VaultUnauthorized();
error CallerUnauthorized(string reason);
nkuba marked this conversation as resolved.
Show resolved Hide resolved
error ZeroAddress();

modifier onlyMaintainer() {
if (msg.sender != maintainer) {
revert CallerUnauthorized("Maintainer only");
}
_;
}

constructor() 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.
/// @param vault Address of the vault to add.
Expand Down Expand Up @@ -63,7 +92,46 @@ contract Dispatcher is Ownable {
emit VaultDeauthorized(vault);
}

/// TODO: make this function internal once the allocation distribution is
/// implemented
/// @notice Routes tBTC from Acre to a vault. Can be called by the maintainer
/// only.
/// @param vault Address of the vault to route the assets to.
/// @param amount Amount of tBTC to deposit.
/// @param minSharesOut Minimum amount of shares to receive by Acre.
function depositToVault(
address vault,
uint256 amount,
uint256 minSharesOut
) public onlyMaintainer {
nkuba marked this conversation as resolved.
Show resolved Hide resolved
if (!vaultsInfo[vault].authorized) {
nkuba marked this conversation as resolved.
Show resolved Hide resolved
revert VaultUnauthorized();
}
emit DepositAllocated(vault, amount, minSharesOut);
nkuba marked this conversation as resolved.
Show resolved Hide resolved

// slither-disable-next-line arbitrary-send-erc20
tBTC.safeTransferFrom(address(acre), address(this), amount);
tBTC.safeIncreaseAllowance(address(vault), amount);
nkuba marked this conversation as resolved.
Show resolved Hide resolved

deposit(IERC4626(vault), address(acre), amount, minSharesOut);
}

/// @notice Updates the maintainer address.
/// @param _maintainer Address of the new maintainer.
nkuba marked this conversation as resolved.
Show resolved Hide resolved
function updateMaintainer(address _maintainer) external onlyOwner {
nkuba marked this conversation as resolved.
Show resolved Hide resolved
if (_maintainer == address(0)) {
revert ZeroAddress();
}

maintainer = _maintainer;

emit MaintainerUpdated(maintainer);
}

/// @notice Returns the list of authorized vaults.
function getVaults() external view returns (address[] memory) {
return vaults;
}

/// TODO: implement redeem() / withdraw() functions
}
34 changes: 34 additions & 0 deletions core/contracts/Router.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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";
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
import "@openzeppelin/contracts/interfaces/IERC4626.sol";

/// @title Router
/// @notice Router 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.
abstract contract Router {
using SafeERC20 for IERC20;
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved

/// @notice thrown when amount of shares received is below the min set by caller
error MinSharesError();
nkuba marked this conversation as resolved.
Show resolved Hide resolved

/// @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 receiver 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 receiver,
uint256 amount,
uint256 minSharesOut
) internal returns (uint256 sharesOut) {
if ((sharesOut = vault.deposit(amount, receiver)) < minSharesOut) {
revert MinSharesError();
}
}
}
12 changes: 12 additions & 0 deletions core/contracts/test/TestERC4626.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.21;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";

contract TestERC4626 is ERC4626 {
constructor(
IERC20 asset,
string memory tokenName,
string memory tokenSymbol
) ERC4626(asset) ERC20(tokenName, tokenSymbol) {}
}
31 changes: 31 additions & 0 deletions core/deploy/00_resolve_testing_erc4626.ts
nkuba marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction } from "hardhat-deploy/types"

const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const { getNamedAccounts, deployments } = hre
const { log } = deployments
const { deployer } = await getNamedAccounts()

const tBTC = await deployments.get("TBTC")

if (hre.network.tags.allowStubs) {
nkuba marked this conversation as resolved.
Show resolved Hide resolved
log("deploying Mock ERC4626 Vault")

await deployments.deploy("Vault", {
contract: "TestERC4626",
from: deployer,
args: [tBTC.address, "MockVault", "MV"],
log: true,
waitConfirmations: 1,
})
}
}

export default func

func.tags = ["TestERC4626"]
func.dependencies = ["TBTC"]

if (hre.network.name === "mainnet") {
func.skip = async () => true
}
nkuba marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const { getNamedAccounts, deployments } = hre
const { deployer } = await getNamedAccounts()

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

await deployments.deploy("Dispatcher", {
from: deployer,
args: [],
args: [acre.address, tbtc.address],
log: true,
waitConfirmations: 1,
})
Expand Down
21 changes: 21 additions & 0 deletions core/deploy/11_acre_update_dispatcher.ts
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction } from "hardhat-deploy/types"

const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const { getNamedAccounts, deployments } = hre
const { deployer } = await getNamedAccounts()

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

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

export default func

func.tags = ["AcreUpdateDispatcher"]
func.dependencies = ["Acre", "Dispatcher"]
19 changes: 19 additions & 0 deletions core/deploy/12_dispatcher_update_maintainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction } from "hardhat-deploy/types"

const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const { getNamedAccounts, deployments } = hre
const { deployer, maintainer } = await getNamedAccounts()

await deployments.execute(
"Dispatcher",
{ from: deployer, log: true, waitConfirmations: 1 },
"updateMaintainer",
maintainer,
)
}

export default func

func.tags = ["DispatcherUpdateMaintainer"]
func.dependencies = ["Dispatcher"]
2 changes: 0 additions & 2 deletions core/deploy/21_transfer_ownership_acre.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,3 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
export default func

func.tags = ["TransferOwnershipAcre"]
// TODO: Enable once Acre extends Ownable
func.skip = async () => true
9 changes: 7 additions & 2 deletions core/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,13 @@ const config: HardhatUserConfig = {
},
governance: {
default: 2,
sepolia: 0,
mainnet: "",
sepolia: 0, // TODO: updated to the actual address once available
mainnet: "", // TODO: updated to the actual address once available
},
maintainer: {
default: 3,
sepolia: 0, // TODO: updated to the actual address once available
mainnet: "", // TODO: updated to the actual address once available
},
},

Expand Down
Loading
Loading