Skip to content

Commit

Permalink
Merge branch 'main' into wait-for-btc-tx
Browse files Browse the repository at this point in the history
  • Loading branch information
r-czajkowski authored Mar 12, 2024
2 parents 3c36df8 + 0ed5b1e commit e53aaec
Show file tree
Hide file tree
Showing 50 changed files with 6,027 additions and 2,340 deletions.
5 changes: 3 additions & 2 deletions core/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
"test/**"
]
}
]
],
"@typescript-eslint/no-use-before-define": "off"
},
"overrides": [
{
"files": ["deploy/*.ts"],
"files": ["deploy/**/*.ts", "test/**/*.ts"],
"rules": {
"@typescript-eslint/unbound-method": "off"
}
Expand Down
679 changes: 679 additions & 0 deletions core/contracts/AcreBitcoinDepositor.sol

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions core/contracts/Dispatcher.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.21;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC4626.sol";
import "./Router.sol";
Expand All @@ -11,7 +11,7 @@ import "./stBTC.sol";
/// @notice Dispatcher is a contract that routes tBTC from stBTC to
/// yield vaults and back. Vaults supply yield strategies with tBTC that
/// generate yield for Bitcoin holders.
contract Dispatcher is Router, Ownable {
contract Dispatcher is Router, Ownable2Step {
using SafeERC20 for IERC20;

/// Struct holds information about a vault.
Expand Down
13 changes: 0 additions & 13 deletions core/contracts/TbtcDepositor.sol

This file was deleted.

11 changes: 8 additions & 3 deletions core/contracts/stBTC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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 "@openzeppelin/contracts/access/Ownable2Step.sol";
import "./Dispatcher.sol";

/// @title stBTC
Expand All @@ -17,7 +17,7 @@ import "./Dispatcher.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 stBTC is ERC4626, Ownable {
contract stBTC is ERC4626, Ownable2Step {
using SafeERC20 for IERC20;

/// Dispatcher contract that routes tBTC from stBTC to a given vault and back.
Expand All @@ -26,8 +26,13 @@ contract stBTC is ERC4626, Ownable {
/// Address of the treasury wallet, where fees should be transferred to.
address public treasury;

/// Minimum amount for a single deposit operation.
/// Minimum amount for a single deposit operation. The value should be set
/// low enough so the deposits routed through Bitcoin Depositor contract won't
/// be rejected. It means that minimumDepositAmount should be lower than
/// tBTC protocol's depositDustThreshold reduced by all the minting fees taken
/// before depositing in the Acre contract.
uint256 public minimumDepositAmount;

/// Maximum total amount of tBTC token held by Acre protocol.
uint256 public maximumTotalAssets;

Expand Down
83 changes: 83 additions & 0 deletions core/contracts/test/AcreBitcoinDepositorHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-3.0-only
/* solhint-disable func-name-mixedcase */
pragma solidity ^0.8.21;

import {AcreBitcoinDepositor} from "../AcreBitcoinDepositor.sol";
import {MockBridge, MockTBTCVault} from "@keep-network/tbtc-v2/contracts/test/TestTBTCDepositor.sol";
import {IBridge} from "@keep-network/tbtc-v2/contracts/integrator/IBridge.sol";
import {IBridgeTypes} from "@keep-network/tbtc-v2/contracts/integrator/IBridge.sol";

import {TestERC20} from "./TestERC20.sol";

/// @dev A test contract to expose internal function from AcreBitcoinDepositor contract.
/// This solution follows Foundry recommendation:
/// https://book.getfoundry.sh/tutorials/best-practices#internal-functions
contract AcreBitcoinDepositorHarness is AcreBitcoinDepositor {
constructor(
address bridge,
address tbtcVault,
address tbtcToken,
address stbtc
) AcreBitcoinDepositor(bridge, tbtcVault, tbtcToken, stbtc) {}

function exposed_finalizeBridging(
uint256 depositKey
) external returns (uint256 amountToStake, address staker) {
return finalizeBridging(depositKey);
}

function exposed_setQueuedStakesBalance(uint256 amount) external {
queuedStakesBalance = amount;
}
}

/// @dev A test contract to stub tBTC Bridge contract.
contract BridgeStub is MockBridge {}

/// @dev A test contract to stub tBTC Vault contract.
contract TBTCVaultStub is MockTBTCVault {
TestERC20 public immutable tbtc;
IBridge public immutable bridge;

/// @notice Multiplier to convert satoshi to TBTC token units.
uint256 public constant SATOSHI_MULTIPLIER = 10 ** 10;

constructor(TestERC20 _tbtc, IBridge _bridge) {
tbtc = _tbtc;
bridge = _bridge;
}

function finalizeOptimisticMintingRequest(
uint256 depositKey
) public override {
IBridgeTypes.DepositRequest memory deposit = bridge.deposits(
depositKey
);

uint256 amountSubTreasury = (deposit.amount - deposit.treasuryFee) *
SATOSHI_MULTIPLIER;

uint256 omFee = optimisticMintingFeeDivisor > 0
? (amountSubTreasury / optimisticMintingFeeDivisor)
: 0;

// The deposit transaction max fee is in the 1e8 satoshi precision.
// We need to convert them to the 1e18 TBTC precision.
// slither-disable-next-line unused-return
(, , uint64 depositTxMaxFee, ) = bridge.depositParameters();
uint256 txMaxFee = depositTxMaxFee * SATOSHI_MULTIPLIER;

uint256 amountToMint = amountSubTreasury - omFee - txMaxFee;

finalizeOptimisticMintingRequestWithAmount(depositKey, amountToMint);
}

function finalizeOptimisticMintingRequestWithAmount(
uint256 depositKey,
uint256 amountToMint
) public {
MockTBTCVault.finalizeOptimisticMintingRequest(depositKey);

tbtc.mint(bridge.deposits(depositKey).depositor, amountToMint);
}
}
2 changes: 1 addition & 1 deletion core/contracts/test/TestERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.21;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TestERC20 is ERC20 {
constructor() ERC20("Test Token", "TEST") {}
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

function mint(address account, uint256 value) external {
_mint(account, value);
Expand Down
35 changes: 35 additions & 0 deletions core/deploy/00_resolve_tbtc_bridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { DeployFunction } from "hardhat-deploy/types"
import type {
HardhatNetworkConfig,
HardhatRuntimeEnvironment,
} from "hardhat/types"
import { isNonZeroAddress } from "../helpers/address"
import { waitConfirmationsNumber } from "../helpers/deployment"

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

const bridge = await deployments.getOrNull("Bridge")

if (bridge && isNonZeroAddress(bridge.address)) {
log(`using Bridge contract at ${bridge.address}`)
} else if ((hre.network.config as HardhatNetworkConfig)?.forking?.enabled) {
throw new Error("deployed Bridge contract not found")
} else {
log("deploying Bridge contract stub")

await deployments.deploy("Bridge", {
contract: "BridgeStub",
args: [],
from: deployer,
log: true,
waitConfirmations: waitConfirmationsNumber(hre),
})
}
}

export default func

func.tags = ["TBTC", "Bridge"]
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction } from "hardhat-deploy/types"
import type {
HardhatNetworkConfig,
HardhatRuntimeEnvironment,
} from "hardhat/types"
import { isNonZeroAddress } from "../helpers/address"
import { waitConfirmationsNumber } from "../helpers/deployment"

const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const { getNamedAccounts, deployments } = hre
Expand All @@ -11,20 +15,24 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {

if (tbtc && isNonZeroAddress(tbtc.address)) {
log(`using TBTC contract at ${tbtc.address}`)
} else if (!hre.network.tags.allowStubs) {
} else if (
!hre.network.tags.allowStubs ||
(hre.network.config as HardhatNetworkConfig)?.forking?.enabled
) {
throw new Error("deployed TBTC contract not found")
} else {
log("deploying TBTC contract stub")

await deployments.deploy("TBTC", {
contract: "TestERC20",
args: ["Test tBTC", "TestTBTC"],
from: deployer,
log: true,
waitConfirmations: 1,
waitConfirmations: waitConfirmationsNumber(hre),
})
}
}

export default func

func.tags = ["TBTC"]
func.tags = ["TBTC", "TBTCToken"]
42 changes: 42 additions & 0 deletions core/deploy/00_resolve_tbtc_vault.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { DeployFunction } from "hardhat-deploy/types"
import type {
HardhatNetworkConfig,
HardhatRuntimeEnvironment,
} from "hardhat/types"
import { isNonZeroAddress } from "../helpers/address"
import { waitConfirmationsNumber } from "../helpers/deployment"

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

const tbtcVault = await deployments.getOrNull("TBTCVault")

if (tbtcVault && isNonZeroAddress(tbtcVault.address)) {
log(`using TBTCVault contract at ${tbtcVault.address}`)
} else if (
!hre.network.tags.allowStubs ||
(hre.network.config as HardhatNetworkConfig)?.forking?.enabled
) {
throw new Error("deployed TBTCVault contract not found")
} else {
log("deploying TBTCVault contract stub")

const tbtc = await deployments.get("TBTC")
const bridge = await deployments.get("Bridge")

await deployments.deploy("TBTCVault", {
contract: "TBTCVaultStub",
args: [tbtc.address, bridge.address],
from: deployer,
log: true,
waitConfirmations: waitConfirmationsNumber(hre),
})
}
}

export default func

func.tags = ["TBTC", "TBTCVault"]
func.dependencies = ["TBTCToken", "Bridge"]
4 changes: 2 additions & 2 deletions core/deploy/02_deploy_dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const tbtc = await deployments.get("TBTC")
const stbtc = await deployments.get("stBTC")

await deployments.deploy("Dispatcher", {
const dispatcher = await deployments.deploy("Dispatcher", {
from: deployer,
args: [stbtc.address, tbtc.address],
log: true,
waitConfirmations: waitConfirmationsNumber(hre),
})

if (hre.network.tags.etherscan) {
await helpers.etherscan.verify(stbtc)
await helpers.etherscan.verify(dispatcher)
}

// TODO: Add Tenderly verification
Expand Down
35 changes: 35 additions & 0 deletions core/deploy/03_deploy_acre_bitcoin_depositor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { DeployFunction } from "hardhat-deploy/types"
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import { waitConfirmationsNumber } from "../helpers/deployment"

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

const bridge = await deployments.get("Bridge")
const tbtcVault = await deployments.get("TBTCVault")
const tbtc = await deployments.get("TBTC")
const stbtc = await deployments.get("stBTC")

const depositor = await deployments.deploy("AcreBitcoinDepositor", {
contract:
process.env.HARDHAT_TEST === "true"
? "AcreBitcoinDepositorHarness"
: "AcreBitcoinDepositor",
from: deployer,
args: [bridge.address, tbtcVault.address, tbtc.address, stbtc.address],
log: true,
waitConfirmations: waitConfirmationsNumber(hre),
})

if (hre.network.tags.etherscan) {
await helpers.etherscan.verify(depositor)
}

// TODO: Add Tenderly verification
}

export default func

func.tags = ["AcreBitcoinDepositor"]
func.dependencies = ["TBTC", "stBTC"]
30 changes: 30 additions & 0 deletions core/deploy/13_stbtc_update_deposit_parameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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 minimumDepositAmount = 10000000000000 // 0.00001 tBTC
const maximumTotalAssets: bigint = (await deployments.read(
"stBTC",
"maximumTotalAssets",
)) as bigint

await deployments.execute(
"stBTC",
{ from: deployer, log: true, waitConfirmations: 1 },
"updateDepositParameters",
minimumDepositAmount,
maximumTotalAssets,
)
}

export default func

func.tags = ["stBTCUpdateDepositParameters"]
func.dependencies = ["stBTC"]

// Run only on Sepolia testnet.
func.skip = async (hre: HardhatRuntimeEnvironment): Promise<boolean> =>
Promise.resolve(hre.network.name !== "sepolia")
9 changes: 9 additions & 0 deletions core/deploy/21_transfer_ownership_stbtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,17 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
"transferOwnership",
governance,
)

if (hre.network.name !== "mainnet") {
await deployments.execute(
"stBTC",
{ from: governance, log: true, waitConfirmations: 1 },
"acceptOwnership",
)
}
}

export default func

func.tags = ["TransferOwnershipStBTC"]
func.dependencies = ["stBTC"]
Loading

0 comments on commit e53aaec

Please sign in to comment.