From a2c5017ac7a5bd0d36dfe119d1e165acf9083443 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 20 Mar 2024 22:02:12 +0100 Subject: [PATCH 01/75] Drafting Mezo Allocator Mezo Allocator will route tBTC tokens between Acre x Mezo. Mezo Allocator is the first pluggable contract to Acre system that will handle staking tBTC on various L2s/DeFi. Withdrawal part will be added in other commits. --- core/contracts/MezoAllocator.sol | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 core/contracts/MezoAllocator.sol diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol new file mode 100644 index 000000000..f96248b08 --- /dev/null +++ b/core/contracts/MezoAllocator.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/access/Ownable2Step.sol"; + +interface IMezoPortal { + function deposit(address token, uint96 amount, uint32 lockPeriod) external; + + function withdraw(address token, uint256 depositId, uint96 amount) external; + + function depositCount() external view returns (uint256); +} + +/// @notice MezoAllocator is a contract that routes tBTC to/from MezoPortal. +contract MezoAllocator is Ownable2Step { + using SafeERC20 for IERC20; + + address public mezoPortal; + /// tBTC token contract. + IERC20 public immutable tbtc; + /// Contract holding tBTC deposited by stakers. + address public tbtcStorage; + + // Deposit ID -> Deposit Amount + mapping(uint256 => uint256) public depositsById; + // Deposit IDs + uint256[] public deposits; + + event DepositAllocated(uint256 depositId, uint256 amount); + + constructor(address _mezoPortal, IERC20 _tbtc) Ownable(msg.sender) { + mezoPortal = _mezoPortal; + tbtc = _tbtc; + } + + // TODO: replace onlyOwner with onlyMaintaier or onlyOwnerAndMaintainer. + /// @notice Deposits tBTC to MezoPortal. + /// @dev This function will be called by the bot at some interval. + function deposit(uint96 amount) external onlyOwner { + IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); + // 0 means no lock. + IMezoPortal(mezoPortal).deposit(address(tbtc), amount, 0); + // MezoPortal doesn't return depositId, so we have to read depositCounter + // which assignes depositId to the current deposit. + uint256 depositId = IMezoPortal(mezoPortal).depositCount(); + depositsById[depositId] = amount; + deposits.push(depositId); + + emit DepositAllocated(depositId, amount); + } + + /// @notice Updates the tBTC storage address. + /// @dev At first this is going to be the stBTC contract. Once Acre + /// works with more destinations for tBTC, this will be updated to + /// the new storage contract like AcreDispatcher. + /// @param _tbtcStorage Address of the new tBTC storage. + function updateTbtcStorage(address _tbtcStorage) external onlyOwner { + tbtcStorage = _tbtcStorage; + } + + // TODO: add updatable withdrawer and onlyWithdrawer modifier (stBTC or AcreDispatcher). + /// @notice Withdraws tBTC from MezoPortal and transfers it to stBTC. + function withdraw(uint96 amount) external { + // TODO: Take the latest deposit and pull funds from it. + // If not enough funds, take everything from that deposit and + // take the rest from the next deposit id until the amount is + // reached. + // IMezoPortal(mezoPortal).withdraw(address(tbtc), depositId, amount); + // TODO: update depositsById and deposits data structures. + // IERC20(tbtc).safeTransfer(address(tbtcStorage), amount); + } +} From 7580b95e0f0461e7c7b163b12160df34b55737bb Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 21 Mar 2024 10:15:55 +0100 Subject: [PATCH 02/75] Adding checks, events and docs to MezoAllocator contract --- core/contracts/MezoAllocator.sol | 64 +++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index f96248b08..914520fc9 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -13,41 +13,77 @@ interface IMezoPortal { function depositCount() external view returns (uint256); } -/// @notice MezoAllocator is a contract that routes tBTC to/from MezoPortal. +/// @notice MezoAllocator routes tBTC to/from MezoPortal. contract MezoAllocator is Ownable2Step { using SafeERC20 for IERC20; - address public mezoPortal; + /// Address of the MezoPortal contract. + address public immutable mezoPortal; /// tBTC token contract. IERC20 public immutable tbtc; /// Contract holding tBTC deposited by stakers. address public tbtcStorage; + /// @notice Maintainer address which can trigger deposit flow. + address public maintainer; + // Deposit ID -> Deposit Amount mapping(uint256 => uint256) public depositsById; // Deposit IDs uint256[] public deposits; + /// Emitted when tBTC is deposited to MezoPortal. event DepositAllocated(uint256 depositId, uint256 amount); + /// @notice Emitted when the tBTC storage address is updated. + event TbtcStorageUpdated(address indexed tbtcStorage); + + /// @notice Emitted when the maintainer address is updated. + event MaintainerUpdated(address indexed maintainer); + + /// @notice Reverts if the caller is not an authorized account. + error NotAuthorized(); + + /// @notice Reverts if the address is 0. + error ZeroAddress(); + + modifier onlyMaintainerAndOwner() { + if (msg.sender != maintainer && owner() != msg.sender) { + revert NotAuthorized(); + } + _; + } + + /// @notice Initializes the MezoAllocator contract. + /// @param _mezoPortal Address of the MezoPortal contract. + /// @param _tbtc Address of the tBTC token contract. constructor(address _mezoPortal, IERC20 _tbtc) Ownable(msg.sender) { + if (_mezoPortal == address(0)) { + revert ZeroAddress(); + } + if (address(_tbtc) == address(0)) { + revert ZeroAddress(); + } mezoPortal = _mezoPortal; tbtc = _tbtc; } - // TODO: replace onlyOwner with onlyMaintaier or onlyOwnerAndMaintainer. /// @notice Deposits tBTC to MezoPortal. - /// @dev This function will be called by the bot at some interval. - function deposit(uint96 amount) external onlyOwner { + /// @dev This function can be invoked periodically by a bot. + /// @param amount Amount of tBTC to deposit to Mezo Portal. + function deposit(uint96 amount) external onlyMaintainerAndOwner { + // slither-disable-next-line arbitrary-send-erc20 IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); - // 0 means no lock. + // 0 denotes no lock period for this deposit. IMezoPortal(mezoPortal).deposit(address(tbtc), amount, 0); // MezoPortal doesn't return depositId, so we have to read depositCounter // which assignes depositId to the current deposit. uint256 depositId = IMezoPortal(mezoPortal).depositCount(); + // slither-disable-next-line reentrancy-benign depositsById[depositId] = amount; deposits.push(depositId); + // slither-disable-next-line reentrancy-events emit DepositAllocated(depositId, amount); } @@ -57,7 +93,23 @@ contract MezoAllocator is Ownable2Step { /// the new storage contract like AcreDispatcher. /// @param _tbtcStorage Address of the new tBTC storage. function updateTbtcStorage(address _tbtcStorage) external onlyOwner { + if (_tbtcStorage == address(0)) { + revert ZeroAddress(); + } tbtcStorage = _tbtcStorage; + + emit TbtcStorageUpdated(_tbtcStorage); + } + + /// @notice Updates the maintainer address. + /// @param _maintainer Address of the new maintainer. + function updateMaintainer(address _maintainer) external onlyOwner { + if (_maintainer == address(0)) { + revert ZeroAddress(); + } + maintainer = _maintainer; + + emit MaintainerUpdated(_maintainer); } // TODO: add updatable withdrawer and onlyWithdrawer modifier (stBTC or AcreDispatcher). From 61b654b20970acf1cbf2447935704f56926e7f12 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 21 Mar 2024 11:18:11 +0100 Subject: [PATCH 03/75] Adding deployment scripts for Mezo Allocator - Added mainnet and sepolia addresses in Jsons for Mezo Portal - Added deployment script for Mezo Allocator contract. A fake random address is used for Mezo Portal when deploying on hardhat network. This is for testing purposes only. --- core/deploy/00_resolve_mezo_portal.ts | 23 +++++++++++++++ core/deploy/02_deploy_mezo_allocator.ts | 38 +++++++++++++++++++++++++ core/external/mainnet/MezoPortal.json | 3 ++ core/external/sepolia/MezoPortal.json | 3 ++ 4 files changed, 67 insertions(+) create mode 100644 core/deploy/00_resolve_mezo_portal.ts create mode 100644 core/deploy/02_deploy_mezo_allocator.ts create mode 100644 core/external/mainnet/MezoPortal.json create mode 100644 core/external/sepolia/MezoPortal.json diff --git a/core/deploy/00_resolve_mezo_portal.ts b/core/deploy/00_resolve_mezo_portal.ts new file mode 100644 index 000000000..02cf262b2 --- /dev/null +++ b/core/deploy/00_resolve_mezo_portal.ts @@ -0,0 +1,23 @@ +import type { DeployFunction } from "hardhat-deploy/types" +import type { + HardhatNetworkConfig, + HardhatRuntimeEnvironment, +} from "hardhat/types" +import { isNonZeroAddress } from "../helpers/address" + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { deployments } = hre + const { log } = deployments + + const mezoPortal = await deployments.getOrNull("MezoPortal") + + if (mezoPortal && isNonZeroAddress(mezoPortal.address)) { + log(`using MezoPortal contract at ${mezoPortal.address}`) + } else if ((hre.network.config as HardhatNetworkConfig)?.forking?.enabled) { + throw new Error("deployed MezoPortal contract not found") + } +} + +export default func + +func.tags = ["MezoPortal"] diff --git a/core/deploy/02_deploy_mezo_allocator.ts b/core/deploy/02_deploy_mezo_allocator.ts new file mode 100644 index 000000000..c07250005 --- /dev/null +++ b/core/deploy/02_deploy_mezo_allocator.ts @@ -0,0 +1,38 @@ +import type { HardhatRuntimeEnvironment } from "hardhat/types" +import type { DeployFunction } from "hardhat-deploy/types" +import { waitConfirmationsNumber } from "../helpers/deployment" + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { getNamedAccounts, deployments, helpers } = hre + const { deployer } = await getNamedAccounts() + const { log } = deployments + + const tbtc = await deployments.get("TBTC") + // Fake random address for local development purposes only. + const fakeMezoPortal = "0x0af5DC16568EFF2d480a43A77E6C409e497FcFb9" + const mezoPortal = await deployments.getOrNull("MezoPortal") + + let mezoPortalAddress = mezoPortal?.address + if (!mezoPortalAddress && hre.network.name === "hardhat") { + mezoPortalAddress = fakeMezoPortal + log(`using fake Mezo Portal address ${mezoPortalAddress}`) + } + + const mezoAllocator = await deployments.deploy("MezoAllocator", { + from: deployer, + args: [mezoPortalAddress, tbtc.address], + log: true, + waitConfirmations: waitConfirmationsNumber(hre), + }) + + if (hre.network.tags.etherscan) { + await helpers.etherscan.verify(mezoAllocator) + } + + // TODO: Add Tenderly verification +} + +export default func + +func.tags = ["MezoAllocator"] +func.dependencies = ["TBTC"] diff --git a/core/external/mainnet/MezoPortal.json b/core/external/mainnet/MezoPortal.json new file mode 100644 index 000000000..858fc0b32 --- /dev/null +++ b/core/external/mainnet/MezoPortal.json @@ -0,0 +1,3 @@ +{ + "address": "0xAB13B8eecf5AA2460841d75da5d5D861fD5B8A39" +} \ No newline at end of file diff --git a/core/external/sepolia/MezoPortal.json b/core/external/sepolia/MezoPortal.json new file mode 100644 index 000000000..7c6c1309f --- /dev/null +++ b/core/external/sepolia/MezoPortal.json @@ -0,0 +1,3 @@ +{ + "address": "0x6978E3e11b8Bc34ea836C1706fC742aC4Cb6b0Db" +} \ No newline at end of file From 6dc9c8fa90d251d8072d25121ba34edb2814c663 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 21 Mar 2024 16:32:16 +0100 Subject: [PATCH 04/75] Adding a stub for MezoPortal for testing purposes --- core/contracts/MezoAllocator.sol | 6 ++++++ core/contracts/stBTC.sol | 3 ++- core/contracts/test/MezoPortalStub.sol | 22 ++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 core/contracts/test/MezoPortalStub.sol diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 914520fc9..294aaa3ca 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -74,6 +74,7 @@ contract MezoAllocator is Ownable2Step { function deposit(uint96 amount) external onlyMaintainerAndOwner { // slither-disable-next-line arbitrary-send-erc20 IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); + IERC20(tbtc).forceApprove(mezoPortal, amount); // 0 denotes no lock period for this deposit. IMezoPortal(mezoPortal).deposit(address(tbtc), amount, 0); // MezoPortal doesn't return depositId, so we have to read depositCounter @@ -112,6 +113,11 @@ contract MezoAllocator is Ownable2Step { emit MaintainerUpdated(_maintainer); } + /// @notice Returns the deposit IDs. + function getDeposits() external view returns (uint256[] memory) { + return deposits; + } + // TODO: add updatable withdrawer and onlyWithdrawer modifier (stBTC or AcreDispatcher). /// @notice Withdraws tBTC from MezoPortal and transfers it to stBTC. function withdraw(uint96 amount) external { diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index c287b322b..1fabf3b16 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -21,7 +21,8 @@ import "./lib/ERC4626Fees.sol"; contract stBTC is ERC4626Fees, Ownable2StepUpgradeable { using SafeERC20 for IERC20; - /// Dispatcher contract that routes tBTC from stBTC to a given vault and back. + /// Dispatcher contract that routes tBTC from stBTC to a given destination + /// and back. Dispatcher public dispatcher; /// Address of the treasury wallet, where fees should be transferred to. diff --git a/core/contracts/test/MezoPortalStub.sol b/core/contracts/test/MezoPortalStub.sol new file mode 100644 index 000000000..496f05394 --- /dev/null +++ b/core/contracts/test/MezoPortalStub.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract MezoPortalStub { + using SafeERC20 for IERC20; + + uint256 public depositCount; + + function withdraw( + address token, + uint256 depositId, + uint96 amount + ) external {} + + function deposit(address token, uint96 amount, uint32 lockPeriod) external { + depositCount++; + IERC20(token).safeTransferFrom(msg.sender, address(this), amount); + } +} From 79aff1a7a8306c170507e61442c52068a1c8e182 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 21 Mar 2024 16:32:48 +0100 Subject: [PATCH 05/75] Adding deployment scripts --- core/deploy/00_resolve_mezo_portal.ts | 14 ++++++- core/deploy/02_deploy_mezo_allocator.ts | 13 +------ core/deploy/11_acre_update_dispatcher.ts | 21 ---------- .../13_mezo_allocator_update_storage.ts | 26 +++++++++++++ .../24_transfer_ownership_mezo_allocator.ts | 39 +++++++++++++++++++ 5 files changed, 80 insertions(+), 33 deletions(-) delete mode 100644 core/deploy/11_acre_update_dispatcher.ts create mode 100644 core/deploy/13_mezo_allocator_update_storage.ts create mode 100644 core/deploy/24_transfer_ownership_mezo_allocator.ts diff --git a/core/deploy/00_resolve_mezo_portal.ts b/core/deploy/00_resolve_mezo_portal.ts index 02cf262b2..312b8ab95 100644 --- a/core/deploy/00_resolve_mezo_portal.ts +++ b/core/deploy/00_resolve_mezo_portal.ts @@ -4,10 +4,12 @@ import type { HardhatRuntimeEnvironment, } from "hardhat/types" import { isNonZeroAddress } from "../helpers/address" +import { waitConfirmationsNumber } from "../helpers/deployment" const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const { deployments } = hre + const { getNamedAccounts, deployments } = hre const { log } = deployments + const { deployer } = await getNamedAccounts() const mezoPortal = await deployments.getOrNull("MezoPortal") @@ -15,6 +17,16 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { log(`using MezoPortal contract at ${mezoPortal.address}`) } else if ((hre.network.config as HardhatNetworkConfig)?.forking?.enabled) { throw new Error("deployed MezoPortal contract not found") + } else { + log("deploying Mezo Portal contract stub") + + await deployments.deploy("MezoPortal", { + contract: "MezoPortalStub", + args: [], + from: deployer, + log: true, + waitConfirmations: waitConfirmationsNumber(hre), + }) } } diff --git a/core/deploy/02_deploy_mezo_allocator.ts b/core/deploy/02_deploy_mezo_allocator.ts index c07250005..bcada08dd 100644 --- a/core/deploy/02_deploy_mezo_allocator.ts +++ b/core/deploy/02_deploy_mezo_allocator.ts @@ -5,22 +5,13 @@ import { waitConfirmationsNumber } from "../helpers/deployment" const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { getNamedAccounts, deployments, helpers } = hre const { deployer } = await getNamedAccounts() - const { log } = deployments const tbtc = await deployments.get("TBTC") - // Fake random address for local development purposes only. - const fakeMezoPortal = "0x0af5DC16568EFF2d480a43A77E6C409e497FcFb9" - const mezoPortal = await deployments.getOrNull("MezoPortal") - - let mezoPortalAddress = mezoPortal?.address - if (!mezoPortalAddress && hre.network.name === "hardhat") { - mezoPortalAddress = fakeMezoPortal - log(`using fake Mezo Portal address ${mezoPortalAddress}`) - } + const mezoPortal = await deployments.get("MezoPortal") const mezoAllocator = await deployments.deploy("MezoAllocator", { from: deployer, - args: [mezoPortalAddress, tbtc.address], + args: [mezoPortal.address, tbtc.address], log: true, waitConfirmations: waitConfirmationsNumber(hre), }) diff --git a/core/deploy/11_acre_update_dispatcher.ts b/core/deploy/11_acre_update_dispatcher.ts deleted file mode 100644 index 6ddb718d8..000000000 --- a/core/deploy/11_acre_update_dispatcher.ts +++ /dev/null @@ -1,21 +0,0 @@ -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( - "stBTC", - { from: deployer, log: true, waitConfirmations: 1 }, - "updateDispatcher", - dispatcher.address, - ) -} - -export default func - -func.tags = ["AcreUpdateDispatcher"] -func.dependencies = ["stBTC", "Dispatcher"] diff --git a/core/deploy/13_mezo_allocator_update_storage.ts b/core/deploy/13_mezo_allocator_update_storage.ts new file mode 100644 index 000000000..8f6785756 --- /dev/null +++ b/core/deploy/13_mezo_allocator_update_storage.ts @@ -0,0 +1,26 @@ +import type { HardhatRuntimeEnvironment } from "hardhat/types" +import type { DeployFunction } from "hardhat-deploy/types" +import { waitConfirmationsNumber } from "../helpers/deployment" + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { getNamedAccounts, deployments } = hre + const { deployer } = await getNamedAccounts() + + const stbtc = await deployments.get("stBTC") + + await deployments.execute( + "MezoAllocator", + { + from: deployer, + log: true, + waitConfirmations: waitConfirmationsNumber(hre), + }, + "updateTbtcStorage", + stbtc.address, + ) +} + +export default func + +func.tags = ["MezoAllocatorUpdateStorage"] +func.dependencies = ["stBTC"] diff --git a/core/deploy/24_transfer_ownership_mezo_allocator.ts b/core/deploy/24_transfer_ownership_mezo_allocator.ts new file mode 100644 index 000000000..f6add8a43 --- /dev/null +++ b/core/deploy/24_transfer_ownership_mezo_allocator.ts @@ -0,0 +1,39 @@ +import type { HardhatRuntimeEnvironment } from "hardhat/types" +import type { DeployFunction } from "hardhat-deploy/types" +import { waitConfirmationsNumber } from "../helpers/deployment" + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { getNamedAccounts, deployments } = hre + const { deployer, governance } = await getNamedAccounts() + const { log } = deployments + + log(`transferring ownership of MezoAllocator contract to ${governance}`) + + await deployments.execute( + "MezoAllocator", + { + from: deployer, + log: true, + waitConfirmations: waitConfirmationsNumber(hre), + }, + "transferOwnership", + governance, + ) + + if (hre.network.name !== "mainnet") { + await deployments.execute( + "MezoAllocator", + { + from: governance, + log: true, + waitConfirmations: waitConfirmationsNumber(hre), + }, + "acceptOwnership", + ) + } +} + +export default func + +func.tags = ["TransferOwnershipMezoAllocator"] +func.dependencies = ["MezoAllocator"] From 0476309be59eb26479f7359f87ca9cf0086334bd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 21 Mar 2024 16:33:06 +0100 Subject: [PATCH 06/75] Replacing Dispatcher with MezoAllocator At first Mezo Allocator will be Acre's dispatcher. Later on a proper dispatcher will be implemented and switched with the Mezo Allocator. --- core/deploy/11_stbtc_update_dispatcher.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 core/deploy/11_stbtc_update_dispatcher.ts diff --git a/core/deploy/11_stbtc_update_dispatcher.ts b/core/deploy/11_stbtc_update_dispatcher.ts new file mode 100644 index 000000000..280950a1d --- /dev/null +++ b/core/deploy/11_stbtc_update_dispatcher.ts @@ -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("MezoAllocator") + + await deployments.execute( + "stBTC", + { from: deployer, log: true, waitConfirmations: 1 }, + "updateDispatcher", + dispatcher.address, + ) +} + +export default func + +func.tags = ["stBTCUpdateDispatcher"] +func.dependencies = ["stBTC", "Dispatcher"] From bf84e76e576d4c5261dd5a110e074e16b3984cdf Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 21 Mar 2024 16:34:32 +0100 Subject: [PATCH 07/75] Drafting first tests for MezoAllocator --- core/.solhintignore | 1 + core/test/Dispatcher.test.ts | 2 +- core/test/MezoAllocator.test.ts | 139 ++++++++++++++++++++++++++++++++ core/test/helpers/context.ts | 7 ++ 4 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 core/test/MezoAllocator.test.ts diff --git a/core/.solhintignore b/core/.solhintignore index c2658d7d1..a908c5b25 100644 --- a/core/.solhintignore +++ b/core/.solhintignore @@ -1 +1,2 @@ node_modules/ +MezoPortalStub.sol diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts index 4e1e8a987..b860a70ad 100644 --- a/core/test/Dispatcher.test.ts +++ b/core/test/Dispatcher.test.ts @@ -29,7 +29,7 @@ async function fixture() { return { dispatcher, governance, thirdParty, maintainer, vault, tbtc, stbtc } } -describe("Dispatcher", () => { +describe.skip("Dispatcher", () => { let dispatcher: Dispatcher let vault: TestERC4626 let tbtc: TestERC20 diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts new file mode 100644 index 000000000..be495e121 --- /dev/null +++ b/core/test/MezoAllocator.test.ts @@ -0,0 +1,139 @@ +import { helpers } from "hardhat" +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" +import { expect } from "chai" +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" + +import { ContractTransactionResponse } from "ethers" +import { beforeAfterSnapshotWrapper, deployment } from "./helpers" + +import { + StBTC as stBTC, + TestERC20, + MezoAllocator, + IMezoPortal, +} from "../typechain" + +import { to1e18 } from "./utils" + +const { getNamedSigners, getUnnamedSigners } = helpers.signers + +async function fixture() { + const { tbtc, stbtc, dispatcher, mezoAllocator, mezoPortal } = + await deployment() + const { governance, maintainer } = await getNamedSigners() + const [thirdParty] = await getUnnamedSigners() + + return { + dispatcher, + governance, + thirdParty, + maintainer, + tbtc, + stbtc, + mezoAllocator, + mezoPortal, + } +} + +describe("MezoAllocator", () => { + let tbtc: TestERC20 + let stbtc: stBTC + let mezoAllocator: MezoAllocator + let mezoPortal: IMezoPortal + + let governance: HardhatEthersSigner + let thirdParty: HardhatEthersSigner + let maintainer: HardhatEthersSigner + + before(async () => { + ;({ + governance, + thirdParty, + maintainer, + tbtc, + stbtc, + mezoAllocator, + mezoPortal, + } = await loadFixture(fixture)) + }) + + describe("deposit", () => { + beforeAfterSnapshotWrapper() + + before(async () => {}) + + context("when the caller is not an owner", () => { + it("should revert", async () => { + await expect( + mezoAllocator.connect(thirdParty).deposit(to1e18(1)), + ).to.be.revertedWithCustomError(mezoAllocator, "NotAuthorized") + }) + }) + + context("when the caller is an owner", () => { + it("should not revert", async () => { + await expect( + mezoAllocator.connect(governance).deposit(to1e18(1)), + ).to.not.be.revertedWithCustomError(mezoAllocator, "NotAuthorized") + }) + }) + + context("when the caller is maintainer", () => { + context("when first deposit is made", () => { + let tx: ContractTransactionResponse + + before(async () => { + await tbtc.mint(await stbtc.getAddress(), to1e18(1)) + await mezoAllocator + .connect(governance) + .updateMaintainer(maintainer.address) + + tx = await mezoAllocator.connect(maintainer).deposit(to1e18(1)) + }) + + it("should deposit and transfer tBTC to Mezo Portal", async () => { + expect( + await tbtc.balanceOf(await mezoAllocator.getAddress()), + ).to.equal(0) + expect(await tbtc.balanceOf(await mezoPortal.getAddress())).to.equal( + to1e18(1), + ) + }) + + it("should populate deposits array", async () => { + expect(await mezoAllocator.deposits(0)).to.equal(1) + }) + + it("should populate deposits mapping", async () => { + expect(await mezoAllocator.depositsById(1)).to.equal(to1e18(1)) + }) + + it("should emit Deposit event", async () => { + const latestDepositId = await mezoAllocator.deposits(0) + await expect(tx) + .to.emit(mezoAllocator, "DepositAllocated") + .withArgs(latestDepositId, to1e18(1)) + }) + }) + + context("when second deposit is made", () => { + before(async () => { + await tbtc.mint(await stbtc.getAddress(), to1e18(5)) + await mezoAllocator + .connect(governance) + .updateMaintainer(maintainer.address) + + await mezoAllocator.connect(maintainer).deposit(to1e18(5)) + }) + + it("should increment the deposits array", async () => { + expect(await mezoAllocator.deposits(1)).to.equal(2) + }) + + it("should populate deposits mapping", async () => { + expect(await mezoAllocator.depositsById(2)).to.equal(to1e18(5)) + }) + }) + }) + }) +}) diff --git a/core/test/helpers/context.ts b/core/test/helpers/context.ts index 239721235..fe7842dd4 100644 --- a/core/test/helpers/context.ts +++ b/core/test/helpers/context.ts @@ -9,6 +9,8 @@ import type { TestERC4626, TBTCVaultStub, AcreBitcoinDepositorHarness, + MezoAllocator, + MezoPortalStub, } from "../../typechain" // eslint-disable-next-line import/prefer-default-export @@ -26,6 +28,9 @@ export async function deployment() { const dispatcher: Dispatcher = await getDeployedContract("Dispatcher") const vault: TestERC4626 = await getDeployedContract("Vault") + const mezoAllocator: MezoAllocator = + await getDeployedContract("MezoAllocator") + const mezoPortal: MezoPortalStub = await getDeployedContract("MezoPortal") return { tbtc, @@ -35,5 +40,7 @@ export async function deployment() { tbtcVault, dispatcher, vault, + mezoAllocator, + mezoPortal, } } From 2ae4a4365be3a0e95a324f368ec81de41ee34264 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 22 Mar 2024 12:24:28 +0100 Subject: [PATCH 08/75] Updating minor comments for MezoAllocator --- core/contracts/MezoAllocator.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 294aaa3ca..ab29376d3 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -121,10 +121,10 @@ contract MezoAllocator is Ownable2Step { // TODO: add updatable withdrawer and onlyWithdrawer modifier (stBTC or AcreDispatcher). /// @notice Withdraws tBTC from MezoPortal and transfers it to stBTC. function withdraw(uint96 amount) external { - // TODO: Take the latest deposit and pull funds from it. + // TODO: Take the last deposit and pull the funds from it (FIFO). // If not enough funds, take everything from that deposit and // take the rest from the next deposit id until the amount is - // reached. + // reached. Delete deposit ids that are empty. // IMezoPortal(mezoPortal).withdraw(address(tbtc), depositId, amount); // TODO: update depositsById and deposits data structures. // IERC20(tbtc).safeTransfer(address(tbtcStorage), amount); From f4a9d5001d1dc3fcf3221a2c1ee8651c8cef1084 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 22 Mar 2024 12:24:36 +0100 Subject: [PATCH 09/75] Adding more tests and deployment scripts Dispatcher in the current form will be replaced in the future. For now, the role of the dispatcher contract will server the MezoAllocator contract since we do not have any other L2s/DeFi partners yet. Once we have it, it will be updated for the proper dispatcher that will include MezoAllocator. --- core/deploy/02_deploy_mezo_allocator.ts | 2 +- .../12_mezo_allocator_update_maintainer.ts | 24 +++++++++ core/test/Deployment.test.ts | 26 +++++++--- core/test/Dispatcher.test.ts | 2 +- core/test/MezoAllocator.test.ts | 50 +++++++++++++++++++ core/test/stBTC.test.ts | 12 ++--- 6 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 core/deploy/12_mezo_allocator_update_maintainer.ts diff --git a/core/deploy/02_deploy_mezo_allocator.ts b/core/deploy/02_deploy_mezo_allocator.ts index bcada08dd..33cee9344 100644 --- a/core/deploy/02_deploy_mezo_allocator.ts +++ b/core/deploy/02_deploy_mezo_allocator.ts @@ -26,4 +26,4 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { export default func func.tags = ["MezoAllocator"] -func.dependencies = ["TBTC"] +func.dependencies = ["TBTC", "MezoPortal"] diff --git a/core/deploy/12_mezo_allocator_update_maintainer.ts b/core/deploy/12_mezo_allocator_update_maintainer.ts new file mode 100644 index 000000000..3d9be2d05 --- /dev/null +++ b/core/deploy/12_mezo_allocator_update_maintainer.ts @@ -0,0 +1,24 @@ +import type { HardhatRuntimeEnvironment } from "hardhat/types" +import type { DeployFunction } from "hardhat-deploy/types" +import { waitConfirmationsNumber } from "../helpers/deployment" + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { getNamedAccounts, deployments } = hre + const { deployer, maintainer } = await getNamedAccounts() + + await deployments.execute( + "MezoAllocator", + { + from: deployer, + log: true, + waitConfirmations: waitConfirmationsNumber(hre), + }, + "updateMaintainer", + maintainer, + ) +} + +export default func + +func.tags = ["DispatcherUpdateMaintainer"] +func.dependencies = ["Dispatcher"] diff --git a/core/test/Deployment.test.ts b/core/test/Deployment.test.ts index 5914e98bb..5b8d42de8 100644 --- a/core/test/Deployment.test.ts +++ b/core/test/Deployment.test.ts @@ -6,26 +6,26 @@ import { helpers } from "hardhat" import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import { deployment } from "./helpers/context" -import type { StBTC as stBTC, Dispatcher, TestERC20 } from "../typechain" +import type { StBTC as stBTC, TestERC20, MezoAllocator } from "../typechain" const { getNamedSigners } = helpers.signers async function fixture() { - const { tbtc, stbtc, dispatcher } = await deployment() + const { tbtc, stbtc, mezoAllocator } = await deployment() const { governance, maintainer, treasury } = await getNamedSigners() - return { stbtc, dispatcher, tbtc, governance, maintainer, treasury } + return { stbtc, mezoAllocator, tbtc, governance, maintainer, treasury } } describe("Deployment", () => { let stbtc: stBTC - let dispatcher: Dispatcher + let mezoAllocator: MezoAllocator let tbtc: TestERC20 let maintainer: HardhatEthersSigner let treasury: HardhatEthersSigner before(async () => { - ;({ stbtc, dispatcher, tbtc, maintainer, treasury } = + ;({ stbtc, mezoAllocator, tbtc, maintainer, treasury } = await loadFixture(fixture)) }) @@ -45,7 +45,7 @@ describe("Deployment", () => { it("should be set to a dispatcher address by the deployment script", async () => { const actualDispatcher = await stbtc.dispatcher() - expect(actualDispatcher).to.be.equal(await dispatcher.getAddress()) + expect(actualDispatcher).to.be.equal(await mezoAllocator.getAddress()) }) it("should approve max amount for the dispatcher", async () => { @@ -61,15 +61,25 @@ describe("Deployment", () => { }) }) - describe("Dispatcher", () => { + describe("MezoAllocator", () => { describe("updateMaintainer", () => { context("when a new maintainer has been set", () => { it("should be set to a new maintainer address", async () => { - const actualMaintainer = await dispatcher.maintainer() + const actualMaintainer = await mezoAllocator.maintainer() expect(actualMaintainer).to.be.equal(await maintainer.getAddress()) }) }) }) + + describe("updateTbtcStorage", () => { + context("when a new stBTC address has been set", () => { + it("should be set to a new stBTC address by the deployment script", async () => { + const actualTbtcStorage = await mezoAllocator.tbtcStorage() + + expect(actualTbtcStorage).to.be.equal(await stbtc.getAddress()) + }) + }) + }) }) }) diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts index b860a70ad..dfe7dba92 100644 --- a/core/test/Dispatcher.test.ts +++ b/core/test/Dispatcher.test.ts @@ -28,7 +28,7 @@ async function fixture() { return { dispatcher, governance, thirdParty, maintainer, vault, tbtc, stbtc } } - +// TODO: Remove these tests once Distpather contract is removed from the project. describe.skip("Dispatcher", () => { let dispatcher: Dispatcher let vault: TestERC4626 diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index be495e121..f4dd95670 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -136,4 +136,54 @@ describe("MezoAllocator", () => { }) }) }) + + describe("updateTbtcStorage", () => { + context("when the caller is not an owner", () => { + it("should revert", async () => { + await expect( + mezoAllocator + .connect(thirdParty) + .updateTbtcStorage(thirdParty.address), + ).to.be.revertedWithCustomError( + mezoAllocator, + "OwnableUnauthorizedAccount", + ) + }) + }) + + context("when the caller is an owner", () => { + it("should not revert", async () => { + await mezoAllocator + .connect(governance) + .updateTbtcStorage(thirdParty.address) + const tbtcStorageAddress = await mezoAllocator.tbtcStorage() + expect(tbtcStorageAddress).to.equal(thirdParty.address) + }) + }) + }) + + describe("updateMaintainer", () => { + context("when the caller is not an owner", () => { + it("should revert", async () => { + await expect( + mezoAllocator + .connect(thirdParty) + .updateMaintainer(thirdParty.address), + ).to.be.revertedWithCustomError( + mezoAllocator, + "OwnableUnauthorizedAccount", + ) + }) + }) + + context("when the caller is an owner", () => { + it("should not revert", async () => { + await mezoAllocator + .connect(governance) + .updateMaintainer(thirdParty.address) + const maintainerAddress = await mezoAllocator.maintainer() + expect(maintainerAddress).to.equal(thirdParty.address) + }) + }) + }) }) diff --git a/core/test/stBTC.test.ts b/core/test/stBTC.test.ts index c5ce7d207..26c50931e 100644 --- a/core/test/stBTC.test.ts +++ b/core/test/stBTC.test.ts @@ -12,12 +12,12 @@ import { beforeAfterSnapshotWrapper, deployment } from "./helpers" import { to1e18 } from "./utils" -import type { StBTC as stBTC, TestERC20, Dispatcher } from "../typechain" +import type { StBTC as stBTC, TestERC20, MezoAllocator } from "../typechain" const { getNamedSigners, getUnnamedSigners } = helpers.signers async function fixture() { - const { tbtc, stbtc, dispatcher } = await deployment() + const { tbtc, stbtc, mezoAllocator } = await deployment() const { governance, treasury } = await getNamedSigners() const [depositor1, depositor2, thirdParty] = await getUnnamedSigners() @@ -31,10 +31,10 @@ async function fixture() { tbtc, depositor1, depositor2, - dispatcher, governance, thirdParty, treasury, + mezoAllocator, } } @@ -45,7 +45,7 @@ describe("stBTC", () => { let stbtc: stBTC let tbtc: TestERC20 - let dispatcher: Dispatcher + let mezoAllocator: MezoAllocator let governance: HardhatEthersSigner let depositor1: HardhatEthersSigner @@ -59,10 +59,10 @@ describe("stBTC", () => { tbtc, depositor1, depositor2, - dispatcher, governance, thirdParty, treasury, + mezoAllocator, } = await loadFixture(fixture)) await stbtc @@ -1327,7 +1327,7 @@ describe("stBTC", () => { before(async () => { // Dispatcher is set by the deployment scripts. See deployment tests // where initial parameters are checked. - dispatcherAddress = await dispatcher.getAddress() + dispatcherAddress = await mezoAllocator.getAddress() newDispatcher = await ethers.Wallet.createRandom().getAddress() stbtcAddress = await stbtc.getAddress() From 4d73d9eeecb2fcf72534187fb7b6b4cec67c961b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 25 Mar 2024 10:33:45 +0100 Subject: [PATCH 10/75] Adding hardhat network check for resolve_mezo_portal script --- core/deploy/00_resolve_mezo_portal.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/deploy/00_resolve_mezo_portal.ts b/core/deploy/00_resolve_mezo_portal.ts index 312b8ab95..412c468d6 100644 --- a/core/deploy/00_resolve_mezo_portal.ts +++ b/core/deploy/00_resolve_mezo_portal.ts @@ -15,7 +15,10 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { if (mezoPortal && isNonZeroAddress(mezoPortal.address)) { log(`using MezoPortal contract at ${mezoPortal.address}`) - } else if ((hre.network.config as HardhatNetworkConfig)?.forking?.enabled) { + } else if ( + (hre.network.config as HardhatNetworkConfig)?.forking?.enabled && + hre.network.name !== "hardhat" + ) { throw new Error("deployed MezoPortal contract not found") } else { log("deploying Mezo Portal contract stub") From 94c96e7989017d5776a287d1bd13b20a563e0ee5 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 26 Mar 2024 16:34:04 +0100 Subject: [PATCH 11/75] Adding creation and unlock deposit timestamps for future use It might happen that we'll need a data structure to support information like deposit creation and unclock time in case we shift from the zero time locks. --- core/contracts/MezoAllocator.sol | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index ab29376d3..55ed4176a 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -17,6 +17,14 @@ interface IMezoPortal { contract MezoAllocator is Ownable2Step { using SafeERC20 for IERC20; + /// @notice DepositInfo keeps track of the deposit balance, creation time, + /// and unlock time. + struct DepositInfo { + uint96 balance; + uint32 createdAt; + uint32 unlockAt; + } + /// Address of the MezoPortal contract. address public immutable mezoPortal; /// tBTC token contract. @@ -27,8 +35,8 @@ contract MezoAllocator is Ownable2Step { /// @notice Maintainer address which can trigger deposit flow. address public maintainer; - // Deposit ID -> Deposit Amount - mapping(uint256 => uint256) public depositsById; + // Deposit ID -> Deposit Info + mapping(uint256 => DepositInfo) public depositsById; // Deposit IDs uint256[] public deposits; @@ -75,13 +83,18 @@ contract MezoAllocator is Ownable2Step { // slither-disable-next-line arbitrary-send-erc20 IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); IERC20(tbtc).forceApprove(mezoPortal, amount); - // 0 denotes no lock period for this deposit. + // 0 denotes no lock period for this deposit. The zero lock time is + // hardcoded as of biz decision. IMezoPortal(mezoPortal).deposit(address(tbtc), amount, 0); // MezoPortal doesn't return depositId, so we have to read depositCounter // which assignes depositId to the current deposit. uint256 depositId = IMezoPortal(mezoPortal).depositCount(); // slither-disable-next-line reentrancy-benign - depositsById[depositId] = amount; + depositsById[depositId] = DepositInfo({ + balance: amount, + createdAt: uint32(block.timestamp), + unlockAt: uint32(block.timestamp) + }); deposits.push(depositId); // slither-disable-next-line reentrancy-events From 0f1ac2389c4d046a21ad319f4241292e571ea23c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 27 Mar 2024 12:36:39 +0100 Subject: [PATCH 12/75] Cleanups and smaller refactorings around MezoAllocator - Made Slither and Solhing happier - Renamed the tag name for Mezo Allocator --- core/contracts/MezoAllocator.sol | 14 ++++++++------ core/deploy/12_mezo_allocator_update_maintainer.ts | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 55ed4176a..c4efa81f9 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -84,7 +84,7 @@ contract MezoAllocator is Ownable2Step { IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); IERC20(tbtc).forceApprove(mezoPortal, amount); // 0 denotes no lock period for this deposit. The zero lock time is - // hardcoded as of biz decision. + // hardcoded as of biz decision. IMezoPortal(mezoPortal).deposit(address(tbtc), amount, 0); // MezoPortal doesn't return depositId, so we have to read depositCounter // which assignes depositId to the current deposit. @@ -92,7 +92,9 @@ contract MezoAllocator is Ownable2Step { // slither-disable-next-line reentrancy-benign depositsById[depositId] = DepositInfo({ balance: amount, + // solhint-disable-next-line not-rely-on-time createdAt: uint32(block.timestamp), + // solhint-disable-next-line not-rely-on-time unlockAt: uint32(block.timestamp) }); deposits.push(depositId); @@ -126,11 +128,6 @@ contract MezoAllocator is Ownable2Step { emit MaintainerUpdated(_maintainer); } - /// @notice Returns the deposit IDs. - function getDeposits() external view returns (uint256[] memory) { - return deposits; - } - // TODO: add updatable withdrawer and onlyWithdrawer modifier (stBTC or AcreDispatcher). /// @notice Withdraws tBTC from MezoPortal and transfers it to stBTC. function withdraw(uint96 amount) external { @@ -142,4 +139,9 @@ contract MezoAllocator is Ownable2Step { // TODO: update depositsById and deposits data structures. // IERC20(tbtc).safeTransfer(address(tbtcStorage), amount); } + + /// @notice Returns the deposit IDs. + function getDeposits() external view returns (uint256[] memory) { + return deposits; + } } diff --git a/core/deploy/12_mezo_allocator_update_maintainer.ts b/core/deploy/12_mezo_allocator_update_maintainer.ts index 3d9be2d05..4e1f5fa18 100644 --- a/core/deploy/12_mezo_allocator_update_maintainer.ts +++ b/core/deploy/12_mezo_allocator_update_maintainer.ts @@ -20,5 +20,5 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { export default func -func.tags = ["DispatcherUpdateMaintainer"] +func.tags = ["MezoAllocatorUpdateMaintainer"] func.dependencies = ["Dispatcher"] From b24448e30169f35354f8191b8c7a505ae7f39716 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 27 Mar 2024 12:42:06 +0100 Subject: [PATCH 13/75] Adding tests supporting DepositInfo struct --- core/test/MezoAllocator.test.ts | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index f4dd95670..9dfd2b2c1 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -60,8 +60,6 @@ describe("MezoAllocator", () => { describe("deposit", () => { beforeAfterSnapshotWrapper() - before(async () => {}) - context("when the caller is not an owner", () => { it("should revert", async () => { await expect( @@ -104,8 +102,31 @@ describe("MezoAllocator", () => { expect(await mezoAllocator.deposits(0)).to.equal(1) }) - it("should populate deposits mapping", async () => { - expect(await mezoAllocator.depositsById(1)).to.equal(to1e18(1)) + it("should set deposit balance", async () => { + const deposit = await mezoAllocator.depositsById(1) + expect(deposit.balance).to.equal(to1e18(1)) + }) + + it("should set creation timestamp", async () => { + const deposit = await mezoAllocator.depositsById(1) + const dateTime = new Date() + // Check if the block timestamp is within 60 seconds of the current + // test time + expect(deposit.createdAt).to.be.closeTo( + String(dateTime.valueOf()).slice(0, -3), + 60, + ) + }) + + it("should set unlocking timestamp", async () => { + const deposit = await mezoAllocator.depositsById(1) + const dateTime = new Date() + // Check if the block timestamp is within 60 seconds of the current + // test time + expect(deposit.unlockAt).to.be.closeTo( + String(dateTime.valueOf()).slice(0, -3), + 60, + ) }) it("should emit Deposit event", async () => { @@ -131,7 +152,8 @@ describe("MezoAllocator", () => { }) it("should populate deposits mapping", async () => { - expect(await mezoAllocator.depositsById(2)).to.equal(to1e18(5)) + const deposit = await mezoAllocator.depositsById(2) + expect(deposit.balance).to.equal(to1e18(5)) }) }) }) From 6c18a4991aca501ce892ddbdb3e40a3901e323ee Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 4 Apr 2024 14:14:03 +0200 Subject: [PATCH 14/75] Rename MezoAllocator deposit -> allocate --- core/contracts/MezoAllocator.sol | 4 ++-- core/test/MezoAllocator.test.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index c4efa81f9..5800897e7 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -76,10 +76,10 @@ contract MezoAllocator is Ownable2Step { tbtc = _tbtc; } - /// @notice Deposits tBTC to MezoPortal. + /// @notice Allocate tBTC to MezoPortal. /// @dev This function can be invoked periodically by a bot. /// @param amount Amount of tBTC to deposit to Mezo Portal. - function deposit(uint96 amount) external onlyMaintainerAndOwner { + function allocate(uint96 amount) external onlyMaintainerAndOwner { // slither-disable-next-line arbitrary-send-erc20 IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); IERC20(tbtc).forceApprove(mezoPortal, amount); diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index 9dfd2b2c1..bbc5232fe 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -57,13 +57,13 @@ describe("MezoAllocator", () => { } = await loadFixture(fixture)) }) - describe("deposit", () => { + describe("allocate", () => { beforeAfterSnapshotWrapper() context("when the caller is not an owner", () => { it("should revert", async () => { await expect( - mezoAllocator.connect(thirdParty).deposit(to1e18(1)), + mezoAllocator.connect(thirdParty).allocate(to1e18(1)), ).to.be.revertedWithCustomError(mezoAllocator, "NotAuthorized") }) }) @@ -71,7 +71,7 @@ describe("MezoAllocator", () => { context("when the caller is an owner", () => { it("should not revert", async () => { await expect( - mezoAllocator.connect(governance).deposit(to1e18(1)), + mezoAllocator.connect(governance).allocate(to1e18(1)), ).to.not.be.revertedWithCustomError(mezoAllocator, "NotAuthorized") }) }) @@ -86,7 +86,7 @@ describe("MezoAllocator", () => { .connect(governance) .updateMaintainer(maintainer.address) - tx = await mezoAllocator.connect(maintainer).deposit(to1e18(1)) + tx = await mezoAllocator.connect(maintainer).allocate(to1e18(1)) }) it("should deposit and transfer tBTC to Mezo Portal", async () => { @@ -144,7 +144,7 @@ describe("MezoAllocator", () => { .connect(governance) .updateMaintainer(maintainer.address) - await mezoAllocator.connect(maintainer).deposit(to1e18(5)) + await mezoAllocator.connect(maintainer).allocate(to1e18(5)) }) it("should increment the deposits array", async () => { From 2b17080e99be329570ce1a4e063cec393b7fe52c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 4 Apr 2024 14:55:09 +0200 Subject: [PATCH 15/75] Allocate tBTC to MezoPortal as a "rolling" deposit Here we change the deposit approach from tracking all the Mezo Deposits to having only a single deposit that accumulates all deposited tBTC by Acre into a single deposit. An "old" deposit is abondoned and balance is zeroed. A "new" deposit is created with a new id and a new balance is equal to the old balance + new requested amount. --- core/contracts/MezoAllocator.sol | 54 +++++++++++++++++++------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 5800897e7..463bddf76 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -17,9 +17,10 @@ interface IMezoPortal { contract MezoAllocator is Ownable2Step { using SafeERC20 for IERC20; - /// @notice DepositInfo keeps track of the deposit balance, creation time, - /// and unlock time. + /// @notice DepositInfo keeps track of the deposit Id, deposit balance, + /// creation time, and unlock time. struct DepositInfo { + uint256 id; uint96 balance; uint32 createdAt; uint32 unlockAt; @@ -35,10 +36,8 @@ contract MezoAllocator is Ownable2Step { /// @notice Maintainer address which can trigger deposit flow. address public maintainer; - // Deposit ID -> Deposit Info - mapping(uint256 => DepositInfo) public depositsById; - // Deposit IDs - uint256[] public deposits; + /// @notice keeps track of the deposit info. + DepositInfo public depositInfo; /// Emitted when tBTC is deposited to MezoPortal. event DepositAllocated(uint256 depositId, uint256 amount); @@ -76,33 +75,49 @@ contract MezoAllocator is Ownable2Step { tbtc = _tbtc; } - /// @notice Allocate tBTC to MezoPortal. + /// @notice Allocate tBTC to MezoPortal. Each allocation creates a new "rolling" + /// deposit meaning that the previous Acre's deposit is fully withdrawn + /// before a new deposit with added amount is created. This mimics a + /// "top up" functionality with the difference that a new deposit id + /// is created and the previous deposit id is no longer used. /// @dev This function can be invoked periodically by a bot. /// @param amount Amount of tBTC to deposit to Mezo Portal. function allocate(uint96 amount) external onlyMaintainerAndOwner { + // Free all Acre's tBTC from MezoPortal before creating a new deposit. + free(); // slither-disable-next-line arbitrary-send-erc20 IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); - IERC20(tbtc).forceApprove(mezoPortal, amount); + + // Add freed tBTC from the previous deposit and add the new amount. + depositInfo.balance += amount; + + IERC20(tbtc).forceApprove(mezoPortal, depositInfo.balance); // 0 denotes no lock period for this deposit. The zero lock time is // hardcoded as of biz decision. - IMezoPortal(mezoPortal).deposit(address(tbtc), amount, 0); + IMezoPortal(mezoPortal).deposit(address(tbtc), depositInfo.balance, 0); // MezoPortal doesn't return depositId, so we have to read depositCounter // which assignes depositId to the current deposit. uint256 depositId = IMezoPortal(mezoPortal).depositCount(); // slither-disable-next-line reentrancy-benign - depositsById[depositId] = DepositInfo({ - balance: amount, - // solhint-disable-next-line not-rely-on-time - createdAt: uint32(block.timestamp), - // solhint-disable-next-line not-rely-on-time - unlockAt: uint32(block.timestamp) - }); - deposits.push(depositId); + depositInfo.id = depositId; + depositInfo.createdAt = uint32(block.timestamp); + depositInfo.unlockAt = uint32(block.timestamp); // slither-disable-next-line reentrancy-events emit DepositAllocated(depositId, amount); } + /// @notice Withdraw all Acre's tBTC from MezoPortal. + function free() private { + if (depositInfo.balance > 0) { + IMezoPortal(mezoPortal).withdraw( + address(tbtc), + depositInfo.id, + depositInfo.balance + ); + } + } + /// @notice Updates the tBTC storage address. /// @dev At first this is going to be the stBTC contract. Once Acre /// works with more destinations for tBTC, this will be updated to @@ -139,9 +154,4 @@ contract MezoAllocator is Ownable2Step { // TODO: update depositsById and deposits data structures. // IERC20(tbtc).safeTransfer(address(tbtcStorage), amount); } - - /// @notice Returns the deposit IDs. - function getDeposits() external view returns (uint256[] memory) { - return deposits; - } } From 37540fb85c1a001d8c3d8fac775cd5383d45a400 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 4 Apr 2024 15:32:06 +0200 Subject: [PATCH 16/75] Cleanup and slithering --- core/contracts/MezoAllocator.sol | 49 +++++++++++++++++++------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 463bddf76..27bceb631 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -40,7 +40,11 @@ contract MezoAllocator is Ownable2Step { DepositInfo public depositInfo; /// Emitted when tBTC is deposited to MezoPortal. - event DepositAllocated(uint256 depositId, uint256 amount); + event DepositAllocated( + uint256 indexed oldDepositId, + uint256 indexed newDepositId, + uint256 amount + ); /// @notice Emitted when the tBTC storage address is updated. event TbtcStorageUpdated(address indexed tbtcStorage); @@ -87,35 +91,28 @@ contract MezoAllocator is Ownable2Step { free(); // slither-disable-next-line arbitrary-send-erc20 IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); - + // Add freed tBTC from the previous deposit and add the new amount. - depositInfo.balance += amount; - - IERC20(tbtc).forceApprove(mezoPortal, depositInfo.balance); + uint96 newBalance = depositInfo.balance + amount; + + IERC20(tbtc).forceApprove(mezoPortal, newBalance); // 0 denotes no lock period for this deposit. The zero lock time is // hardcoded as of biz decision. - IMezoPortal(mezoPortal).deposit(address(tbtc), depositInfo.balance, 0); + IMezoPortal(mezoPortal).deposit(address(tbtc), newBalance, 0); // MezoPortal doesn't return depositId, so we have to read depositCounter // which assignes depositId to the current deposit. - uint256 depositId = IMezoPortal(mezoPortal).depositCount(); + uint256 newDepositId = IMezoPortal(mezoPortal).depositCount(); // slither-disable-next-line reentrancy-benign - depositInfo.id = depositId; + uint256 oldDepositId = depositInfo.id; + depositInfo.id = newDepositId; + depositInfo.balance = newBalance; + // solhint-disable-next-line not-rely-on-time depositInfo.createdAt = uint32(block.timestamp); + // solhint-disable-next-line not-rely-on-time depositInfo.unlockAt = uint32(block.timestamp); // slither-disable-next-line reentrancy-events - emit DepositAllocated(depositId, amount); - } - - /// @notice Withdraw all Acre's tBTC from MezoPortal. - function free() private { - if (depositInfo.balance > 0) { - IMezoPortal(mezoPortal).withdraw( - address(tbtc), - depositInfo.id, - depositInfo.balance - ); - } + emit DepositAllocated(oldDepositId, newDepositId, amount); } /// @notice Updates the tBTC storage address. @@ -154,4 +151,16 @@ contract MezoAllocator is Ownable2Step { // TODO: update depositsById and deposits data structures. // IERC20(tbtc).safeTransfer(address(tbtcStorage), amount); } + + /// @notice Withdraw all Acre's tBTC from MezoPortal. + function free() private { + if (depositInfo.balance > 0) { + // slither-disable-next-line reentrancy-no-eth + IMezoPortal(mezoPortal).withdraw( + address(tbtc), + depositInfo.id, + depositInfo.balance + ); + } + } } From dde4c23ec84ad9f723b79e3db2495f8f1a7d6fa7 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 4 Apr 2024 15:54:05 +0200 Subject: [PATCH 17/75] Fixing tests for rolling deposits --- core/contracts/test/MezoPortalStub.sol | 4 +++- core/test/MezoAllocator.test.ts | 29 +++++++++++--------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/core/contracts/test/MezoPortalStub.sol b/core/contracts/test/MezoPortalStub.sol index 496f05394..bdeaae0ee 100644 --- a/core/contracts/test/MezoPortalStub.sol +++ b/core/contracts/test/MezoPortalStub.sol @@ -13,7 +13,9 @@ contract MezoPortalStub { address token, uint256 depositId, uint96 amount - ) external {} + ) external { + IERC20(token).safeTransfer(msg.sender, amount); + } function deposit(address token, uint96 amount, uint32 lockPeriod) external { depositCount++; diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index bbc5232fe..7fb25c48a 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -18,13 +18,11 @@ import { to1e18 } from "./utils" const { getNamedSigners, getUnnamedSigners } = helpers.signers async function fixture() { - const { tbtc, stbtc, dispatcher, mezoAllocator, mezoPortal } = - await deployment() + const { tbtc, stbtc, mezoAllocator, mezoPortal } = await deployment() const { governance, maintainer } = await getNamedSigners() const [thirdParty] = await getUnnamedSigners() return { - dispatcher, governance, thirdParty, maintainer, @@ -98,17 +96,13 @@ describe("MezoAllocator", () => { ) }) - it("should populate deposits array", async () => { - expect(await mezoAllocator.deposits(0)).to.equal(1) - }) - it("should set deposit balance", async () => { - const deposit = await mezoAllocator.depositsById(1) + const deposit = await mezoAllocator.depositInfo() expect(deposit.balance).to.equal(to1e18(1)) }) it("should set creation timestamp", async () => { - const deposit = await mezoAllocator.depositsById(1) + const deposit = await mezoAllocator.depositInfo() const dateTime = new Date() // Check if the block timestamp is within 60 seconds of the current // test time @@ -119,7 +113,7 @@ describe("MezoAllocator", () => { }) it("should set unlocking timestamp", async () => { - const deposit = await mezoAllocator.depositsById(1) + const deposit = await mezoAllocator.depositInfo() const dateTime = new Date() // Check if the block timestamp is within 60 seconds of the current // test time @@ -130,10 +124,9 @@ describe("MezoAllocator", () => { }) it("should emit Deposit event", async () => { - const latestDepositId = await mezoAllocator.deposits(0) await expect(tx) .to.emit(mezoAllocator, "DepositAllocated") - .withArgs(latestDepositId, to1e18(1)) + .withArgs(0, 1, to1e18(1)) }) }) @@ -147,13 +140,15 @@ describe("MezoAllocator", () => { await mezoAllocator.connect(maintainer).allocate(to1e18(5)) }) - it("should increment the deposits array", async () => { - expect(await mezoAllocator.deposits(1)).to.equal(2) + it("should increment the deposit id", async () => { + const depositInfo = await mezoAllocator.depositInfo() + expect(depositInfo.id).to.equal(2) }) - it("should populate deposits mapping", async () => { - const deposit = await mezoAllocator.depositsById(2) - expect(deposit.balance).to.equal(to1e18(5)) + it("should populate deposit balance", async () => { + const deposit = await mezoAllocator.depositInfo() + // 1 + 5 = 6 + expect(deposit.balance).to.equal(to1e18(6)) }) }) }) From 1a24f0009d4196340ce748bd61acefb44f210d09 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 5 Apr 2024 11:09:02 +0200 Subject: [PATCH 18/75] Make MezoAllocator upgradable Make the MezoAllocator contract upgradable and deploy it as transparent proxy with Open Zeppelin upgrades plugin. --- core/contracts/MezoAllocator.sol | 19 ++++++++++++++----- core/deploy/02_deploy_mezo_allocator.ts | 24 +++++++++++++++--------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 27bceb631..addc3953b 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/access/Ownable2Step.sol"; +import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; interface IMezoPortal { function deposit(address token, uint96 amount, uint32 lockPeriod) external; @@ -14,7 +14,7 @@ interface IMezoPortal { } /// @notice MezoAllocator routes tBTC to/from MezoPortal. -contract MezoAllocator is Ownable2Step { +contract MezoAllocator is Ownable2StepUpgradeable { using SafeERC20 for IERC20; /// @notice DepositInfo keeps track of the deposit Id, deposit balance, @@ -27,9 +27,9 @@ contract MezoAllocator is Ownable2Step { } /// Address of the MezoPortal contract. - address public immutable mezoPortal; + address public mezoPortal; /// tBTC token contract. - IERC20 public immutable tbtc; + IERC20 public tbtc; /// Contract holding tBTC deposited by stakers. address public tbtcStorage; @@ -65,16 +65,25 @@ contract MezoAllocator is Ownable2Step { _; } + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + /// @notice Initializes the MezoAllocator contract. /// @param _mezoPortal Address of the MezoPortal contract. /// @param _tbtc Address of the tBTC token contract. - constructor(address _mezoPortal, IERC20 _tbtc) Ownable(msg.sender) { + function initialize(address _mezoPortal, IERC20 _tbtc) public initializer { + __Ownable2Step_init(); + __Ownable_init(msg.sender); + if (_mezoPortal == address(0)) { revert ZeroAddress(); } if (address(_tbtc) == address(0)) { revert ZeroAddress(); } + mezoPortal = _mezoPortal; tbtc = _tbtc; } diff --git a/core/deploy/02_deploy_mezo_allocator.ts b/core/deploy/02_deploy_mezo_allocator.ts index 33cee9344..dd40acbe3 100644 --- a/core/deploy/02_deploy_mezo_allocator.ts +++ b/core/deploy/02_deploy_mezo_allocator.ts @@ -1,23 +1,29 @@ import type { HardhatRuntimeEnvironment } from "hardhat/types" import type { DeployFunction } from "hardhat-deploy/types" -import { waitConfirmationsNumber } from "../helpers/deployment" +import { waitForTransaction } from "../helpers/deployment" const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { getNamedAccounts, deployments, helpers } = hre - const { deployer } = await getNamedAccounts() + const { governance } = await getNamedAccounts() + const { deployer } = await helpers.signers.getNamedSigners() const tbtc = await deployments.get("TBTC") const mezoPortal = await deployments.get("MezoPortal") - const mezoAllocator = await deployments.deploy("MezoAllocator", { - from: deployer, - args: [mezoPortal.address, tbtc.address], - log: true, - waitConfirmations: waitConfirmationsNumber(hre), + const [, deployment] = await helpers.upgrades.deployProxy("MezoAllocator", { + factoryOpts: { + signer: deployer, + }, + initializerArgs: [mezoPortal.address, tbtc.address], + proxyOpts: { + kind: "transparent", + initialOwner: governance, + }, }) - if (hre.network.tags.etherscan) { - await helpers.etherscan.verify(mezoAllocator) + if (deployment.transactionHash && hre.network.tags.etherscan) { + await waitForTransaction(hre, deployment.transactionHash) + await helpers.etherscan.verify(deployment) } // TODO: Add Tenderly verification From ac1e4498c38b1306f7856c8d839303efec05f50b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 5 Apr 2024 12:29:58 +0200 Subject: [PATCH 19/75] Ignoring solhint errors in contracts/test/ --- core/.solhintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/.solhintignore b/core/.solhintignore index a908c5b25..e2af04a45 100644 --- a/core/.solhintignore +++ b/core/.solhintignore @@ -1,2 +1,2 @@ node_modules/ -MezoPortalStub.sol +contracts/test/ \ No newline at end of file From f2d8ca76f9c875a408fb8b7ebab93516088cc762 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 5 Apr 2024 12:31:47 +0200 Subject: [PATCH 20/75] Importing ZeroAddress error from utils --- core/contracts/MezoAllocator.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 27bceb631..a5a0dbc4f 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.21; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {ZeroAddress} from "./utils/Errors.sol"; interface IMezoPortal { function deposit(address token, uint96 amount, uint32 lockPeriod) external; @@ -55,9 +56,6 @@ contract MezoAllocator is Ownable2Step { /// @notice Reverts if the caller is not an authorized account. error NotAuthorized(); - /// @notice Reverts if the address is 0. - error ZeroAddress(); - modifier onlyMaintainerAndOwner() { if (msg.sender != maintainer && owner() != msg.sender) { revert NotAuthorized(); From 17995d972252335be1672e1099f500e4de14dc8b Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 5 Apr 2024 11:42:00 +0200 Subject: [PATCH 21/75] Add tests for MezoAllocator upgrades These are tests we use for contracts upgradeability testing. --- .../test/upgrades/MezoAllocatorV2.sol | 182 ++++++++++++++++++ core/test/MezoAllocator.upgrade.test.ts | 91 +++++++++ 2 files changed, 273 insertions(+) create mode 100644 core/contracts/test/upgrades/MezoAllocatorV2.sol create mode 100644 core/test/MezoAllocator.upgrade.test.ts diff --git a/core/contracts/test/upgrades/MezoAllocatorV2.sol b/core/contracts/test/upgrades/MezoAllocatorV2.sol new file mode 100644 index 000000000..e5e862e07 --- /dev/null +++ b/core/contracts/test/upgrades/MezoAllocatorV2.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; + +interface IMezoPortal { + function deposit(address token, uint96 amount, uint32 lockPeriod) external; + + function withdraw(address token, uint256 depositId, uint96 amount) external; + + function depositCount() external view returns (uint256); +} + +/// @dev This is a contract used to test stBTC upgradeability. It is a copy of +/// stBTC contract with some differences marked with `TEST:` comments. +contract MezoAllocatorV2 is Ownable2StepUpgradeable { + using SafeERC20 for IERC20; + + /// @notice DepositInfo keeps track of the deposit Id, deposit balance, + /// creation time, and unlock time. + struct DepositInfo { + uint256 id; + uint96 balance; + uint32 createdAt; + uint32 unlockAt; + } + + /// Address of the MezoPortal contract. + address public mezoPortal; + /// tBTC token contract. + IERC20 public tbtc; + /// Contract holding tBTC deposited by stakers. + address public tbtcStorage; + + /// @notice Maintainer address which can trigger deposit flow. + address public maintainer; + + /// @notice keeps track of the deposit info. + DepositInfo public depositInfo; + + // TEST: New variable. + uint256 public newVariable; + + /// Emitted when tBTC is deposited to MezoPortal. + event DepositAllocated( + uint256 indexed oldDepositId, + uint256 indexed newDepositId, + uint256 amount + ); + + /// @notice Emitted when the tBTC storage address is updated. + event TbtcStorageUpdated(address indexed tbtcStorage); + + /// @notice Emitted when the maintainer address is updated. + event MaintainerUpdated(address indexed maintainer); + + // TEST: New event. + event NewEvent(); + + /// @notice Reverts if the caller is not an authorized account. + error NotAuthorized(); + + /// @notice Reverts if the address is 0. + error ZeroAddress(); + + modifier onlyMaintainerAndOwner() { + if (msg.sender != maintainer && owner() != msg.sender) { + revert NotAuthorized(); + } + _; + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @notice Initializes the MezoAllocator contract. + /// @param _mezoPortal Address of the MezoPortal contract. + /// @param _tbtc Address of the tBTC token contract. + function initialize(address _mezoPortal, IERC20 _tbtc) public initializer { + // TEST: Removed content of initialize function. Initialize shouldn't be + // called again during the upgrade because of the `initializer` + // modifier. + } + + // TEST: Initializer for V2. + function initializeV2(uint256 _newVariable) public reinitializer(2) { + newVariable = _newVariable; + } + + /// @notice Allocate tBTC to MezoPortal. Each allocation creates a new "rolling" + /// deposit meaning that the previous Acre's deposit is fully withdrawn + /// before a new deposit with added amount is created. This mimics a + /// "top up" functionality with the difference that a new deposit id + /// is created and the previous deposit id is no longer used. + /// @dev This function can be invoked periodically by a bot. + /// @param amount Amount of tBTC to deposit to Mezo Portal. + function allocate(uint96 amount) external onlyMaintainerAndOwner { + // Free all Acre's tBTC from MezoPortal before creating a new deposit. + free(); + // slither-disable-next-line arbitrary-send-erc20 + IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); + + // Add freed tBTC from the previous deposit and add the new amount. + uint96 newBalance = depositInfo.balance + amount; + + IERC20(tbtc).forceApprove(mezoPortal, newBalance); + // 0 denotes no lock period for this deposit. The zero lock time is + // hardcoded as of biz decision. + IMezoPortal(mezoPortal).deposit(address(tbtc), newBalance, 0); + // MezoPortal doesn't return depositId, so we have to read depositCounter + // which assignes depositId to the current deposit. + uint256 newDepositId = IMezoPortal(mezoPortal).depositCount(); + // slither-disable-next-line reentrancy-benign + uint256 oldDepositId = depositInfo.id; + depositInfo.id = newDepositId; + depositInfo.balance = newBalance; + // solhint-disable-next-line not-rely-on-time + depositInfo.createdAt = uint32(block.timestamp); + // solhint-disable-next-line not-rely-on-time + depositInfo.unlockAt = uint32(block.timestamp); + + // slither-disable-next-line reentrancy-events + emit DepositAllocated(oldDepositId, newDepositId, amount); + } + + /// @notice Updates the tBTC storage address. + /// @dev At first this is going to be the stBTC contract. Once Acre + /// works with more destinations for tBTC, this will be updated to + /// the new storage contract like AcreDispatcher. + /// @param _tbtcStorage Address of the new tBTC storage. + // TEST: Modified function. + function updateTbtcStorage(address _tbtcStorage) external onlyOwner { + if (_tbtcStorage == address(0)) { + revert ZeroAddress(); + } + tbtcStorage = _tbtcStorage; + + emit TbtcStorageUpdated(_tbtcStorage); + + // TEST: Emit new event. + emit NewEvent(); + } + + /// @notice Updates the maintainer address. + /// @param _maintainer Address of the new maintainer. + function updateMaintainer(address _maintainer) external onlyOwner { + if (_maintainer == address(0)) { + revert ZeroAddress(); + } + maintainer = _maintainer; + + emit MaintainerUpdated(_maintainer); + } + + // TODO: add updatable withdrawer and onlyWithdrawer modifier (stBTC or AcreDispatcher). + /// @notice Withdraws tBTC from MezoPortal and transfers it to stBTC. + function withdraw(uint96 amount) external { + // TODO: Take the last deposit and pull the funds from it (FIFO). + // If not enough funds, take everything from that deposit and + // take the rest from the next deposit id until the amount is + // reached. Delete deposit ids that are empty. + // IMezoPortal(mezoPortal).withdraw(address(tbtc), depositId, amount); + // TODO: update depositsById and deposits data structures. + // IERC20(tbtc).safeTransfer(address(tbtcStorage), amount); + } + + /// @notice Withdraw all Acre's tBTC from MezoPortal. + function free() private { + if (depositInfo.balance > 0) { + // slither-disable-next-line reentrancy-no-eth + IMezoPortal(mezoPortal).withdraw( + address(tbtc), + depositInfo.id, + depositInfo.balance + ); + } + } +} diff --git a/core/test/MezoAllocator.upgrade.test.ts b/core/test/MezoAllocator.upgrade.test.ts new file mode 100644 index 000000000..4c891e127 --- /dev/null +++ b/core/test/MezoAllocator.upgrade.test.ts @@ -0,0 +1,91 @@ +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { expect } from "chai" +import { ethers, helpers } from "hardhat" +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" +import { ContractTransactionResponse } from "ethers" +import { beforeAfterSnapshotWrapper, deployment } from "./helpers" +import { + TestERC20, + MezoAllocator, + MezoAllocatorV2, + IMezoPortal, + StBTC, +} from "../typechain" + +async function fixture() { + const { stbtc, tbtc, mezoAllocator, mezoPortal } = await deployment() + + return { stbtc, tbtc, mezoAllocator, mezoPortal } +} + +describe("MezoAllocator contract upgrade", () => { + let mezoPortal: IMezoPortal + let tbtc: TestERC20 + let stbtc: StBTC + let mezoAllocator: MezoAllocator + let governance: HardhatEthersSigner + + before(async () => { + ;({ stbtc, tbtc, mezoAllocator, mezoPortal } = await loadFixture(fixture)) + ;({ governance } = await helpers.signers.getNamedSigners()) + }) + + context("when upgrading to a valid contract", () => { + let allocatorV2: MezoAllocatorV2 + const newVariable = 1n + + beforeAfterSnapshotWrapper() + + before(async () => { + const [upgradedAllocator] = await helpers.upgrades.upgradeProxy( + "MezoAllocator", + "MezoAllocatorV2", + { + factoryOpts: { signer: governance }, + proxyOpts: { + call: { + fn: "initializeV2", + args: [newVariable], + }, + }, + }, + ) + + allocatorV2 = upgradedAllocator as unknown as MezoAllocatorV2 + }) + + it("new instance should have the same address as the old one", async () => { + expect(await allocatorV2.getAddress()).to.equal( + await mezoAllocator.getAddress(), + ) + }) + + describe("contract variables", () => { + it("should initialize new variable correctly", async () => { + expect(await allocatorV2.newVariable()).to.eq(newVariable) + }) + + it("should keep v1 initial parameters", async () => { + expect(await allocatorV2.mezoPortal()).to.eq( + await mezoPortal.getAddress(), + ) + expect(await allocatorV2.tbtc()).to.eq(await tbtc.getAddress()) + expect(await allocatorV2.tbtcStorage()).to.eq(await stbtc.getAddress()) + }) + }) + + describe("upgraded `updateTbtcStorage` function", () => { + let tx: ContractTransactionResponse + + before(async () => { + const newAddress = await ethers.Wallet.createRandom().getAddress() + + tx = await allocatorV2.connect(governance).updateTbtcStorage(newAddress) + }) + + it("should emit `NewEvent` event", async () => { + await expect(tx).to.emit(allocatorV2, "NewEvent") + }) + }) + }) +}) From 8faf5422ea2c01b2df7388dcf303ec7cdbacea98 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 5 Apr 2024 14:19:06 +0200 Subject: [PATCH 22/75] Allocating 100% of tBTC Changed the current approach of allocating 100% from deposited tBTC in stBTC. As this is MVP, a bot won't need to set an exact amount that should be allocated in Mezo Portal. Other changes include inlining a "free" function for some gas optimization, removing DepositInfo struct as tracking of depositId is enough and some other minor refactoring. --- core/contracts/MezoAllocator.sol | 90 +++++++++++++++----------------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index a5a0dbc4f..f8f307792 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -7,44 +7,45 @@ import "@openzeppelin/contracts/access/Ownable2Step.sol"; import {ZeroAddress} from "./utils/Errors.sol"; interface IMezoPortal { + struct DepositInfo { + uint96 balance; + uint32 unlockAt; + } + function deposit(address token, uint96 amount, uint32 lockPeriod) external; function withdraw(address token, uint256 depositId, uint96 amount) external; function depositCount() external view returns (uint256); + + function getDeposit( + address depositor, + address token, + uint256 depositId + ) external view returns (DepositInfo memory); } /// @notice MezoAllocator routes tBTC to/from MezoPortal. contract MezoAllocator is Ownable2Step { using SafeERC20 for IERC20; - /// @notice DepositInfo keeps track of the deposit Id, deposit balance, - /// creation time, and unlock time. - struct DepositInfo { - uint256 id; - uint96 balance; - uint32 createdAt; - uint32 unlockAt; - } - /// Address of the MezoPortal contract. - address public immutable mezoPortal; + IMezoPortal public immutable mezoPortal; /// tBTC token contract. IERC20 public immutable tbtc; /// Contract holding tBTC deposited by stakers. address public tbtcStorage; - /// @notice Maintainer address which can trigger deposit flow. address public maintainer; - - /// @notice keeps track of the deposit info. - DepositInfo public depositInfo; + /// @notice keeps track of the latest deposit ID assigned in Mezo Portal. + uint256 public depositId; /// Emitted when tBTC is deposited to MezoPortal. event DepositAllocated( uint256 indexed oldDepositId, uint256 indexed newDepositId, - uint256 amount + uint256 addedAmount, + uint256 newDepositAmount ); /// @notice Emitted when the tBTC storage address is updated. @@ -73,7 +74,7 @@ contract MezoAllocator is Ownable2Step { if (address(_tbtc) == address(0)) { revert ZeroAddress(); } - mezoPortal = _mezoPortal; + mezoPortal = IMezoPortal(_mezoPortal); tbtc = _tbtc; } @@ -81,36 +82,39 @@ contract MezoAllocator is Ownable2Step { /// deposit meaning that the previous Acre's deposit is fully withdrawn /// before a new deposit with added amount is created. This mimics a /// "top up" functionality with the difference that a new deposit id - /// is created and the previous deposit id is no longer used. + /// is created and the previous deposit id is no longer in use. /// @dev This function can be invoked periodically by a bot. - /// @param amount Amount of tBTC to deposit to Mezo Portal. - function allocate(uint96 amount) external onlyMaintainerAndOwner { - // Free all Acre's tBTC from MezoPortal before creating a new deposit. - free(); + function allocate() external onlyMaintainerAndOwner { + uint96 depositBalance = mezoPortal + .getDeposit(address(this), address(tbtc), depositId) + .balance; + if (depositBalance > 0) { + // Free all Acre's tBTC from MezoPortal before creating a new deposit. + // slither-disable-next-line reentrancy-no-eth + mezoPortal.withdraw(address(tbtc), depositId, depositBalance); + } + uint256 addedAmount = IERC20(tbtc).balanceOf(address(tbtcStorage)); // slither-disable-next-line arbitrary-send-erc20 - IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); + IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), addedAmount); - // Add freed tBTC from the previous deposit and add the new amount. - uint96 newBalance = depositInfo.balance + amount; + uint96 newDepositAmount = uint96(IERC20(tbtc).balanceOf(address(this))); - IERC20(tbtc).forceApprove(mezoPortal, newBalance); + IERC20(tbtc).forceApprove(address(mezoPortal), newDepositAmount); // 0 denotes no lock period for this deposit. The zero lock time is // hardcoded as of biz decision. - IMezoPortal(mezoPortal).deposit(address(tbtc), newBalance, 0); + mezoPortal.deposit(address(tbtc), newDepositAmount, 0); + uint256 oldDepositId = depositId; // MezoPortal doesn't return depositId, so we have to read depositCounter - // which assignes depositId to the current deposit. - uint256 newDepositId = IMezoPortal(mezoPortal).depositCount(); - // slither-disable-next-line reentrancy-benign - uint256 oldDepositId = depositInfo.id; - depositInfo.id = newDepositId; - depositInfo.balance = newBalance; - // solhint-disable-next-line not-rely-on-time - depositInfo.createdAt = uint32(block.timestamp); - // solhint-disable-next-line not-rely-on-time - depositInfo.unlockAt = uint32(block.timestamp); + // which assigns depositId to the current deposit. + depositId = mezoPortal.depositCount(); // slither-disable-next-line reentrancy-events - emit DepositAllocated(oldDepositId, newDepositId, amount); + emit DepositAllocated( + oldDepositId, + depositId, + addedAmount, + newDepositAmount + ); } /// @notice Updates the tBTC storage address. @@ -149,16 +153,4 @@ contract MezoAllocator is Ownable2Step { // TODO: update depositsById and deposits data structures. // IERC20(tbtc).safeTransfer(address(tbtcStorage), amount); } - - /// @notice Withdraw all Acre's tBTC from MezoPortal. - function free() private { - if (depositInfo.balance > 0) { - // slither-disable-next-line reentrancy-no-eth - IMezoPortal(mezoPortal).withdraw( - address(tbtc), - depositInfo.id, - depositInfo.balance - ); - } - } } From 020528a31573f4a1b1554c5abb5c452f81268353 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 5 Apr 2024 14:42:50 +0200 Subject: [PATCH 23/75] Updating deployment files --- core/deploy/11_stbtc_update_dispatcher.ts | 2 +- core/deploy/13_mezo_allocator_update_storage.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/deploy/11_stbtc_update_dispatcher.ts b/core/deploy/11_stbtc_update_dispatcher.ts index 280950a1d..9fc08fc51 100644 --- a/core/deploy/11_stbtc_update_dispatcher.ts +++ b/core/deploy/11_stbtc_update_dispatcher.ts @@ -18,4 +18,4 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { export default func func.tags = ["stBTCUpdateDispatcher"] -func.dependencies = ["stBTC", "Dispatcher"] +func.dependencies = ["stBTC", "MezoAllocator"] diff --git a/core/deploy/13_mezo_allocator_update_storage.ts b/core/deploy/13_mezo_allocator_update_storage.ts index 8f6785756..dad7d1a6f 100644 --- a/core/deploy/13_mezo_allocator_update_storage.ts +++ b/core/deploy/13_mezo_allocator_update_storage.ts @@ -23,4 +23,4 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { export default func func.tags = ["MezoAllocatorUpdateStorage"] -func.dependencies = ["stBTC"] +func.dependencies = ["stBTC", "MezoAllocator"] From 109421349b016402171ba740a26238eae633e121 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 5 Apr 2024 14:45:24 +0200 Subject: [PATCH 24/75] Updating deployment Mezo Portal artifacts --- core/external/mainnet/MezoPortal.json | 827 +++++++++++++++++++++++++- core/external/sepolia/MezoPortal.json | 827 +++++++++++++++++++++++++- 2 files changed, 1652 insertions(+), 2 deletions(-) diff --git a/core/external/mainnet/MezoPortal.json b/core/external/mainnet/MezoPortal.json index 858fc0b32..07a2d5bce 100644 --- a/core/external/mainnet/MezoPortal.json +++ b/core/external/mainnet/MezoPortal.json @@ -1,3 +1,828 @@ { - "address": "0xAB13B8eecf5AA2460841d75da5d5D861fD5B8A39" + "address": "0xAB13B8eecf5AA2460841d75da5d5D861fD5B8A39", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "unlockAt", + "type": "uint32" + } + ], + "name": "DepositLocked", + "type": "error" + }, + { + "inputs": [], + "name": "DepositNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "IncorrectAmount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "depositor", + "type": "address" + } + ], + "name": "IncorrectDepositor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "lockPeriod", + "type": "uint256" + } + ], + "name": "IncorrectLockPeriod", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum Portal.TokenAbility", + "name": "ability", + "type": "uint8" + } + ], + "name": "IncorrectTokenAbility", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "IncorrectTokenAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "depositBalance", + "type": "uint256" + } + ], + "name": "InsufficientDepositAmount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "enum Portal.TokenAbility", + "name": "tokenAbility", + "type": "uint8" + } + ], + "name": "InsufficientTokenAbility", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + } + ], + "name": "LockPeriodOutOfRange", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "newUnlockAt", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "existingUnlockAt", + "type": "uint32" + } + ], + "name": "LockPeriodTooShort", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "enum Portal.TokenAbility", + "name": "tokenAbility", + "type": "uint8" + } + ], + "name": "TokenAlreadySupported", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "TokenNotSupported", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Deposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "unlockAt", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + } + ], + "name": "Locked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "maxLockPeriod", + "type": "uint32" + } + ], + "name": "MaxLockPeriodUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "minLockPeriod", + "type": "uint32" + } + ], + "name": "MinLockPeriodUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum Portal.TokenAbility", + "name": "tokenAbility", + "type": "uint8" + } + ], + "name": "SupportedTokenAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawn", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "enum Portal.TokenAbility", + "name": "tokenAbility", + "type": "uint8" + } + ], + "internalType": "struct Portal.SupportedToken", + "name": "supportedToken", + "type": "tuple" + } + ], + "name": "addSupportedToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint96", + "name": "amount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "depositCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "depositOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint96", + "name": "amount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + } + ], + "name": "depositFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "deposits", + "outputs": [ + { + "internalType": "uint96", + "name": "balance", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "unlockAt", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + } + ], + "name": "getDeposit", + "outputs": [ + { + "components": [ + { + "internalType": "uint96", + "name": "balance", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "unlockAt", + "type": "uint32" + } + ], + "internalType": "struct Portal.DepositInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "enum Portal.TokenAbility", + "name": "tokenAbility", + "type": "uint8" + } + ], + "internalType": "struct Portal.SupportedToken[]", + "name": "supportedTokens", + "type": "tuple[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + } + ], + "name": "lock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "maxLockPeriod", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minLockPeriod", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "receiveApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_maxLockPeriod", + "type": "uint32" + } + ], + "name": "setMaxLockPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_minLockPeriod", + "type": "uint32" + } + ], + "name": "setMinLockPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenAbility", + "outputs": [ + { + "internalType": "enum Portal.TokenAbility", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "amount", + "type": "uint96" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0x3e13a1ece7173342ac0146c63dbfb2fb60b2badfb0991a493a868ff0db55540b", + "numDeployments": 1, + "implementation": "0xeAAf2B9e90aA6400d83e07606F5F2A5432502216", + "devdoc": "Contract deployed as upgradable proxy" } \ No newline at end of file diff --git a/core/external/sepolia/MezoPortal.json b/core/external/sepolia/MezoPortal.json index 7c6c1309f..2ba1a0e07 100644 --- a/core/external/sepolia/MezoPortal.json +++ b/core/external/sepolia/MezoPortal.json @@ -1,3 +1,828 @@ { - "address": "0x6978E3e11b8Bc34ea836C1706fC742aC4Cb6b0Db" + "address": "0x6978E3e11b8Bc34ea836C1706fC742aC4Cb6b0Db", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "unlockAt", + "type": "uint32" + } + ], + "name": "DepositLocked", + "type": "error" + }, + { + "inputs": [], + "name": "DepositNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "IncorrectAmount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "depositor", + "type": "address" + } + ], + "name": "IncorrectDepositor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "lockPeriod", + "type": "uint256" + } + ], + "name": "IncorrectLockPeriod", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum Portal.TokenAbility", + "name": "ability", + "type": "uint8" + } + ], + "name": "IncorrectTokenAbility", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "IncorrectTokenAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "depositBalance", + "type": "uint256" + } + ], + "name": "InsufficientDepositAmount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "enum Portal.TokenAbility", + "name": "tokenAbility", + "type": "uint8" + } + ], + "name": "InsufficientTokenAbility", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + } + ], + "name": "LockPeriodOutOfRange", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "newUnlockAt", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "existingUnlockAt", + "type": "uint32" + } + ], + "name": "LockPeriodTooShort", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "enum Portal.TokenAbility", + "name": "tokenAbility", + "type": "uint8" + } + ], + "name": "TokenAlreadySupported", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "TokenNotSupported", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Deposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "unlockAt", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + } + ], + "name": "Locked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "maxLockPeriod", + "type": "uint32" + } + ], + "name": "MaxLockPeriodUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "minLockPeriod", + "type": "uint32" + } + ], + "name": "MinLockPeriodUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum Portal.TokenAbility", + "name": "tokenAbility", + "type": "uint8" + } + ], + "name": "SupportedTokenAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawn", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "enum Portal.TokenAbility", + "name": "tokenAbility", + "type": "uint8" + } + ], + "internalType": "struct Portal.SupportedToken", + "name": "supportedToken", + "type": "tuple" + } + ], + "name": "addSupportedToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint96", + "name": "amount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "depositCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "depositOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint96", + "name": "amount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + } + ], + "name": "depositFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "deposits", + "outputs": [ + { + "internalType": "uint96", + "name": "balance", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "unlockAt", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + } + ], + "name": "getDeposit", + "outputs": [ + { + "components": [ + { + "internalType": "uint96", + "name": "balance", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "unlockAt", + "type": "uint32" + } + ], + "internalType": "struct Portal.DepositInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "enum Portal.TokenAbility", + "name": "tokenAbility", + "type": "uint8" + } + ], + "internalType": "struct Portal.SupportedToken[]", + "name": "supportedTokens", + "type": "tuple[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "lockPeriod", + "type": "uint32" + } + ], + "name": "lock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "maxLockPeriod", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minLockPeriod", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "receiveApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_maxLockPeriod", + "type": "uint32" + } + ], + "name": "setMaxLockPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_minLockPeriod", + "type": "uint32" + } + ], + "name": "setMinLockPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenAbility", + "outputs": [ + { + "internalType": "enum Portal.TokenAbility", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "depositId", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "amount", + "type": "uint96" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xc4b6c7f1865b884f292f456a648464bfa3e5f67133d4b0a92abd11c48945fee2", + "numDeployments": 1, + "implementation": "0x7641f007de71e849c1b75A3e430e8CA13d4bF646", + "devdoc": "Contract deployed as upgradable proxy" } \ No newline at end of file From fee50a4c31e81141885e0ad8a3cf5cb8b0ab8b9f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 5 Apr 2024 15:54:00 +0200 Subject: [PATCH 25/75] Replacing tbtcStorage with stBTC contract Acre will pull the funds directly from stBTC contract to allocate in MezoPortal. --- core/contracts/MezoAllocator.sol | 42 +++++++++++++------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index f8f307792..c6f1777b8 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -5,6 +5,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; import {ZeroAddress} from "./utils/Errors.sol"; +import "./stBTC.sol"; interface IMezoPortal { struct DepositInfo { @@ -33,8 +34,8 @@ contract MezoAllocator is Ownable2Step { IMezoPortal public immutable mezoPortal; /// tBTC token contract. IERC20 public immutable tbtc; - /// Contract holding tBTC deposited by stakers. - address public tbtcStorage; + /// stBTC token vault contract. + stBTC public immutable stbtc; /// @notice Maintainer address which can trigger deposit flow. address public maintainer; /// @notice keeps track of the latest deposit ID assigned in Mezo Portal. @@ -48,9 +49,6 @@ contract MezoAllocator is Ownable2Step { uint256 newDepositAmount ); - /// @notice Emitted when the tBTC storage address is updated. - event TbtcStorageUpdated(address indexed tbtcStorage); - /// @notice Emitted when the maintainer address is updated. event MaintainerUpdated(address indexed maintainer); @@ -67,7 +65,11 @@ contract MezoAllocator is Ownable2Step { /// @notice Initializes the MezoAllocator contract. /// @param _mezoPortal Address of the MezoPortal contract. /// @param _tbtc Address of the tBTC token contract. - constructor(address _mezoPortal, IERC20 _tbtc) Ownable(msg.sender) { + constructor( + address _mezoPortal, + IERC20 _tbtc, + stBTC _stbtc + ) Ownable(msg.sender) { if (_mezoPortal == address(0)) { revert ZeroAddress(); } @@ -76,6 +78,7 @@ contract MezoAllocator is Ownable2Step { } mezoPortal = IMezoPortal(_mezoPortal); tbtc = _tbtc; + stbtc = _stbtc; } /// @notice Allocate tBTC to MezoPortal. Each allocation creates a new "rolling" @@ -93,15 +96,18 @@ contract MezoAllocator is Ownable2Step { // slither-disable-next-line reentrancy-no-eth mezoPortal.withdraw(address(tbtc), depositId, depositBalance); } - uint256 addedAmount = IERC20(tbtc).balanceOf(address(tbtcStorage)); + uint256 addedAmount = IERC20(tbtc).balanceOf(address(stbtc)); // slither-disable-next-line arbitrary-send-erc20 - IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), addedAmount); + IERC20(tbtc).safeTransferFrom( + address(stbtc), + address(this), + addedAmount + ); uint96 newDepositAmount = uint96(IERC20(tbtc).balanceOf(address(this))); IERC20(tbtc).forceApprove(address(mezoPortal), newDepositAmount); - // 0 denotes no lock period for this deposit. The zero lock time is - // hardcoded as of biz decision. + // 0 denotes no lock period for this deposit. mezoPortal.deposit(address(tbtc), newDepositAmount, 0); uint256 oldDepositId = depositId; // MezoPortal doesn't return depositId, so we have to read depositCounter @@ -117,20 +123,6 @@ contract MezoAllocator is Ownable2Step { ); } - /// @notice Updates the tBTC storage address. - /// @dev At first this is going to be the stBTC contract. Once Acre - /// works with more destinations for tBTC, this will be updated to - /// the new storage contract like AcreDispatcher. - /// @param _tbtcStorage Address of the new tBTC storage. - function updateTbtcStorage(address _tbtcStorage) external onlyOwner { - if (_tbtcStorage == address(0)) { - revert ZeroAddress(); - } - tbtcStorage = _tbtcStorage; - - emit TbtcStorageUpdated(_tbtcStorage); - } - /// @notice Updates the maintainer address. /// @param _maintainer Address of the new maintainer. function updateMaintainer(address _maintainer) external onlyOwner { @@ -151,6 +143,6 @@ contract MezoAllocator is Ownable2Step { // reached. Delete deposit ids that are empty. // IMezoPortal(mezoPortal).withdraw(address(tbtc), depositId, amount); // TODO: update depositsById and deposits data structures. - // IERC20(tbtc).safeTransfer(address(tbtcStorage), amount); + // IERC20(tbtc).safeTransfer(address(stbtc), amount); } } From 67993ce3ed2bf831f55ffa991facefc6faed318d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 5 Apr 2024 22:14:35 +0200 Subject: [PATCH 26/75] Drafting withdrawals from Mezo Allocator Acre can pull partial or all assets from Mezo Allocator for given deposit id. These assets are transferred back to stBTC contract and users shares are burned and tBTC is returned to a receiver. --- core/contracts/MezoAllocator.sol | 53 ++++++++++++++++++++++++++------ core/contracts/stBTC.sol | 28 ++++++++++++++--- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index c6f1777b8..0e4f5120c 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -38,6 +38,8 @@ contract MezoAllocator is Ownable2Step { stBTC public immutable stbtc; /// @notice Maintainer address which can trigger deposit flow. address public maintainer; + /// @notice Address that can withdraw tBTC from Mezo Portal. + address public withdrawer; /// @notice keeps track of the latest deposit ID assigned in Mezo Portal. uint256 public depositId; @@ -49,12 +51,21 @@ contract MezoAllocator is Ownable2Step { uint256 newDepositAmount ); + /// Emitted when tBTC is withdrawn from MezoPortal. + event DepositWithdraw(uint256 indexed depositId, uint96 amount); + /// @notice Emitted when the maintainer address is updated. event MaintainerUpdated(address indexed maintainer); + /// @notice Emitted when the withdrawer address is updated. + event WithdrawerUpdated(address indexed withdrawer); + /// @notice Reverts if the caller is not an authorized account. error NotAuthorized(); + /// @notice Reverts if the caller tries to withdraw more tBTC than available. + error InsufficientBalance(); + modifier onlyMaintainerAndOwner() { if (msg.sender != maintainer && owner() != msg.sender) { revert NotAuthorized(); @@ -62,6 +73,13 @@ contract MezoAllocator is Ownable2Step { _; } + modifier onlyWithdrawer() { + if (msg.sender != withdrawer) { + revert NotAuthorized(); + } + _; + } + /// @notice Initializes the MezoAllocator contract. /// @param _mezoPortal Address of the MezoPortal contract. /// @param _tbtc Address of the tBTC token contract. @@ -123,6 +141,22 @@ contract MezoAllocator is Ownable2Step { ); } + /// @notice Withdraws tBTC from MezoPortal and transfers it to stBTC. + /// This function can withdraw partial or a full amount of tBTC from + /// MezoPortal for a given deposit id. + /// @param amount Amount of tBTC to withdraw. + function withdraw(uint96 amount) external onlyWithdrawer { + uint96 balance = mezoPortal + .getDeposit(address(this), address(tbtc), depositId) + .balance; + if (amount > balance) { + revert InsufficientBalance(); + } + emit DepositWithdraw(depositId, amount); + mezoPortal.withdraw(address(tbtc), depositId, amount); + IERC20(tbtc).safeTransfer(address(stbtc), amount); + } + /// @notice Updates the maintainer address. /// @param _maintainer Address of the new maintainer. function updateMaintainer(address _maintainer) external onlyOwner { @@ -134,15 +168,14 @@ contract MezoAllocator is Ownable2Step { emit MaintainerUpdated(_maintainer); } - // TODO: add updatable withdrawer and onlyWithdrawer modifier (stBTC or AcreDispatcher). - /// @notice Withdraws tBTC from MezoPortal and transfers it to stBTC. - function withdraw(uint96 amount) external { - // TODO: Take the last deposit and pull the funds from it (FIFO). - // If not enough funds, take everything from that deposit and - // take the rest from the next deposit id until the amount is - // reached. Delete deposit ids that are empty. - // IMezoPortal(mezoPortal).withdraw(address(tbtc), depositId, amount); - // TODO: update depositsById and deposits data structures. - // IERC20(tbtc).safeTransfer(address(stbtc), amount); + /// @notice Updates the withdrawer address. + /// @param _withdrawer Address of the new withdrawer. + function updateWithdrawer(address _withdrawer) external onlyOwner { + if (_withdrawer == address(0)) { + revert ZeroAddress(); + } + withdrawer = _withdrawer; + + emit WithdrawerUpdated(_withdrawer); } } diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index 563918084..c4a862d91 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -8,6 +8,11 @@ import "./PausableOwnable.sol"; import "./lib/ERC4626Fees.sol"; import {ZeroAddress} from "./utils/Errors.sol"; +// slither-disable-next-line missing-inheritance +interface IDispatcher { + function withdraw(uint256 amount) external; +} + /// @title stBTC /// @notice This contract implements the ERC-4626 tokenized vault standard. By /// staking tBTC, users acquire a liquid staking token called stBTC, @@ -22,9 +27,9 @@ import {ZeroAddress} from "./utils/Errors.sol"; contract stBTC is ERC4626Fees, PausableOwnable { using SafeERC20 for IERC20; - /// Dispatcher contract that routes tBTC from stBTC to a given destination - /// and back. - Dispatcher public dispatcher; + /// Dispatcher contract that routes tBTC from stBTC to a given allocation + /// contract and back. + IDispatcher public dispatcher; /// Address of the treasury wallet, where fees should be transferred to. address public treasury; @@ -124,7 +129,7 @@ contract stBTC is ERC4626Fees, PausableOwnable { /// @notice Updates the dispatcher contract and gives it an unlimited /// allowance to transfer staked tBTC. /// @param newDispatcher Address of the new dispatcher contract. - function updateDispatcher(Dispatcher newDispatcher) external onlyOwner { + function updateDispatcher(IDispatcher newDispatcher) external onlyOwner { if (address(newDispatcher) == address(0)) { revert ZeroAddress(); } @@ -215,14 +220,27 @@ contract stBTC is ERC4626Fees, PausableOwnable { } } + /// @notice Withdraws assets from the vault and transfers them to the + /// receiver. + /// @dev Withdraw unallocated assets first and and if not enough, then pull + /// the assets from the dispatcher. + /// @param assets Amount of assets to withdraw. + /// @param receiver The address to which the assets will be transferred. + /// @param owner The address of the owner of the shares. function withdraw( uint256 assets, address receiver, address owner - ) public override whenNotPaused returns (uint256) { + ) public override returns (uint256) { + if (assets > totalAssets()) { + uint256 missingAmount = assets - totalAssets(); + dispatcher.withdraw(missingAmount); + } + return super.withdraw(assets, receiver, owner); } + // TODO: change this function to pull assets from the dispatcher. function redeem( uint256 shares, address receiver, From fe194470437e03117fb60aa199f188fa95903e05 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 11:23:27 +0200 Subject: [PATCH 27/75] Adding redeeming function Took assets from stBTC first before calling for withdrawal on the Dispatcher contract. --- core/contracts/stBTC.sol | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index c4a862d91..338a4d3c2 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -231,7 +231,7 @@ contract stBTC is ERC4626Fees, PausableOwnable { uint256 assets, address receiver, address owner - ) public override returns (uint256) { + ) public override whenNotPaused returns (uint256) { if (assets > totalAssets()) { uint256 missingAmount = assets - totalAssets(); dispatcher.withdraw(missingAmount); @@ -240,12 +240,23 @@ contract stBTC is ERC4626Fees, PausableOwnable { return super.withdraw(assets, receiver, owner); } - // TODO: change this function to pull assets from the dispatcher. + /// @notice Redeems shares for assets and transfers them to the receiver. + /// @dev Redeem unallocated assets first and and if not enough, then pull + /// the assets from the dispatcher. + /// @param shares Amount of shares to redeem. + /// @param receiver The address to which the assets will be transferred. + /// @param owner The address of the owner of the shares. function redeem( uint256 shares, address receiver, address owner ) public override whenNotPaused returns (uint256) { + uint256 assets = super.previewRedeem(shares); + if (assets > totalAssets()) { + uint256 missingAmount = assets - totalAssets(); + dispatcher.withdraw(missingAmount); + } + return super.redeem(shares, receiver, owner); } From 0e519372a033c01d211046daea1790e64267dce6 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 11:30:31 +0200 Subject: [PATCH 28/75] Updating deloyment scripts --- core/deploy/02_deploy_mezo_allocator.ts | 5 +++-- .../deploy/12_dispatcher_update_maintainer.ts | 19 ------------------- ...=> 14_mezo_allocator_update_withdrawer.ts} | 10 +++++----- 3 files changed, 8 insertions(+), 26 deletions(-) delete mode 100644 core/deploy/12_dispatcher_update_maintainer.ts rename core/deploy/{13_mezo_allocator_update_storage.ts => 14_mezo_allocator_update_withdrawer.ts} (75%) diff --git a/core/deploy/02_deploy_mezo_allocator.ts b/core/deploy/02_deploy_mezo_allocator.ts index 33cee9344..f1002afba 100644 --- a/core/deploy/02_deploy_mezo_allocator.ts +++ b/core/deploy/02_deploy_mezo_allocator.ts @@ -7,11 +7,12 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { deployer } = await getNamedAccounts() const tbtc = await deployments.get("TBTC") + const stbtc = await deployments.get("stBTC") const mezoPortal = await deployments.get("MezoPortal") const mezoAllocator = await deployments.deploy("MezoAllocator", { from: deployer, - args: [mezoPortal.address, tbtc.address], + args: [mezoPortal.address, tbtc.address, stbtc.address], log: true, waitConfirmations: waitConfirmationsNumber(hre), }) @@ -26,4 +27,4 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { export default func func.tags = ["MezoAllocator"] -func.dependencies = ["TBTC", "MezoPortal"] +func.dependencies = ["TBTC", "stBTC", "MezoPortal"] diff --git a/core/deploy/12_dispatcher_update_maintainer.ts b/core/deploy/12_dispatcher_update_maintainer.ts deleted file mode 100644 index 8f616fac0..000000000 --- a/core/deploy/12_dispatcher_update_maintainer.ts +++ /dev/null @@ -1,19 +0,0 @@ -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"] diff --git a/core/deploy/13_mezo_allocator_update_storage.ts b/core/deploy/14_mezo_allocator_update_withdrawer.ts similarity index 75% rename from core/deploy/13_mezo_allocator_update_storage.ts rename to core/deploy/14_mezo_allocator_update_withdrawer.ts index dad7d1a6f..86a178173 100644 --- a/core/deploy/13_mezo_allocator_update_storage.ts +++ b/core/deploy/14_mezo_allocator_update_withdrawer.ts @@ -6,7 +6,7 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { getNamedAccounts, deployments } = hre const { deployer } = await getNamedAccounts() - const stbtc = await deployments.get("stBTC") + const withdrawer = await deployments.get("stBTC") await deployments.execute( "MezoAllocator", @@ -15,12 +15,12 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { log: true, waitConfirmations: waitConfirmationsNumber(hre), }, - "updateTbtcStorage", - stbtc.address, + "updateWithdrawer", + withdrawer.address, ) } export default func -func.tags = ["MezoAllocatorUpdateStorage"] -func.dependencies = ["stBTC", "MezoAllocator"] +func.tags = ["MezoAllocatorUpdateWithdrawer"] +func.dependencies = ["stBTC"] From b7ed6000962a61c7a89ff35e987cde1f682a0dbe Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 11:32:14 +0200 Subject: [PATCH 29/75] Fixing deployment tests --- core/test/Deployment.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/test/Deployment.test.ts b/core/test/Deployment.test.ts index 5b8d42de8..86f79fbd8 100644 --- a/core/test/Deployment.test.ts +++ b/core/test/Deployment.test.ts @@ -29,7 +29,7 @@ describe("Deployment", () => { await loadFixture(fixture)) }) - describe("Acre", () => { + describe("stBTC", () => { describe("constructor", () => { context("when treasury has been set", () => { it("should be set to a treasury address", async () => { @@ -72,12 +72,12 @@ describe("Deployment", () => { }) }) - describe("updateTbtcStorage", () => { - context("when a new stBTC address has been set", () => { - it("should be set to a new stBTC address by the deployment script", async () => { - const actualTbtcStorage = await mezoAllocator.tbtcStorage() + describe("updateWithdrawer", () => { + context("when a new withdrawer has been set", () => { + it("should be set to a new maintainer address", async () => { + const actualWithdrawer = await mezoAllocator.withdrawer() - expect(actualTbtcStorage).to.be.equal(await stbtc.getAddress()) + expect(actualWithdrawer).to.be.equal(await stbtc.getAddress()) }) }) }) From be029b81c562caed77c6db3676430a1a83dd50cf Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 15:02:21 +0200 Subject: [PATCH 30/75] Reading balance of tBTC in stBTC using balanceOf instead of totalAssets totalAssets should be reserved for reading total assets across all the yield sources including stBTC contract. --- core/contracts/stBTC.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index 338a4d3c2..0c0cf946c 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -232,9 +232,9 @@ contract stBTC is ERC4626Fees, PausableOwnable { address receiver, address owner ) public override whenNotPaused returns (uint256) { - if (assets > totalAssets()) { - uint256 missingAmount = assets - totalAssets(); - dispatcher.withdraw(missingAmount); + uint256 currentAssetsBalance = IERC20(asset()).balanceOf(address(this)); + if (assets > currentAssetsBalance) { + dispatcher.withdraw(assets - currentAssetsBalance); } return super.withdraw(assets, receiver, owner); @@ -252,9 +252,9 @@ contract stBTC is ERC4626Fees, PausableOwnable { address owner ) public override whenNotPaused returns (uint256) { uint256 assets = super.previewRedeem(shares); - if (assets > totalAssets()) { - uint256 missingAmount = assets - totalAssets(); - dispatcher.withdraw(missingAmount); + uint256 currentAssetsBalance = IERC20(asset()).balanceOf(address(this)); + if (assets > currentAssetsBalance) { + dispatcher.withdraw(assets - currentAssetsBalance); } return super.redeem(shares, receiver, owner); From f2729c37deff755044787dfb160e4d694f1665e5 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 15:09:15 +0200 Subject: [PATCH 31/75] Removing IERC20 casting for tBTC. It's already declared as IERC20 --- core/contracts/MezoAllocator.sol | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 0e4f5120c..45d60fb8e 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -114,17 +114,13 @@ contract MezoAllocator is Ownable2Step { // slither-disable-next-line reentrancy-no-eth mezoPortal.withdraw(address(tbtc), depositId, depositBalance); } - uint256 addedAmount = IERC20(tbtc).balanceOf(address(stbtc)); + uint256 addedAmount = tbtc.balanceOf(address(stbtc)); // slither-disable-next-line arbitrary-send-erc20 - IERC20(tbtc).safeTransferFrom( - address(stbtc), - address(this), - addedAmount - ); + tbtc.safeTransferFrom(address(stbtc), address(this), addedAmount); - uint96 newDepositAmount = uint96(IERC20(tbtc).balanceOf(address(this))); + uint96 newDepositAmount = uint96(tbtc.balanceOf(address(this))); - IERC20(tbtc).forceApprove(address(mezoPortal), newDepositAmount); + tbtc.forceApprove(address(mezoPortal), newDepositAmount); // 0 denotes no lock period for this deposit. mezoPortal.deposit(address(tbtc), newDepositAmount, 0); uint256 oldDepositId = depositId; @@ -154,7 +150,7 @@ contract MezoAllocator is Ownable2Step { } emit DepositWithdraw(depositId, amount); mezoPortal.withdraw(address(tbtc), depositId, amount); - IERC20(tbtc).safeTransfer(address(stbtc), amount); + tbtc.safeTransfer(address(stbtc), amount); } /// @notice Updates the maintainer address. From d7fafb3d75580e56714a3e012c56ce3963acfcd6 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 15:33:02 +0200 Subject: [PATCH 32/75] Implementing totalAssets for tBTC balance check totalAssets in stBTC should return all the tBTC across all allocations and its own contract totalAssets in MezoAllocator should return all tBTC staked in Mezo Portal --- core/contracts/MezoAllocator.sol | 20 +++++++++++++++++--- core/contracts/interfaces/IDispatcher.sol | 6 ++++++ core/contracts/stBTC.sol | 14 +++++++++----- 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 core/contracts/interfaces/IDispatcher.sol diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 45d60fb8e..70d918f03 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; import {ZeroAddress} from "./utils/Errors.sol"; import "./stBTC.sol"; +import "./interfaces/IDispatcher.sol"; interface IMezoPortal { struct DepositInfo { @@ -52,7 +53,7 @@ contract MezoAllocator is Ownable2Step { ); /// Emitted when tBTC is withdrawn from MezoPortal. - event DepositWithdraw(uint256 indexed depositId, uint96 amount); + event DepositWithdraw(uint256 indexed depositId, uint256 amount); /// @notice Emitted when the maintainer address is updated. event MaintainerUpdated(address indexed maintainer); @@ -141,7 +142,7 @@ contract MezoAllocator is Ownable2Step { /// This function can withdraw partial or a full amount of tBTC from /// MezoPortal for a given deposit id. /// @param amount Amount of tBTC to withdraw. - function withdraw(uint96 amount) external onlyWithdrawer { + function withdraw(uint256 amount) external onlyWithdrawer { uint96 balance = mezoPortal .getDeposit(address(this), address(tbtc), depositId) .balance; @@ -149,7 +150,7 @@ contract MezoAllocator is Ownable2Step { revert InsufficientBalance(); } emit DepositWithdraw(depositId, amount); - mezoPortal.withdraw(address(tbtc), depositId, amount); + mezoPortal.withdraw(address(tbtc), depositId, uint96(amount)); tbtc.safeTransfer(address(stbtc), amount); } @@ -174,4 +175,17 @@ contract MezoAllocator is Ownable2Step { emit WithdrawerUpdated(_withdrawer); } + + /// @notice Returns the total amount of tBTC allocated to MezoPortal. + function totalAssets() + external + view + override + returns (uint256 totalAmount) + { + return + mezoPortal + .getDeposit(address(this), address(tbtc), depositId) + .balance; + } } diff --git a/core/contracts/interfaces/IDispatcher.sol b/core/contracts/interfaces/IDispatcher.sol new file mode 100644 index 000000000..5a8abbe2c --- /dev/null +++ b/core/contracts/interfaces/IDispatcher.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.8.21; + +interface IDispatcher { + function withdraw(uint256 amount) external; + function totalAssets() external view returns (uint256); +} diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index 0c0cf946c..dce7e4073 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -6,13 +6,9 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "./Dispatcher.sol"; import "./PausableOwnable.sol"; import "./lib/ERC4626Fees.sol"; +import "./interfaces/IDispatcher.sol"; import {ZeroAddress} from "./utils/Errors.sol"; -// slither-disable-next-line missing-inheritance -interface IDispatcher { - function withdraw(uint256 amount) external; -} - /// @title stBTC /// @notice This contract implements the ERC-4626 tokenized vault standard. By /// staking tBTC, users acquire a liquid staking token called stBTC, @@ -176,6 +172,14 @@ contract stBTC is ERC4626Fees, PausableOwnable { emit ExitFeeBasisPointsUpdated(newExitFeeBasisPoints); } + /// @notice Returns the total amount of assets held by the vault across all + /// allocations and this contract. + function totalAssets() public view override returns (uint256) { + uint256 totalAmount = IERC20(asset()).balanceOf(address(this)); + totalAmount += dispatcher.totalAssets(); + return totalAmount; + } + /// @notice Mints shares to receiver by depositing exactly amount of /// tBTC tokens. /// @dev Takes into account a deposit parameter, minimum deposit amount, From 4a3eb9d1fd791c5a03570ceac3ae0377f4f2cdcc Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 15:41:25 +0200 Subject: [PATCH 33/75] Replacing withdrawer role with hardcoded stBTC contract --- core/contracts/MezoAllocator.sol | 27 ++----------------- .../14_mezo_allocator_update_withdrawer.ts | 26 ------------------ core/test/Deployment.test.ts | 10 ------- 3 files changed, 2 insertions(+), 61 deletions(-) delete mode 100644 core/deploy/14_mezo_allocator_update_withdrawer.ts diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 70d918f03..b6c82f749 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -39,8 +39,6 @@ contract MezoAllocator is Ownable2Step { stBTC public immutable stbtc; /// @notice Maintainer address which can trigger deposit flow. address public maintainer; - /// @notice Address that can withdraw tBTC from Mezo Portal. - address public withdrawer; /// @notice keeps track of the latest deposit ID assigned in Mezo Portal. uint256 public depositId; @@ -58,9 +56,6 @@ contract MezoAllocator is Ownable2Step { /// @notice Emitted when the maintainer address is updated. event MaintainerUpdated(address indexed maintainer); - /// @notice Emitted when the withdrawer address is updated. - event WithdrawerUpdated(address indexed withdrawer); - /// @notice Reverts if the caller is not an authorized account. error NotAuthorized(); @@ -74,13 +69,6 @@ contract MezoAllocator is Ownable2Step { _; } - modifier onlyWithdrawer() { - if (msg.sender != withdrawer) { - revert NotAuthorized(); - } - _; - } - /// @notice Initializes the MezoAllocator contract. /// @param _mezoPortal Address of the MezoPortal contract. /// @param _tbtc Address of the tBTC token contract. @@ -142,7 +130,8 @@ contract MezoAllocator is Ownable2Step { /// This function can withdraw partial or a full amount of tBTC from /// MezoPortal for a given deposit id. /// @param amount Amount of tBTC to withdraw. - function withdraw(uint256 amount) external onlyWithdrawer { + function withdraw(uint256 amount) external { + if (msg.sender != address(stbtc)) revert NotAuthorized(); uint96 balance = mezoPortal .getDeposit(address(this), address(tbtc), depositId) .balance; @@ -165,22 +154,10 @@ contract MezoAllocator is Ownable2Step { emit MaintainerUpdated(_maintainer); } - /// @notice Updates the withdrawer address. - /// @param _withdrawer Address of the new withdrawer. - function updateWithdrawer(address _withdrawer) external onlyOwner { - if (_withdrawer == address(0)) { - revert ZeroAddress(); - } - withdrawer = _withdrawer; - - emit WithdrawerUpdated(_withdrawer); - } - /// @notice Returns the total amount of tBTC allocated to MezoPortal. function totalAssets() external view - override returns (uint256 totalAmount) { return diff --git a/core/deploy/14_mezo_allocator_update_withdrawer.ts b/core/deploy/14_mezo_allocator_update_withdrawer.ts deleted file mode 100644 index 86a178173..000000000 --- a/core/deploy/14_mezo_allocator_update_withdrawer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { HardhatRuntimeEnvironment } from "hardhat/types" -import type { DeployFunction } from "hardhat-deploy/types" -import { waitConfirmationsNumber } from "../helpers/deployment" - -const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const { getNamedAccounts, deployments } = hre - const { deployer } = await getNamedAccounts() - - const withdrawer = await deployments.get("stBTC") - - await deployments.execute( - "MezoAllocator", - { - from: deployer, - log: true, - waitConfirmations: waitConfirmationsNumber(hre), - }, - "updateWithdrawer", - withdrawer.address, - ) -} - -export default func - -func.tags = ["MezoAllocatorUpdateWithdrawer"] -func.dependencies = ["stBTC"] diff --git a/core/test/Deployment.test.ts b/core/test/Deployment.test.ts index 86f79fbd8..6571063bc 100644 --- a/core/test/Deployment.test.ts +++ b/core/test/Deployment.test.ts @@ -71,15 +71,5 @@ describe("Deployment", () => { }) }) }) - - describe("updateWithdrawer", () => { - context("when a new withdrawer has been set", () => { - it("should be set to a new maintainer address", async () => { - const actualWithdrawer = await mezoAllocator.withdrawer() - - expect(actualWithdrawer).to.be.equal(await stbtc.getAddress()) - }) - }) - }) }) }) From 98c6f0098c95def864797713c40f42a314c44808 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 16:14:30 +0200 Subject: [PATCH 34/75] Adding a list of maintainers An owner can approve multiple maintainers that can trigger funds allocation. --- core/contracts/MezoAllocator.sol | 72 +++++++++++++++-------- core/contracts/interfaces/IDispatcher.sol | 2 + 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index b6c82f749..0c808f331 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -31,39 +31,42 @@ interface IMezoPortal { contract MezoAllocator is Ownable2Step { using SafeERC20 for IERC20; - /// Address of the MezoPortal contract. + /// @notice Address of the MezoPortal contract. IMezoPortal public immutable mezoPortal; - /// tBTC token contract. + /// @notice tBTC token contract. IERC20 public immutable tbtc; - /// stBTC token vault contract. + /// @notice stBTC token vault contract. stBTC public immutable stbtc; - /// @notice Maintainer address which can trigger deposit flow. - address public maintainer; + /// @notice Keeps track of the addresses that are allowed to trigger deposit + /// allocations. + mapping(address => bool) public isMaintainer; + /// @notice List of maintainers. + address[] public maintainers; /// @notice keeps track of the latest deposit ID assigned in Mezo Portal. uint256 public depositId; - /// Emitted when tBTC is deposited to MezoPortal. + /// @notice Emitted when tBTC is deposited to MezoPortal. event DepositAllocated( uint256 indexed oldDepositId, uint256 indexed newDepositId, uint256 addedAmount, uint256 newDepositAmount ); - - /// Emitted when tBTC is withdrawn from MezoPortal. + /// @notice Emitted when tBTC is withdrawn from MezoPortal. event DepositWithdraw(uint256 indexed depositId, uint256 amount); - /// @notice Emitted when the maintainer address is updated. - event MaintainerUpdated(address indexed maintainer); - + event MaintainerAdded(address indexed maintainer); + /// @notice Emitted when the maintainer address is updated. + event MaintainerRemoved(address indexed maintainer); /// @notice Reverts if the caller is not an authorized account. error NotAuthorized(); - /// @notice Reverts if the caller tries to withdraw more tBTC than available. error InsufficientBalance(); + /// @notice Reverts if the caller is not a maintainer. + error NotMaintainer(); - modifier onlyMaintainerAndOwner() { - if (msg.sender != maintainer && owner() != msg.sender) { + modifier onlyMaintainer() { + if (!isMaintainer[msg.sender]) { revert NotAuthorized(); } _; @@ -93,8 +96,8 @@ contract MezoAllocator is Ownable2Step { /// before a new deposit with added amount is created. This mimics a /// "top up" functionality with the difference that a new deposit id /// is created and the previous deposit id is no longer in use. - /// @dev This function can be invoked periodically by a bot. - function allocate() external onlyMaintainerAndOwner { + /// @dev This function can be invoked periodically by a maintainer. + function allocate() external onlyMaintainer { uint96 depositBalance = mezoPortal .getDeposit(address(this), address(tbtc), depositId) .balance; @@ -144,22 +147,39 @@ contract MezoAllocator is Ownable2Step { } /// @notice Updates the maintainer address. - /// @param _maintainer Address of the new maintainer. - function updateMaintainer(address _maintainer) external onlyOwner { - if (_maintainer == address(0)) { + /// @param maintainerToAdd Address of the new maintainer. + function addMaintainer(address maintainerToAdd) external onlyOwner { + if (maintainerToAdd == address(0)) { revert ZeroAddress(); } - maintainer = _maintainer; + maintainers.push(maintainerToAdd); + isMaintainer[maintainerToAdd] = true; + + emit MaintainerAdded(maintainerToAdd); + } + + /// @notice Removes the maintainer address. + /// @param maintainerToRemove Address of the maintainer to remove. + function removeMaintainer(address maintainerToRemove) external onlyOwner { + if (!isMaintainer[maintainerToRemove]) { + revert NotMaintainer(); + } + delete (isMaintainer[maintainerToRemove]); + + for (uint256 i = 0; i < maintainers.length; i++) { + if (maintainers[i] == maintainerToRemove) { + maintainers[i] = maintainers[maintainers.length - 1]; + // slither-disable-next-line costly-loop + maintainers.pop(); + break; + } + } - emit MaintainerUpdated(_maintainer); + emit MaintainerRemoved(maintainerToRemove); } /// @notice Returns the total amount of tBTC allocated to MezoPortal. - function totalAssets() - external - view - returns (uint256 totalAmount) - { + function totalAssets() external view returns (uint256 totalAmount) { return mezoPortal .getDeposit(address(this), address(tbtc), depositId) diff --git a/core/contracts/interfaces/IDispatcher.sol b/core/contracts/interfaces/IDispatcher.sol index 5a8abbe2c..af28b1b10 100644 --- a/core/contracts/interfaces/IDispatcher.sol +++ b/core/contracts/interfaces/IDispatcher.sol @@ -1,5 +1,7 @@ +// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; +// slither-disable-next-line missing-inheritance interface IDispatcher { function withdraw(uint256 amount) external; function totalAssets() external view returns (uint256); From ba702cb1dd2bc6204d63b03fa6989ec1a61c262e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 16:15:36 +0200 Subject: [PATCH 35/75] Adding getDeposit function to MezoPortalStub --- core/contracts/test/MezoPortalStub.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/contracts/test/MezoPortalStub.sol b/core/contracts/test/MezoPortalStub.sol index bdeaae0ee..d38b86de8 100644 --- a/core/contracts/test/MezoPortalStub.sol +++ b/core/contracts/test/MezoPortalStub.sol @@ -21,4 +21,15 @@ contract MezoPortalStub { depositCount++; IERC20(token).safeTransferFrom(msg.sender, address(this), amount); } + + function getDeposit( + address depositor, + address token, + uint256 depositId + ) external view returns (uint96 balance, uint256 unlockAt) { + return ( + uint96(IERC20(token).balanceOf(address(this))), + block.timestamp + ); + } } From 1a5dc7dbeff3c0658aacd31569b32b21e5fadeb2 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 21:21:05 +0200 Subject: [PATCH 36/75] Fixing shares->assets by using convertToAssets Accounted for the fees that will be collected by Acre. We need to use convertToAssests instead of previewRedeem function. --- core/contracts/stBTC.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index dce7e4073..8b91845b5 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -255,7 +255,7 @@ contract stBTC is ERC4626Fees, PausableOwnable { address receiver, address owner ) public override whenNotPaused returns (uint256) { - uint256 assets = super.previewRedeem(shares); + uint256 assets = convertToAssets(shares); uint256 currentAssetsBalance = IERC20(asset()).balanceOf(address(this)); if (assets > currentAssetsBalance) { dispatcher.withdraw(assets - currentAssetsBalance); From 1f90524ab7696c235123f252279c1621cc47f535 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 21:26:49 +0200 Subject: [PATCH 37/75] Removing balance check for while withdrawing We can also rely on MezoPortal check that will also include balance validation against the amount to be withdrawn. --- core/contracts/MezoAllocator.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 0c808f331..b8ce32d6e 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -135,12 +135,6 @@ contract MezoAllocator is Ownable2Step { /// @param amount Amount of tBTC to withdraw. function withdraw(uint256 amount) external { if (msg.sender != address(stbtc)) revert NotAuthorized(); - uint96 balance = mezoPortal - .getDeposit(address(this), address(tbtc), depositId) - .balance; - if (amount > balance) { - revert InsufficientBalance(); - } emit DepositWithdraw(depositId, amount); mezoPortal.withdraw(address(tbtc), depositId, uint96(amount)); tbtc.safeTransfer(address(stbtc), amount); From 791b10cfa44bb39c729f6c545ea675d3885f815b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Apr 2024 21:30:27 +0200 Subject: [PATCH 38/75] Inlining math for totalAssets() --- core/contracts/stBTC.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index 8b91845b5..d97aadd8b 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -175,9 +175,8 @@ contract stBTC is ERC4626Fees, PausableOwnable { /// @notice Returns the total amount of assets held by the vault across all /// allocations and this contract. function totalAssets() public view override returns (uint256) { - uint256 totalAmount = IERC20(asset()).balanceOf(address(this)); - totalAmount += dispatcher.totalAssets(); - return totalAmount; + return + IERC20(asset()).balanceOf(address(this)) + dispatcher.totalAssets(); } /// @notice Mints shares to receiver by depositing exactly amount of From ace0dbf2bb278da28d2dc16634e16a28d46e92b3 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 9 Apr 2024 11:44:47 +0200 Subject: [PATCH 39/75] MezoAllocator inheriting IDispatcher interface --- core/contracts/MezoAllocator.sol | 2 +- core/contracts/interfaces/IDispatcher.sol | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index b8ce32d6e..98f6b43ce 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -28,7 +28,7 @@ interface IMezoPortal { } /// @notice MezoAllocator routes tBTC to/from MezoPortal. -contract MezoAllocator is Ownable2Step { +contract MezoAllocator is IDispatcher, Ownable2Step { using SafeERC20 for IERC20; /// @notice Address of the MezoPortal contract. diff --git a/core/contracts/interfaces/IDispatcher.sol b/core/contracts/interfaces/IDispatcher.sol index af28b1b10..0490db373 100644 --- a/core/contracts/interfaces/IDispatcher.sol +++ b/core/contracts/interfaces/IDispatcher.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -// slither-disable-next-line missing-inheritance interface IDispatcher { function withdraw(uint256 amount) external; function totalAssets() external view returns (uint256); From c777719c623acf1c6f6aabd981bed4a442db9f97 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 9 Apr 2024 14:07:36 +0200 Subject: [PATCH 40/75] Tracking of the deposit balance locally in Mezo Allocator Storing Mezo Portal deposit balance locally instead of calling a view function on Mezo Portal. Added docs around IMezoPortal interface --- core/contracts/MezoAllocator.sol | 50 +++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 98f6b43ce..91fa836c9 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -8,18 +8,49 @@ import {ZeroAddress} from "./utils/Errors.sol"; import "./stBTC.sol"; import "./interfaces/IDispatcher.sol"; +/// @title IMezoPortal +/// @dev Interface for the Mezo's Portal contract. interface IMezoPortal { + /// @notice DepositInfo keeps track of the deposit balance and unlock time. + /// Each deposit is tracked separately and associated with a specific + /// token. Some tokens can be deposited but can not be locked - in + /// that case the unlockAt is the block timestamp of when the deposit + /// was created. The same is true for tokens that can be locked but + /// the depositor decided not to lock them. struct DepositInfo { uint96 balance; uint32 unlockAt; } + /// @notice Deposit and optionally lock tokens for the given period. + /// @dev Lock period will be normalized to weeks. If non-zero, it must not + /// be shorter than the minimum lock period and must not be longer than + /// the maximum lock period. + /// @param token token address to deposit + /// @param amount amount of tokens to deposit + /// @param lockPeriod lock period in seconds, 0 to not lock the deposit function deposit(address token, uint96 amount, uint32 lockPeriod) external; + /// @notice Withdraw deposited tokens. + /// Deposited lockable tokens can be withdrawn at any time if + /// there is no lock set on the deposit or the lock period has passed. + /// There is no way to withdraw locked deposit. Tokens that are not + /// lockable can be withdrawn at any time. Deposit can be withdrawn + /// partially. + /// @param token deposited token address + /// @param depositId id of the deposit + /// @param amount amount of the token to be withdrawn from the deposit function withdraw(address token, uint256 depositId, uint96 amount) external; + /// @notice The number of deposits created. Includes the deposits that + /// were fully withdrawn. This is also the identifier of the most + /// recently created deposit. function depositCount() external view returns (uint256); + /// @notice Get the balance and unlock time of a given deposit. + /// @param depositor depositor address + /// @param token token address to get the balance + /// @param depositId id of the deposit function getDeposit( address depositor, address token, @@ -44,6 +75,8 @@ contract MezoAllocator is IDispatcher, Ownable2Step { address[] public maintainers; /// @notice keeps track of the latest deposit ID assigned in Mezo Portal. uint256 public depositId; + /// @notice Keeps track of the total amount of tBTC allocated to MezoPortal. + uint96 public depositBalance; /// @notice Emitted when tBTC is deposited to MezoPortal. event DepositAllocated( @@ -98,9 +131,6 @@ contract MezoAllocator is IDispatcher, Ownable2Step { /// is created and the previous deposit id is no longer in use. /// @dev This function can be invoked periodically by a maintainer. function allocate() external onlyMaintainer { - uint96 depositBalance = mezoPortal - .getDeposit(address(this), address(tbtc), depositId) - .balance; if (depositBalance > 0) { // Free all Acre's tBTC from MezoPortal before creating a new deposit. // slither-disable-next-line reentrancy-no-eth @@ -110,11 +140,11 @@ contract MezoAllocator is IDispatcher, Ownable2Step { // slither-disable-next-line arbitrary-send-erc20 tbtc.safeTransferFrom(address(stbtc), address(this), addedAmount); - uint96 newDepositAmount = uint96(tbtc.balanceOf(address(this))); + depositBalance = uint96(tbtc.balanceOf(address(this))); - tbtc.forceApprove(address(mezoPortal), newDepositAmount); + tbtc.forceApprove(address(mezoPortal), depositBalance); // 0 denotes no lock period for this deposit. - mezoPortal.deposit(address(tbtc), newDepositAmount, 0); + mezoPortal.deposit(address(tbtc), depositBalance, 0); uint256 oldDepositId = depositId; // MezoPortal doesn't return depositId, so we have to read depositCounter // which assigns depositId to the current deposit. @@ -125,7 +155,7 @@ contract MezoAllocator is IDispatcher, Ownable2Step { oldDepositId, depositId, addedAmount, - newDepositAmount + depositBalance ); } @@ -136,6 +166,7 @@ contract MezoAllocator is IDispatcher, Ownable2Step { function withdraw(uint256 amount) external { if (msg.sender != address(stbtc)) revert NotAuthorized(); emit DepositWithdraw(depositId, amount); + depositBalance -= uint96(amount); mezoPortal.withdraw(address(tbtc), depositId, uint96(amount)); tbtc.safeTransfer(address(stbtc), amount); } @@ -174,9 +205,6 @@ contract MezoAllocator is IDispatcher, Ownable2Step { /// @notice Returns the total amount of tBTC allocated to MezoPortal. function totalAssets() external view returns (uint256 totalAmount) { - return - mezoPortal - .getDeposit(address(this), address(tbtc), depositId) - .balance; + return depositBalance; } } From 4de3f2d6d670dcd961e4f5151cef88cc51970917 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 9 Apr 2024 14:16:07 +0200 Subject: [PATCH 41/75] DepositWithdraw -> DepositWithdrawn event --- core/contracts/MezoAllocator.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 91fa836c9..22c50f7ed 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -86,7 +86,7 @@ contract MezoAllocator is IDispatcher, Ownable2Step { uint256 newDepositAmount ); /// @notice Emitted when tBTC is withdrawn from MezoPortal. - event DepositWithdraw(uint256 indexed depositId, uint256 amount); + event DepositWithdrawn(uint256 indexed depositId, uint256 amount); /// @notice Emitted when the maintainer address is updated. event MaintainerAdded(address indexed maintainer); /// @notice Emitted when the maintainer address is updated. @@ -165,7 +165,7 @@ contract MezoAllocator is IDispatcher, Ownable2Step { /// @param amount Amount of tBTC to withdraw. function withdraw(uint256 amount) external { if (msg.sender != address(stbtc)) revert NotAuthorized(); - emit DepositWithdraw(depositId, amount); + emit DepositWithdrawn(depositId, amount); depositBalance -= uint96(amount); mezoPortal.withdraw(address(tbtc), depositId, uint96(amount)); tbtc.safeTransfer(address(stbtc), amount); From 67da3b907742a1f4402edd3d07d001ab6648ff81 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 9 Apr 2024 14:16:40 +0200 Subject: [PATCH 42/75] Removing unused event --- core/contracts/MezoAllocator.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 22c50f7ed..8a0f54e48 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -93,8 +93,6 @@ contract MezoAllocator is IDispatcher, Ownable2Step { event MaintainerRemoved(address indexed maintainer); /// @notice Reverts if the caller is not an authorized account. error NotAuthorized(); - /// @notice Reverts if the caller tries to withdraw more tBTC than available. - error InsufficientBalance(); /// @notice Reverts if the caller is not a maintainer. error NotMaintainer(); From 0aa3357e1dd48b49fba7542162b9439d3a8245ee Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 9 Apr 2024 14:18:00 +0200 Subject: [PATCH 43/75] Adding validation for stBTC address not zero --- core/contracts/MezoAllocator.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 8a0f54e48..0da91fd48 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -117,6 +117,9 @@ contract MezoAllocator is IDispatcher, Ownable2Step { if (address(_tbtc) == address(0)) { revert ZeroAddress(); } + if (address(_stbtc) == address(0)) { + revert ZeroAddress(); + } mezoPortal = IMezoPortal(_mezoPortal); tbtc = _tbtc; stbtc = _stbtc; From 5f9fa2f0a273b62c53d2b9ba9e25ae4871002d67 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 9 Apr 2024 14:55:51 +0200 Subject: [PATCH 44/75] Renaming errors around new maintainers NotMaintainer -> MaintainerNotRegistered AlreadyMaintainer -> MaintainerAlreadyRegistered --- core/contracts/MezoAllocator.sol | 14 +++++++++++--- core/contracts/interfaces/IDispatcher.sol | 5 +++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 0da91fd48..e61117a7c 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -94,7 +94,9 @@ contract MezoAllocator is IDispatcher, Ownable2Step { /// @notice Reverts if the caller is not an authorized account. error NotAuthorized(); /// @notice Reverts if the caller is not a maintainer. - error NotMaintainer(); + error MaintainerNotRegistered(); + /// @notice Reverts if the caller is already a maintainer. + error MaintainerAlreadyRegistered(); modifier onlyMaintainer() { if (!isMaintainer[msg.sender]) { @@ -137,12 +139,14 @@ contract MezoAllocator is IDispatcher, Ownable2Step { // slither-disable-next-line reentrancy-no-eth mezoPortal.withdraw(address(tbtc), depositId, depositBalance); } + + // Fetch unallocated tBTC from stBTC contract. uint256 addedAmount = tbtc.balanceOf(address(stbtc)); // slither-disable-next-line arbitrary-send-erc20 tbtc.safeTransferFrom(address(stbtc), address(this), addedAmount); + // Create a new deposit in the MezoPortal. depositBalance = uint96(tbtc.balanceOf(address(this))); - tbtc.forceApprove(address(mezoPortal), depositBalance); // 0 denotes no lock period for this deposit. mezoPortal.deposit(address(tbtc), depositBalance, 0); @@ -166,6 +170,7 @@ contract MezoAllocator is IDispatcher, Ownable2Step { /// @param amount Amount of tBTC to withdraw. function withdraw(uint256 amount) external { if (msg.sender != address(stbtc)) revert NotAuthorized(); + emit DepositWithdrawn(depositId, amount); depositBalance -= uint96(amount); mezoPortal.withdraw(address(tbtc), depositId, uint96(amount)); @@ -178,6 +183,9 @@ contract MezoAllocator is IDispatcher, Ownable2Step { if (maintainerToAdd == address(0)) { revert ZeroAddress(); } + if (isMaintainer[maintainerToAdd]) { + revert MaintainerAlreadyRegistered(); + } maintainers.push(maintainerToAdd); isMaintainer[maintainerToAdd] = true; @@ -188,7 +196,7 @@ contract MezoAllocator is IDispatcher, Ownable2Step { /// @param maintainerToRemove Address of the maintainer to remove. function removeMaintainer(address maintainerToRemove) external onlyOwner { if (!isMaintainer[maintainerToRemove]) { - revert NotMaintainer(); + revert MaintainerNotRegistered(); } delete (isMaintainer[maintainerToRemove]); diff --git a/core/contracts/interfaces/IDispatcher.sol b/core/contracts/interfaces/IDispatcher.sol index 0490db373..4c762240c 100644 --- a/core/contracts/interfaces/IDispatcher.sol +++ b/core/contracts/interfaces/IDispatcher.sol @@ -1,7 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; +/// @title IDispatcher +/// @notice Interface for the Dispatcher contract. interface IDispatcher { + /// @notice Withdraw assets from the Dispatcher. function withdraw(uint256 amount) external; + + /// @notice Returns the total amount of assets held by the Dispatcher. function totalAssets() external view returns (uint256); } From 9b1f0697195ea164aae855a74f773dd96ef0b653 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 9 Apr 2024 16:48:50 +0200 Subject: [PATCH 45/75] Pulling assets from Mezo Portal first We need to pull funds from Mezo Portal first before we can update the balance. This is because if amount > then the balance, it should revert. Otherwise, we'll get the underflow error. --- core/contracts/MezoAllocator.sol | 3 ++- core/test/BitcoinRedeemer.test.ts | 19 ++++--------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index e61117a7c..ebc46b677 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -172,8 +172,9 @@ contract MezoAllocator is IDispatcher, Ownable2Step { if (msg.sender != address(stbtc)) revert NotAuthorized(); emit DepositWithdrawn(depositId, amount); - depositBalance -= uint96(amount); mezoPortal.withdraw(address(tbtc), depositId, uint96(amount)); + // slither-disable-next-line reentrancy-benign + depositBalance -= uint96(amount); tbtc.safeTransfer(address(stbtc), amount); } diff --git a/core/test/BitcoinRedeemer.test.ts b/core/test/BitcoinRedeemer.test.ts index 2874cf9cd..396391945 100644 --- a/core/test/BitcoinRedeemer.test.ts +++ b/core/test/BitcoinRedeemer.test.ts @@ -133,12 +133,7 @@ describe("BitcoinRedeemer", () => { to1e18(1), tbtcRedemptionData.redemptionData, ), - ) - .to.be.revertedWithCustomError( - stbtc, - "ERC4626ExceededMaxRedeem", - ) - .withArgs(await depositor.getAddress(), to1e18(1), 0) + ).to.be.revertedWithCustomError(stbtc, "ERC20InsufficientBalance") }) }) @@ -170,16 +165,10 @@ describe("BitcoinRedeemer", () => { amountToRedeem, tbtcRedemptionData.redemptionData, ), + ).to.be.revertedWithCustomError( + stbtc, + "ERC20InsufficientBalance", ) - .to.be.revertedWithCustomError( - stbtc, - "ERC4626ExceededMaxRedeem", - ) - .withArgs( - await depositor.getAddress(), - amountToRedeem, - depositAmount, - ) }) }) From 39bc4176baf13687f50ad1e98e9efca61ee9f313 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 9 Apr 2024 16:52:19 +0200 Subject: [PATCH 46/75] Fixing and adding some of the tests for MezoAllocator --- .../12_mezo_allocator_update_maintainer.ts | 2 +- core/test/Deployment.test.ts | 4 +- core/test/MezoAllocator.test.ts | 151 +++++------------- 3 files changed, 39 insertions(+), 118 deletions(-) diff --git a/core/deploy/12_mezo_allocator_update_maintainer.ts b/core/deploy/12_mezo_allocator_update_maintainer.ts index 4e1f5fa18..aa7cec484 100644 --- a/core/deploy/12_mezo_allocator_update_maintainer.ts +++ b/core/deploy/12_mezo_allocator_update_maintainer.ts @@ -13,7 +13,7 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { log: true, waitConfirmations: waitConfirmationsNumber(hre), }, - "updateMaintainer", + "addMaintainer", maintainer, ) } diff --git a/core/test/Deployment.test.ts b/core/test/Deployment.test.ts index 6571063bc..d318d9652 100644 --- a/core/test/Deployment.test.ts +++ b/core/test/Deployment.test.ts @@ -65,9 +65,9 @@ describe("Deployment", () => { describe("updateMaintainer", () => { context("when a new maintainer has been set", () => { it("should be set to a new maintainer address", async () => { - const actualMaintainer = await mezoAllocator.maintainer() + const isMaintainer = await mezoAllocator.isMaintainer(maintainer) - expect(actualMaintainer).to.be.equal(await maintainer.getAddress()) + expect(isMaintainer).to.be.equal(true) }) }) }) diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index 7fb25c48a..e12c90eeb 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -39,167 +39,88 @@ describe("MezoAllocator", () => { let mezoAllocator: MezoAllocator let mezoPortal: IMezoPortal - let governance: HardhatEthersSigner let thirdParty: HardhatEthersSigner let maintainer: HardhatEthersSigner before(async () => { - ;({ - governance, - thirdParty, - maintainer, - tbtc, - stbtc, - mezoAllocator, - mezoPortal, - } = await loadFixture(fixture)) + ;({ thirdParty, maintainer, tbtc, stbtc, mezoAllocator, mezoPortal } = + await loadFixture(fixture)) }) describe("allocate", () => { beforeAfterSnapshotWrapper() - context("when the caller is not an owner", () => { + context("when a caller is not a maintainer", () => { it("should revert", async () => { await expect( - mezoAllocator.connect(thirdParty).allocate(to1e18(1)), + mezoAllocator.connect(thirdParty).allocate(), ).to.be.revertedWithCustomError(mezoAllocator, "NotAuthorized") }) }) - context("when the caller is an owner", () => { - it("should not revert", async () => { - await expect( - mezoAllocator.connect(governance).allocate(to1e18(1)), - ).to.not.be.revertedWithCustomError(mezoAllocator, "NotAuthorized") - }) - }) - context("when the caller is maintainer", () => { - context("when first deposit is made", () => { + context("when a first deposit is made", () => { let tx: ContractTransactionResponse before(async () => { - await tbtc.mint(await stbtc.getAddress(), to1e18(1)) - await mezoAllocator - .connect(governance) - .updateMaintainer(maintainer.address) - - tx = await mezoAllocator.connect(maintainer).allocate(to1e18(1)) + await tbtc.mint(await stbtc.getAddress(), to1e18(6)) + tx = await mezoAllocator.connect(maintainer).allocate() }) it("should deposit and transfer tBTC to Mezo Portal", async () => { - expect( - await tbtc.balanceOf(await mezoAllocator.getAddress()), - ).to.equal(0) expect(await tbtc.balanceOf(await mezoPortal.getAddress())).to.equal( - to1e18(1), + to1e18(6), ) }) - it("should set deposit balance", async () => { - const deposit = await mezoAllocator.depositInfo() - expect(deposit.balance).to.equal(to1e18(1)) - }) - - it("should set creation timestamp", async () => { - const deposit = await mezoAllocator.depositInfo() - const dateTime = new Date() - // Check if the block timestamp is within 60 seconds of the current - // test time - expect(deposit.createdAt).to.be.closeTo( - String(dateTime.valueOf()).slice(0, -3), - 60, - ) + it("should not store any tBTC in Mezo Allocator", async () => { + expect( + await tbtc.balanceOf(await mezoAllocator.getAddress()), + ).to.equal(0) }) - it("should set unlocking timestamp", async () => { - const deposit = await mezoAllocator.depositInfo() - const dateTime = new Date() - // Check if the block timestamp is within 60 seconds of the current - // test time - expect(deposit.unlockAt).to.be.closeTo( - String(dateTime.valueOf()).slice(0, -3), - 60, - ) + it("should increment the deposit id", async () => { + const actualDepositId = await mezoAllocator.depositId() + expect(actualDepositId).to.equal(1) }) - it("should emit Deposit event", async () => { + it("should emit DepositAllocated event", async () => { await expect(tx) .to.emit(mezoAllocator, "DepositAllocated") - .withArgs(0, 1, to1e18(1)) + .withArgs(0, 1, to1e18(6), to1e18(6)) }) }) - context("when second deposit is made", () => { + context("when a second deposit is made", () => { + let tx: ContractTransactionResponse + before(async () => { await tbtc.mint(await stbtc.getAddress(), to1e18(5)) - await mezoAllocator - .connect(governance) - .updateMaintainer(maintainer.address) - await mezoAllocator.connect(maintainer).allocate(to1e18(5)) + tx = await mezoAllocator.connect(maintainer).allocate() }) it("should increment the deposit id", async () => { - const depositInfo = await mezoAllocator.depositInfo() - expect(depositInfo.id).to.equal(2) + const actualDepositId = await mezoAllocator.depositId() + expect(actualDepositId).to.equal(2) }) - it("should populate deposit balance", async () => { - const deposit = await mezoAllocator.depositInfo() - // 1 + 5 = 6 - expect(deposit.balance).to.equal(to1e18(6)) + it("should emit DepositAllocated event", async () => { + await expect(tx) + .to.emit(mezoAllocator, "DepositAllocated") + .withArgs(1, 2, to1e18(5), to1e18(11)) }) - }) - }) - }) - - describe("updateTbtcStorage", () => { - context("when the caller is not an owner", () => { - it("should revert", async () => { - await expect( - mezoAllocator - .connect(thirdParty) - .updateTbtcStorage(thirdParty.address), - ).to.be.revertedWithCustomError( - mezoAllocator, - "OwnableUnauthorizedAccount", - ) - }) - }) - - context("when the caller is an owner", () => { - it("should not revert", async () => { - await mezoAllocator - .connect(governance) - .updateTbtcStorage(thirdParty.address) - const tbtcStorageAddress = await mezoAllocator.tbtcStorage() - expect(tbtcStorageAddress).to.equal(thirdParty.address) - }) - }) - }) - describe("updateMaintainer", () => { - context("when the caller is not an owner", () => { - it("should revert", async () => { - await expect( - mezoAllocator - .connect(thirdParty) - .updateMaintainer(thirdParty.address), - ).to.be.revertedWithCustomError( - mezoAllocator, - "OwnableUnauthorizedAccount", - ) - }) - }) + it("should deposit and transfer tBTC to Mezo Portal", async () => { + expect(await tbtc.balanceOf(await mezoPortal.getAddress())).to.equal( + to1e18(11), + ) + }) - context("when the caller is an owner", () => { - it("should not revert", async () => { - await mezoAllocator - .connect(governance) - .updateMaintainer(thirdParty.address) - const maintainerAddress = await mezoAllocator.maintainer() - expect(maintainerAddress).to.equal(thirdParty.address) + it("should increase tracked deposit balance amount", async () => { + const depositBalance = await mezoAllocator.depositBalance() + expect(depositBalance).to.equal(to1e18(11)) + }) }) }) }) From 4036ea904dbe321f95b885feba3715763756efe3 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 9 Apr 2024 15:59:09 +0200 Subject: [PATCH 47/75] Replace stake naming with deposit We decided not to call the operation stake in the dapp, so for consistency we align the naming here. It makes it more consistent also with the AbstractBitcoinDepositor as we call the operation deposit there. --- core/contracts/BitcoinDepositor.sol | 189 ++++++++++++++-------------- 1 file changed, 93 insertions(+), 96 deletions(-) diff --git a/core/contracts/BitcoinDepositor.sol b/core/contracts/BitcoinDepositor.sol index c0de3cd37..0576ff3da 100644 --- a/core/contracts/BitcoinDepositor.sol +++ b/core/contracts/BitcoinDepositor.sol @@ -12,13 +12,13 @@ import "@keep-network/tbtc-v2/contracts/integrator/AbstractTBTCDepositor.sol"; import {stBTC} from "./stBTC.sol"; /// @title Bitcoin Depositor contract. -/// @notice The contract integrates Acre staking with tBTC minting. -/// User who wants to stake BTC in Acre should submit a Bitcoin transaction +/// @notice The contract integrates Acre depositing with tBTC minting. +/// User who wants to deposit BTC in Acre should submit a Bitcoin transaction /// to the most recently created off-chain ECDSA wallets of the tBTC Bridge /// using pay-to-script-hash (P2SH) or pay-to-witness-script-hash (P2WSH) /// containing hashed information about this Depositor contract address, -/// and staker's Ethereum address. -/// Then, the staker initiates tBTC minting by revealing their Ethereum +/// and deposit owner's Ethereum address. +/// Then, the deposit owner initiates tBTC minting by revealing their Ethereum /// address along with their deposit blinding factor, refund public key /// hash and refund locktime on the tBTC Bridge through this Depositor /// contract. @@ -32,22 +32,29 @@ import {stBTC} from "./stBTC.sol"; /// the off-chain ECDSA wallet may decide to pick the deposit transaction /// for sweeping, and when the sweep operation is confirmed on the Bitcoin /// network, the tBTC Bridge and tBTC vault mint the tBTC token to the -/// Depositor address. After tBTC is minted to the Depositor, on the stake -/// finalization tBTC is staked in Acre and stBTC shares are emitted -/// to the staker. +/// Depositor address. After tBTC is minted to the Depositor, on the deposit +/// finalization tBTC is deposited in Acre and stBTC shares are emitted +/// to the deposit owner. contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { using SafeERC20 for IERC20; - /// @notice State of the stake request. - enum StakeRequestState { + /// @notice Reflects the deposit state: + /// - Unknown deposit has not been initialized yet. + /// - Initialized deposit has been initialized with a call to + /// `initializeDeposit` function and is known to this contract. + /// - Finalized deposit led to tBTC ERC20 minting and was finalized + /// with a call to `finalizeDeposit` function that deposited tBTC + /// to the stBTC contract. + enum DepositState { Unknown, Initialized, Finalized } - /// @notice Mapping of stake requests. - /// @dev The key is a deposit key identifying the deposit. - mapping(uint256 => StakeRequestState) public stakeRequests; + /// @notice Holds the deposit state, keyed by the deposit key calculated for + /// the individual deposit during the call to `initializeDeposit` + /// function. + mapping(uint256 => DepositState) public deposits; /// @notice tBTC Token contract. IERC20 public tbtcToken; @@ -55,13 +62,13 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { /// @notice stBTC contract. stBTC public stbtc; - /// @notice Minimum amount of a single stake request (in tBTC token precision). + /// @notice Minimum amount of a single deposit (in tBTC token precision). /// @dev This parameter should be set to a value exceeding the minimum deposit - /// amount supported by tBTC Bridge. - uint256 public minStakeAmount; + /// amount supported by the tBTC Bridge. + uint256 public minDepositAmount; /// @notice Divisor used to compute the depositor fee taken from each deposit - /// and transferred to the treasury upon stake request finalization. + /// and transferred to the treasury upon deposit finalization. /// @dev That fee is computed as follows: /// `depositorFee = depositedAmount / depositorFeeDivisor` /// for example, if the depositor fee needs to be 2% of each deposit, @@ -69,29 +76,29 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { /// `1/50 = 0.02 = 2%`. uint64 public depositorFeeDivisor; - /// @notice Emitted when a stake request is initialized. + /// @notice Emitted when a deposit is initialized. /// @dev Deposit details can be fetched from {{ Bridge.DepositRevealed }} /// event emitted in the same transaction. /// @param depositKey Deposit key identifying the deposit. - /// @param caller Address that initialized the stake request. - /// @param staker The address to which the stBTC shares will be minted. + /// @param caller Address that initialized the deposit. + /// @param depositOwner The address to which the stBTC shares will be minted. /// @param initialAmount Amount of funding transaction. - event StakeRequestInitialized( + event DepositInitialized( uint256 indexed depositKey, address indexed caller, - address indexed staker, + address indexed depositOwner, uint256 initialAmount ); - /// @notice Emitted when a stake request is finalized. + /// @notice Emitted when a deposit is finalized. /// @dev Deposit details can be fetched from {{ ERC4626.Deposit }} /// event emitted in the same transaction. /// @param depositKey Deposit key identifying the deposit. - /// @param caller Address that finalized the stake request. + /// @param caller Address that finalized the deposit. /// @param initialAmount Amount of funding transaction. /// @param bridgedAmount Amount of tBTC tokens that was bridged by the tBTC bridge. /// @param depositorFee Depositor fee amount. - event StakeRequestFinalized( + event DepositFinalized( uint256 indexed depositKey, address indexed caller, uint16 indexed referral, @@ -100,10 +107,10 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { uint256 depositorFee ); - /// @notice Emitted when a minimum single stake amount is updated. - /// @param minStakeAmount New value of the minimum single stake + /// @notice Emitted when a minimum single deposit amount is updated. + /// @param minDepositAmount New value of the minimum single deposit /// amount (in tBTC token precision). - event MinStakeAmountUpdated(uint256 minStakeAmount); + event MinDepositAmountUpdated(uint256 minDepositAmount); /// @notice Emitted when a depositor fee divisor is updated. /// @param depositorFeeDivisor New value of the depositor fee divisor. @@ -115,14 +122,13 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { /// Reverts if the stBTC address is zero. error StbtcZeroAddress(); - /// @dev Staker address is zero. - error StakerIsZeroAddress(); + /// @dev Deposit owner address is zero. + error DepositOwnerIsZeroAddress(); - /// @dev Attempted to execute function for stake request in unexpected current - /// state. - error UnexpectedStakeRequestState( - StakeRequestState currentState, - StakeRequestState expectedState + /// @dev Attempted to execute function for deposit in unexpected current state. + error UnexpectedDepositState( + DepositState actualState, + DepositState expectedState ); /// @dev Calculated depositor fee exceeds the amount of minted tBTC tokens. @@ -131,10 +137,10 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { uint256 bridgedAmount ); - /// @dev Attempted to set minimum stake amount to a value lower than the + /// @dev Attempted to set minimum deposit amount to a value lower than the /// tBTC Bridge deposit dust threshold. - error MinStakeAmountLowerThanBridgeMinDeposit( - uint256 minStakeAmount, + error MinDepositAmountLowerThanBridgeMinDeposit( + uint256 minDepositAmount, uint256 bridgeMinDepositAmount ); @@ -169,18 +175,18 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { stbtc = stBTC(_stbtc); // TODO: Revisit initial values before mainnet deployment. - minStakeAmount = 0.015 * 1e18; // 0.015 BTC + minDepositAmount = 0.015 * 1e18; // 0.015 BTC depositorFeeDivisor = 1000; // 1/1000 == 10bps == 0.1% == 0.001 } - /// @notice This function allows staking process initialization for a Bitcoin + /// @notice This function allows depositing process initialization for a Bitcoin /// deposit made by an user with a P2(W)SH transaction. It uses the /// supplied information to reveal a deposit to the tBTC Bridge contract. /// @dev Requirements: /// - The revealed vault address must match the TBTCVault address, /// - All requirements from {Bridge#revealDepositWithExtraData} /// function must be met. - /// - `staker` must be the staker address used in the P2(W)SH BTC + /// - `depositOwner` must be the deposit owner address used in the P2(W)SH BTC /// deposit transaction as part of the extra data. /// - `referral` must be the referral info used in the P2(W)SH BTC /// deposit transaction as part of the extra data. @@ -188,15 +194,15 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { /// can be revealed only one time. /// @param fundingTx Bitcoin funding transaction data, see `IBridgeTypes.BitcoinTxInfo`. /// @param reveal Deposit reveal data, see `IBridgeTypes.DepositRevealInfo`. - /// @param staker The address to which the stBTC shares will be minted. + /// @param depositOwner The address to which the stBTC shares will be minted. /// @param referral Data used for referral program. - function initializeStake( + function initializeDeposit( IBridgeTypes.BitcoinTxInfo calldata fundingTx, IBridgeTypes.DepositRevealInfo calldata reveal, - address staker, + address depositOwner, uint16 referral ) external { - if (staker == address(0)) revert StakerIsZeroAddress(); + if (depositOwner == address(0)) revert DepositOwnerIsZeroAddress(); // We don't check if the request was already initialized, as this check // is enforced in `_initializeDeposit` when calling the @@ -204,47 +210,47 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { (uint256 depositKey, uint256 initialAmount) = _initializeDeposit( fundingTx, reveal, - encodeExtraData(staker, referral) + encodeExtraData(depositOwner, referral) ); - // Validate current stake request state. - if (stakeRequests[depositKey] != StakeRequestState.Unknown) - revert UnexpectedStakeRequestState( - stakeRequests[depositKey], - StakeRequestState.Unknown + // Validate current deposit state. + if (deposits[depositKey] != DepositState.Unknown) + revert UnexpectedDepositState( + deposits[depositKey], + DepositState.Unknown ); // Transition to a new state. - stakeRequests[depositKey] = StakeRequestState.Initialized; + deposits[depositKey] = DepositState.Initialized; - emit StakeRequestInitialized( + emit DepositInitialized( depositKey, msg.sender, - staker, + depositOwner, initialAmount ); } - /// @notice This function should be called for previously initialized stake + /// @notice This function should be called for previously initialized deposit /// request, after tBTC minting process completed, meaning tBTC was /// minted to this contract. - /// @dev It calculates the amount to stake based on the approximate minted + /// @dev It calculates the amount to deposit based on the approximate minted /// tBTC amount reduced by the depositor fee. /// @dev IMPORTANT NOTE: The minted tBTC amount used by this function is an /// approximation. See documentation of the /// {{AbstractTBTCDepositor#_calculateTbtcAmount}} responsible for calculating /// this value for more details. /// @param depositKey Deposit key identifying the deposit. - function finalizeStake(uint256 depositKey) external { - // Validate current stake request state. - if (stakeRequests[depositKey] != StakeRequestState.Initialized) - revert UnexpectedStakeRequestState( - stakeRequests[depositKey], - StakeRequestState.Initialized + function finalizeDeposit(uint256 depositKey) external { + // Validate current deposit state. + if (deposits[depositKey] != DepositState.Initialized) + revert UnexpectedDepositState( + deposits[depositKey], + DepositState.Initialized ); // Transition to a new state. - stakeRequests[depositKey] = StakeRequestState.Finalized; + deposits[depositKey] = DepositState.Finalized; ( uint256 initialAmount, @@ -269,9 +275,9 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { tbtcToken.safeTransfer(stbtc.treasury(), depositorFee); } - (address staker, uint16 referral) = decodeExtraData(extraData); + (address depositOwner, uint16 referral) = decodeExtraData(extraData); - emit StakeRequestFinalized( + emit DepositFinalized( depositKey, msg.sender, referral, @@ -280,33 +286,33 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { depositorFee ); - uint256 amountToStake = tbtcAmount - depositorFee; + uint256 amountToDeposit = tbtcAmount - depositorFee; // Deposit tBTC in stBTC. - tbtcToken.safeIncreaseAllowance(address(stbtc), amountToStake); + tbtcToken.safeIncreaseAllowance(address(stbtc), amountToDeposit); // slither-disable-next-line unused-return - stbtc.deposit(amountToStake, staker); + stbtc.deposit(amountToDeposit, depositOwner); } - /// @notice Updates the minimum stake amount. + /// @notice Updates the minimum deposit amount. /// @dev It requires that the new value is greater or equal to the tBTC Bridge /// deposit dust threshold, to ensure deposit will be able to be bridged. - /// @param newMinStakeAmount New minimum stake amount (in tBTC precision). - function updateMinStakeAmount( - uint256 newMinStakeAmount + /// @param newMinDepositAmount New minimum deposit amount (in tBTC precision). + function updateMinDepositAmount( + uint256 newMinDepositAmount ) external onlyOwner { uint256 minBridgeDepositAmount = _minDepositAmount(); // Check if new value is at least equal the tBTC Bridge Deposit Dust Threshold. - if (newMinStakeAmount < minBridgeDepositAmount) - revert MinStakeAmountLowerThanBridgeMinDeposit( - newMinStakeAmount, + if (newMinDepositAmount < minBridgeDepositAmount) + revert MinDepositAmountLowerThanBridgeMinDeposit( + newMinDepositAmount, minBridgeDepositAmount ); - minStakeAmount = newMinStakeAmount; + minDepositAmount = newMinDepositAmount; - emit MinStakeAmountUpdated(newMinStakeAmount); + emit MinDepositAmountUpdated(newMinDepositAmount); } /// @notice Updates the depositor fee divisor. @@ -320,39 +326,30 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { emit DepositorFeeDivisorUpdated(newDepositorFeeDivisor); } - /// @notice Minimum stake amount (in tBTC token precision). - /// @dev This function should be used by dApp to check the minimum amount - /// for the stake request. - /// @dev It is not enforced in the `initializeStakeRequest` function, as - /// it is intended to be used in the dApp staking form. - function minStake() external view returns (uint256) { - return minStakeAmount; - } - - /// @notice Encodes staker address and referral as extra data. - /// @dev Packs the data to bytes32: 20 bytes of staker address and + /// @notice Encodes deposit owner address and referral as extra data. + /// @dev Packs the data to bytes32: 20 bytes of deposit owner address and /// 2 bytes of referral, 10 bytes of trailing zeros. - /// @param staker The address to which the stBTC shares will be minted. + /// @param depositOwner The address to which the stBTC shares will be minted. /// @param referral Data used for referral program. /// @return Encoded extra data. function encodeExtraData( - address staker, + address depositOwner, uint16 referral ) public pure returns (bytes32) { - return bytes32(abi.encodePacked(staker, referral)); + return bytes32(abi.encodePacked(depositOwner, referral)); } - /// @notice Decodes staker address and referral from extra data. - /// @dev Unpacks the data from bytes32: 20 bytes of staker address and + /// @notice Decodes deposit owner address and referral from extra data. + /// @dev Unpacks the data from bytes32: 20 bytes of deposit owner address and /// 2 bytes of referral, 10 bytes of trailing zeros. /// @param extraData Encoded extra data. - /// @return staker The address to which the stBTC shares will be minted. + /// @return depositOwner The address to which the stBTC shares will be minted. /// @return referral Data used for referral program. function decodeExtraData( bytes32 extraData - ) public pure returns (address staker, uint16 referral) { - // First 20 bytes of extra data is staker address. - staker = address(uint160(bytes20(extraData))); + ) public pure returns (address depositOwner, uint16 referral) { + // First 20 bytes of extra data is deposit owner address. + depositOwner = address(uint160(bytes20(extraData))); // Next 2 bytes of extra data is referral info. referral = uint16(bytes2(extraData << (8 * 20))); } From a1a6a7a0436344f55fc64dc1c9d6964361faf3e7 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 9 Apr 2024 17:01:25 +0200 Subject: [PATCH 48/75] Remove TODO --- core/contracts/BitcoinDepositor.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/core/contracts/BitcoinDepositor.sol b/core/contracts/BitcoinDepositor.sol index 0576ff3da..2251dbd57 100644 --- a/core/contracts/BitcoinDepositor.sol +++ b/core/contracts/BitcoinDepositor.sol @@ -320,7 +320,6 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { function updateDepositorFeeDivisor( uint64 newDepositorFeeDivisor ) external onlyOwner { - // TODO: Introduce a parameters update process. depositorFeeDivisor = newDepositorFeeDivisor; emit DepositorFeeDivisorUpdated(newDepositorFeeDivisor); From cfef3fe09cc73e4cd38a80f8ba0724c5721a90e0 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 9 Apr 2024 17:30:36 +0200 Subject: [PATCH 49/75] Update BitcoinDepositor upgrade test contract --- .../test/upgrades/BitcoinDepositorV2.sol | 174 +++++++++--------- 1 file changed, 85 insertions(+), 89 deletions(-) diff --git a/core/contracts/test/upgrades/BitcoinDepositorV2.sol b/core/contracts/test/upgrades/BitcoinDepositorV2.sol index 106629430..85f02f3c8 100644 --- a/core/contracts/test/upgrades/BitcoinDepositorV2.sol +++ b/core/contracts/test/upgrades/BitcoinDepositorV2.sol @@ -18,16 +18,23 @@ import {stBTC} from "../../stBTC.sol"; contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { using SafeERC20 for IERC20; - /// @notice State of the stake request. - enum StakeRequestState { + /// @notice Reflects the deposit state: + /// - Unknown deposit has not been initialized yet. + /// - Initialized deposit has been initialized with a call to + /// `initializeDeposit` function and is known to this contract. + /// - Finalized deposit led to tBTC ERC20 minting and was finalized + /// with a call to `finalizeDeposit` function that deposited tBTC + /// to the stBTC contract. + enum DepositState { Unknown, Initialized, Finalized } - /// @notice Mapping of stake requests. - /// @dev The key is a deposit key identifying the deposit. - mapping(uint256 => StakeRequestState) public stakeRequests; + /// @notice Holds the deposit state, keyed by the deposit key calculated for + /// the individual deposit during the call to `initializeDeposit` + /// function. + mapping(uint256 => DepositState) public deposits; /// @notice tBTC Token contract. IERC20 public tbtcToken; @@ -35,13 +42,13 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { /// @notice stBTC contract. stBTC public stbtc; - /// @notice Minimum amount of a single stake request (in tBTC token precision). + /// @notice Minimum amount of a single deposit (in tBTC token precision). /// @dev This parameter should be set to a value exceeding the minimum deposit - /// amount supported by tBTC Bridge. - uint256 public minStakeAmount; + /// amount supported by the tBTC Bridge. + uint256 public minDepositAmount; /// @notice Divisor used to compute the depositor fee taken from each deposit - /// and transferred to the treasury upon stake request finalization. + /// and transferred to the treasury upon deposit finalization. /// @dev That fee is computed as follows: /// `depositorFee = depositedAmount / depositorFeeDivisor` /// for example, if the depositor fee needs to be 2% of each deposit, @@ -52,29 +59,29 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { // TEST: New variable; uint256 public newVariable; - /// @notice Emitted when a stake request is initialized. + /// @notice Emitted when a deposit is initialized. /// @dev Deposit details can be fetched from {{ Bridge.DepositRevealed }} /// event emitted in the same transaction. /// @param depositKey Deposit key identifying the deposit. - /// @param caller Address that initialized the stake request. - /// @param staker The address to which the stBTC shares will be minted. + /// @param caller Address that initialized the deposit. + /// @param depositOwner The address to which the stBTC shares will be minted. /// @param initialAmount Amount of funding transaction. - event StakeRequestInitialized( + event DepositInitialized( uint256 indexed depositKey, address indexed caller, - address indexed staker, + address indexed depositOwner, uint256 initialAmount ); - /// @notice Emitted when a stake request is finalized. + /// @notice Emitted when a deposit is finalized. /// @dev Deposit details can be fetched from {{ ERC4626.Deposit }} /// event emitted in the same transaction. /// @param depositKey Deposit key identifying the deposit. - /// @param caller Address that finalized the stake request. + /// @param caller Address that finalized the deposit. /// @param initialAmount Amount of funding transaction. /// @param bridgedAmount Amount of tBTC tokens that was bridged by the tBTC bridge. /// @param depositorFee Depositor fee amount. - event StakeRequestFinalized( + event DepositFinalized( uint256 indexed depositKey, address indexed caller, uint16 indexed referral, @@ -83,10 +90,10 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { uint256 depositorFee ); - /// @notice Emitted when a minimum single stake amount is updated. - /// @param minStakeAmount New value of the minimum single stake + /// @notice Emitted when a minimum single deposit amount is updated. + /// @param minDepositAmount New value of the minimum single deposit /// amount (in tBTC token precision). - event MinStakeAmountUpdated(uint256 minStakeAmount); + event MinDepositAmountUpdated(uint256 minDepositAmount); /// @notice Emitted when a depositor fee divisor is updated. /// @param depositorFeeDivisor New value of the depositor fee divisor. @@ -101,14 +108,13 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { /// Reverts if the stBTC address is zero. error StbtcZeroAddress(); - /// @dev Staker address is zero. - error StakerIsZeroAddress(); + /// @dev Deposit owner address is zero. + error DepositOwnerIsZeroAddress(); - /// @dev Attempted to execute function for stake request in unexpected current - /// state. - error UnexpectedStakeRequestState( - StakeRequestState currentState, - StakeRequestState expectedState + /// @dev Attempted to execute function for deposit in unexpected current state. + error UnexpectedDepositState( + DepositState actualState, + DepositState expectedState ); /// @dev Calculated depositor fee exceeds the amount of minted tBTC tokens. @@ -117,10 +123,10 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { uint256 bridgedAmount ); - /// @dev Attempted to set minimum stake amount to a value lower than the + /// @dev Attempted to set minimum deposit amount to a value lower than the /// tBTC Bridge deposit dust threshold. - error MinStakeAmountLowerThanBridgeMinDeposit( - uint256 minStakeAmount, + error MinDepositAmountLowerThanBridgeMinDeposit( + uint256 minDepositAmount, uint256 bridgeMinDepositAmount ); @@ -139,14 +145,14 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { newVariable = _newVariable; } - /// @notice This function allows staking process initialization for a Bitcoin + /// @notice This function allows depositing process initialization for a Bitcoin /// deposit made by an user with a P2(W)SH transaction. It uses the /// supplied information to reveal a deposit to the tBTC Bridge contract. /// @dev Requirements: /// - The revealed vault address must match the TBTCVault address, /// - All requirements from {Bridge#revealDepositWithExtraData} /// function must be met. - /// - `staker` must be the staker address used in the P2(W)SH BTC + /// - `depositOwner` must be the deposit owner address used in the P2(W)SH BTC /// deposit transaction as part of the extra data. /// - `referral` must be the referral info used in the P2(W)SH BTC /// deposit transaction as part of the extra data. @@ -154,15 +160,15 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { /// can be revealed only one time. /// @param fundingTx Bitcoin funding transaction data, see `IBridgeTypes.BitcoinTxInfo`. /// @param reveal Deposit reveal data, see `IBridgeTypes.DepositRevealInfo`. - /// @param staker The address to which the stBTC shares will be minted. + /// @param depositOwner The address to which the stBTC shares will be minted. /// @param referral Data used for referral program. - function initializeStake( + function initializeDeposit( IBridgeTypes.BitcoinTxInfo calldata fundingTx, IBridgeTypes.DepositRevealInfo calldata reveal, - address staker, + address depositOwner, uint16 referral ) external { - if (staker == address(0)) revert StakerIsZeroAddress(); + if (depositOwner == address(0)) revert DepositOwnerIsZeroAddress(); // We don't check if the request was already initialized, as this check // is enforced in `_initializeDeposit` when calling the @@ -170,47 +176,47 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { (uint256 depositKey, uint256 initialAmount) = _initializeDeposit( fundingTx, reveal, - encodeExtraData(staker, referral) + encodeExtraData(depositOwner, referral) ); - // Validate current stake request state. - if (stakeRequests[depositKey] != StakeRequestState.Unknown) - revert UnexpectedStakeRequestState( - stakeRequests[depositKey], - StakeRequestState.Unknown + // Validate current deposit state. + if (deposits[depositKey] != DepositState.Unknown) + revert UnexpectedDepositState( + deposits[depositKey], + DepositState.Unknown ); // Transition to a new state. - stakeRequests[depositKey] = StakeRequestState.Initialized; + deposits[depositKey] = DepositState.Initialized; - emit StakeRequestInitialized( + emit DepositInitialized( depositKey, msg.sender, - staker, + depositOwner, initialAmount ); } - /// @notice This function should be called for previously initialized stake + /// @notice This function should be called for previously initialized deposit /// request, after tBTC minting process completed, meaning tBTC was /// minted to this contract. - /// @dev It calculates the amount to stake based on the approximate minted + /// @dev It calculates the amount to deposit based on the approximate minted /// tBTC amount reduced by the depositor fee. /// @dev IMPORTANT NOTE: The minted tBTC amount used by this function is an /// approximation. See documentation of the /// {{AbstractTBTCDepositor#_calculateTbtcAmount}} responsible for calculating /// this value for more details. /// @param depositKey Deposit key identifying the deposit. - function finalizeStake(uint256 depositKey) external { - // Validate current stake request state. - if (stakeRequests[depositKey] != StakeRequestState.Initialized) - revert UnexpectedStakeRequestState( - stakeRequests[depositKey], - StakeRequestState.Initialized + function finalizeDeposit(uint256 depositKey) external { + // Validate current deposit state. + if (deposits[depositKey] != DepositState.Initialized) + revert UnexpectedDepositState( + deposits[depositKey], + DepositState.Initialized ); // Transition to a new state. - stakeRequests[depositKey] = StakeRequestState.Finalized; + deposits[depositKey] = DepositState.Finalized; ( uint256 initialAmount, @@ -235,9 +241,9 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { tbtcToken.safeTransfer(stbtc.treasury(), depositorFee); } - (address staker, uint16 referral) = decodeExtraData(extraData); + (address depositOwner, uint16 referral) = decodeExtraData(extraData); - emit StakeRequestFinalized( + emit DepositFinalized( depositKey, msg.sender, referral, @@ -246,33 +252,33 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { depositorFee ); - uint256 amountToStake = tbtcAmount - depositorFee; + uint256 amountToDeposit = tbtcAmount - depositorFee; // Deposit tBTC in stBTC. - tbtcToken.safeIncreaseAllowance(address(stbtc), amountToStake); + tbtcToken.safeIncreaseAllowance(address(stbtc), amountToDeposit); // slither-disable-next-line unused-return - stbtc.deposit(amountToStake, staker); + stbtc.deposit(amountToDeposit, depositOwner); } - /// @notice Updates the minimum stake amount. + /// @notice Updates the minimum deposit amount. /// @dev It requires that the new value is greater or equal to the tBTC Bridge /// deposit dust threshold, to ensure deposit will be able to be bridged. - /// @param newMinStakeAmount New minimum stake amount (in tBTC precision). - function updateMinStakeAmount( - uint256 newMinStakeAmount + /// @param newMinDepositAmount New minimum deposit amount (in tBTC precision). + function updateMinDepositAmount( + uint256 newMinDepositAmount ) external onlyOwner { uint256 minBridgeDepositAmount = _minDepositAmount(); // Check if new value is at least equal the tBTC Bridge Deposit Dust Threshold. - if (newMinStakeAmount < minBridgeDepositAmount) - revert MinStakeAmountLowerThanBridgeMinDeposit( - newMinStakeAmount, + if (newMinDepositAmount < minBridgeDepositAmount) + revert MinDepositAmountLowerThanBridgeMinDeposit( + newMinDepositAmount, minBridgeDepositAmount ); - minStakeAmount = newMinStakeAmount; + minDepositAmount = newMinDepositAmount; - emit MinStakeAmountUpdated(newMinStakeAmount); + emit MinDepositAmountUpdated(newMinDepositAmount); // TEST: Emit newly added event. emit NewEvent(); @@ -283,45 +289,35 @@ contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable { function updateDepositorFeeDivisor( uint64 newDepositorFeeDivisor ) external onlyOwner { - // TODO: Introduce a parameters update process. depositorFeeDivisor = newDepositorFeeDivisor; emit DepositorFeeDivisorUpdated(newDepositorFeeDivisor); } - /// @notice Minimum stake amount (in tBTC token precision). - /// @dev This function should be used by dApp to check the minimum amount - /// for the stake request. - /// @dev It is not enforced in the `initializeStakeRequest` function, as - /// it is intended to be used in the dApp staking form. - function minStake() external view returns (uint256) { - return minStakeAmount; - } - - /// @notice Encodes staker address and referral as extra data. - /// @dev Packs the data to bytes32: 20 bytes of staker address and + /// @notice Encodes deposit owner address and referral as extra data. + /// @dev Packs the data to bytes32: 20 bytes of deposit owner address and /// 2 bytes of referral, 10 bytes of trailing zeros. - /// @param staker The address to which the stBTC shares will be minted. + /// @param depositOwner The address to which the stBTC shares will be minted. /// @param referral Data used for referral program. /// @return Encoded extra data. function encodeExtraData( - address staker, + address depositOwner, uint16 referral ) public pure returns (bytes32) { - return bytes32(abi.encodePacked(staker, referral)); + return bytes32(abi.encodePacked(depositOwner, referral)); } - /// @notice Decodes staker address and referral from extra data. - /// @dev Unpacks the data from bytes32: 20 bytes of staker address and + /// @notice Decodes deposit owner address and referral from extra data. + /// @dev Unpacks the data from bytes32: 20 bytes of deposit owner address and /// 2 bytes of referral, 10 bytes of trailing zeros. /// @param extraData Encoded extra data. - /// @return staker The address to which the stBTC shares will be minted. + /// @return depositOwner The address to which the stBTC shares will be minted. /// @return referral Data used for referral program. function decodeExtraData( bytes32 extraData - ) public pure returns (address staker, uint16 referral) { - // First 20 bytes of extra data is staker address. - staker = address(uint160(bytes20(extraData))); + ) public pure returns (address depositOwner, uint16 referral) { + // First 20 bytes of extra data is deposit owner address. + depositOwner = address(uint160(bytes20(extraData))); // Next 2 bytes of extra data is referral info. referral = uint16(bytes2(extraData << (8 * 20))); } From f73e7296f393f99101ad5704bf5094829220204b Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 9 Apr 2024 17:34:36 +0200 Subject: [PATCH 50/75] Update BitcoinDepositor tests after stake rename --- core/test/BitcoinDepositor.test.ts | 235 +++++++++++---------- core/test/BitcoinDepositor.upgrade.test.ts | 16 +- core/test/data/tbtc.ts | 2 +- core/test/stBTC.test.ts | 10 +- core/test/stBTC.upgrade.test.ts | 18 +- core/types/index.ts | 2 +- 6 files changed, 147 insertions(+), 136 deletions(-) diff --git a/core/test/BitcoinDepositor.test.ts b/core/test/BitcoinDepositor.test.ts index 2c0fd02f7..79b7c82aa 100644 --- a/core/test/BitcoinDepositor.test.ts +++ b/core/test/BitcoinDepositor.test.ts @@ -6,7 +6,7 @@ import { expect } from "chai" import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import { ContractTransactionResponse, MaxUint256, ZeroAddress } from "ethers" -import { StakeRequestState } from "../types" +import { DepositState } from "../types" import type { StBTC, @@ -46,7 +46,7 @@ describe("BitcoinDepositor", () => { const initialDepositAmount = to1ePrecision(10000, 10) // 10000 satoshi const bridgedTbtcAmount = to1ePrecision(897501, 8) // 8975,01 satoshi const depositorFee = to1ePrecision(10, 10) // 10 satoshi - const amountToStake = to1ePrecision(896501, 8) // 8965,01 satoshi + const amountToDeposit = to1ePrecision(896501, 8) // 8965,01 satoshi let bitcoinDepositor: BitcoinDepositor let tbtcBridge: BridgeStub @@ -87,24 +87,27 @@ describe("BitcoinDepositor", () => { .updateDepositorFeeDivisor(defaultDepositorFeeDivisor) }) - describe("initializeStake", () => { + describe("initializeDeposit", () => { beforeAfterSnapshotWrapper() - describe("when staker is zero address", () => { + describe("when depositOwner is zero address", () => { it("should revert", async () => { await expect( - bitcoinDepositor.initializeStake( + bitcoinDepositor.initializeDeposit( tbtcDepositData.fundingTxInfo, tbtcDepositData.reveal, ZeroAddress, 0, ), - ).to.be.revertedWithCustomError(bitcoinDepositor, "StakerIsZeroAddress") + ).to.be.revertedWithCustomError( + bitcoinDepositor, + "DepositOwnerIsZeroAddress", + ) }) }) - describe("when staker is non zero address", () => { - describe("when stake is not in progress", () => { + describe("when depositOwner is non zero address", () => { + describe("when deposit is not in progress", () => { describe("when tbtc vault address is incorrect", () => { beforeAfterSnapshotWrapper() @@ -115,10 +118,10 @@ describe("BitcoinDepositor", () => { await expect( bitcoinDepositor .connect(thirdParty) - .initializeStake( + .initializeDeposit( tbtcDepositData.fundingTxInfo, { ...tbtcDepositData.reveal, vault: invalidTbtcVault }, - tbtcDepositData.staker, + tbtcDepositData.depositOwner, tbtcDepositData.referral, ), ).to.be.revertedWith("Vault address mismatch") @@ -134,31 +137,31 @@ describe("BitcoinDepositor", () => { before(async () => { tx = await bitcoinDepositor .connect(thirdParty) - .initializeStake( + .initializeDeposit( tbtcDepositData.fundingTxInfo, tbtcDepositData.reveal, - tbtcDepositData.staker, + tbtcDepositData.depositOwner, tbtcDepositData.referral, ) }) - it("should emit StakeRequestInitialized event", async () => { + it("should emit DepositInitialized event", async () => { await expect(tx) - .to.emit(bitcoinDepositor, "StakeRequestInitialized") + .to.emit(bitcoinDepositor, "DepositInitialized") .withArgs( tbtcDepositData.depositKey, thirdParty.address, - tbtcDepositData.staker, + tbtcDepositData.depositOwner, initialDepositAmount, ) }) - it("should update stake state", async () => { - const stakeRequest = await bitcoinDepositor.stakeRequests( + it("should update deposit state", async () => { + const deposit = await bitcoinDepositor.deposits( tbtcDepositData.depositKey, ) - expect(stakeRequest).to.be.equal(StakeRequestState.Initialized) + expect(deposit).to.be.equal(DepositState.Initialized) }) it("should reveal the deposit to the bridge contract with extra data", async () => { @@ -185,10 +188,10 @@ describe("BitcoinDepositor", () => { await expect( bitcoinDepositor .connect(thirdParty) - .initializeStake( + .initializeDeposit( tbtcDepositData.fundingTxInfo, tbtcDepositData.reveal, - tbtcDepositData.staker, + tbtcDepositData.depositOwner, 0, ), ).to.be.not.reverted @@ -197,49 +200,49 @@ describe("BitcoinDepositor", () => { }) }) - describe("when stake is already in progress", () => { + describe("when deposit is already in progress", () => { beforeAfterSnapshotWrapper() before(async () => { - await initializeStake() + await initializeDeposit() }) it("should revert", async () => { await expect( bitcoinDepositor .connect(thirdParty) - .initializeStake( + .initializeDeposit( tbtcDepositData.fundingTxInfo, tbtcDepositData.reveal, - tbtcDepositData.staker, + tbtcDepositData.depositOwner, tbtcDepositData.referral, ), ).to.be.revertedWith("Deposit already revealed") }) }) - describe("when stake is already finalized", () => { + describe("when deposit is already finalized", () => { beforeAfterSnapshotWrapper() before(async () => { - await initializeStake() + await initializeDeposit() // Simulate deposit request finalization. await finalizeMinting(tbtcDepositData.depositKey) await bitcoinDepositor .connect(thirdParty) - .finalizeStake(tbtcDepositData.depositKey) + .finalizeDeposit(tbtcDepositData.depositKey) }) it("should revert", async () => { await expect( bitcoinDepositor .connect(thirdParty) - .initializeStake( + .initializeDeposit( tbtcDepositData.fundingTxInfo, tbtcDepositData.reveal, - tbtcDepositData.staker, + tbtcDepositData.depositOwner, tbtcDepositData.referral, ), ).to.be.revertedWith("Deposit already revealed") @@ -248,29 +251,29 @@ describe("BitcoinDepositor", () => { }) }) - describe("finalizeStake", () => { + describe("finalizeDeposit", () => { beforeAfterSnapshotWrapper() - describe("when stake has not been initialized", () => { + describe("when deposit has not been initialized", () => { it("should revert", async () => { await expect( bitcoinDepositor .connect(thirdParty) - .finalizeStake(tbtcDepositData.depositKey), + .finalizeDeposit(tbtcDepositData.depositKey), ) .to.be.revertedWithCustomError( bitcoinDepositor, - "UnexpectedStakeRequestState", + "UnexpectedDepositState", ) - .withArgs(StakeRequestState.Unknown, StakeRequestState.Initialized) + .withArgs(DepositState.Unknown, DepositState.Initialized) }) }) - describe("when stake has been initialized", () => { + describe("when deposit has been initialized", () => { beforeAfterSnapshotWrapper() before(async () => { - await initializeStake() + await initializeDeposit() }) describe("when deposit was not bridged", () => { @@ -278,13 +281,13 @@ describe("BitcoinDepositor", () => { await expect( bitcoinDepositor .connect(thirdParty) - .finalizeStake(tbtcDepositData.depositKey), + .finalizeDeposit(tbtcDepositData.depositKey), ).to.be.revertedWith("Deposit not finalized by the bridge") }) }) describe("when deposit was bridged", () => { - describe("when stake has not been finalized", () => { + describe("when deposit has not been finalized", () => { describe("when depositor contract balance is lower than bridged amount", () => { beforeAfterSnapshotWrapper() @@ -300,7 +303,7 @@ describe("BitcoinDepositor", () => { await expect( bitcoinDepositor .connect(thirdParty) - .finalizeStake(tbtcDepositData.depositKey), + .finalizeDeposit(tbtcDepositData.depositKey), ) .to.be.revertedWithCustomError( stbtc, @@ -309,7 +312,7 @@ describe("BitcoinDepositor", () => { .withArgs( await bitcoinDepositor.getAddress(), mintedAmount - depositorFee, - amountToStake, + amountToDeposit, ) }) }) @@ -325,15 +328,15 @@ describe("BitcoinDepositor", () => { describe("when depositor fee divisor is not zero", () => { beforeAfterSnapshotWrapper() - const expectedAssetsAmount = amountToStake - const expectedReceivedSharesAmount = amountToStake + const expectedAssetsAmount = amountToDeposit + const expectedReceivedSharesAmount = amountToDeposit let tx: ContractTransactionResponse before(async () => { tx = await bitcoinDepositor .connect(thirdParty) - .finalizeStake(tbtcDepositData.depositKey) + .finalizeDeposit(tbtcDepositData.depositKey) }) it("should transfer depositor fee", async () => { @@ -344,17 +347,17 @@ describe("BitcoinDepositor", () => { ) }) - it("should update stake state", async () => { - const stakeRequest = await bitcoinDepositor.stakeRequests( + it("should update deposit state", async () => { + const depositState = await bitcoinDepositor.deposits( tbtcDepositData.depositKey, ) - expect(stakeRequest).to.be.equal(StakeRequestState.Finalized) + expect(depositState).to.be.equal(DepositState.Finalized) }) - it("should emit StakeRequestFinalized event", async () => { + it("should emit DepositFinalized event", async () => { await expect(tx) - .to.emit(bitcoinDepositor, "StakeRequestFinalized") + .to.emit(bitcoinDepositor, "DepositFinalized") .withArgs( tbtcDepositData.depositKey, thirdParty.address, @@ -370,25 +373,25 @@ describe("BitcoinDepositor", () => { .to.emit(stbtc, "Deposit") .withArgs( await bitcoinDepositor.getAddress(), - tbtcDepositData.staker, + tbtcDepositData.depositOwner, expectedAssetsAmount, expectedReceivedSharesAmount, ) }) - it("should stake in Acre contract", async () => { + it("should deposit in Acre contract", async () => { await expect( tx, "invalid minted stBTC amount", ).to.changeTokenBalances( stbtc, - [tbtcDepositData.staker], + [tbtcDepositData.depositOwner], [expectedReceivedSharesAmount], ) await expect( tx, - "invalid staked tBTC amount", + "invalid deposited tBTC amount", ).to.changeTokenBalances(tbtc, [stbtc], [expectedAssetsAmount]) }) }) @@ -396,8 +399,9 @@ describe("BitcoinDepositor", () => { describe("when depositor fee divisor is zero", () => { beforeAfterSnapshotWrapper() - const expectedAssetsAmount = amountToStake + depositorFee - const expectedReceivedSharesAmount = amountToStake + depositorFee + const expectedAssetsAmount = amountToDeposit + depositorFee + const expectedReceivedSharesAmount = + amountToDeposit + depositorFee let tx: ContractTransactionResponse @@ -408,24 +412,24 @@ describe("BitcoinDepositor", () => { tx = await bitcoinDepositor .connect(thirdParty) - .finalizeStake(tbtcDepositData.depositKey) + .finalizeDeposit(tbtcDepositData.depositKey) }) it("should not transfer depositor fee", async () => { await expect(tx).to.changeTokenBalances(tbtc, [treasury], [0]) }) - it("should update stake state", async () => { - const stakeRequest = await bitcoinDepositor.stakeRequests( + it("should update deposit state", async () => { + const deposit = await bitcoinDepositor.deposits( tbtcDepositData.depositKey, ) - expect(stakeRequest).to.be.equal(StakeRequestState.Finalized) + expect(deposit).to.be.equal(DepositState.Finalized) }) - it("should emit StakeRequestFinalized event", async () => { + it("should emit DepositFinalized event", async () => { await expect(tx) - .to.emit(bitcoinDepositor, "StakeRequestFinalized") + .to.emit(bitcoinDepositor, "DepositFinalized") .withArgs( tbtcDepositData.depositKey, thirdParty.address, @@ -441,25 +445,25 @@ describe("BitcoinDepositor", () => { .to.emit(stbtc, "Deposit") .withArgs( await bitcoinDepositor.getAddress(), - tbtcDepositData.staker, + tbtcDepositData.depositOwner, expectedAssetsAmount, expectedReceivedSharesAmount, ) }) - it("should stake in Acre contract", async () => { + it("should deposit in Acre contract", async () => { await expect( tx, "invalid minted stBTC amount", ).to.changeTokenBalances( stbtc, - [tbtcDepositData.staker], + [tbtcDepositData.depositOwner], [expectedReceivedSharesAmount], ) await expect( tx, - "invalid staked tBTC amount", + "invalid deposited tBTC amount", ).to.changeTokenBalances(tbtc, [stbtc], [expectedAssetsAmount]) }) }) @@ -477,7 +481,7 @@ describe("BitcoinDepositor", () => { await expect( bitcoinDepositor .connect(thirdParty) - .finalizeStake(tbtcDepositData.depositKey), + .finalizeDeposit(tbtcDepositData.depositKey), ) .to.be.revertedWithCustomError( bitcoinDepositor, @@ -489,40 +493,37 @@ describe("BitcoinDepositor", () => { }) }) - describe("when stake has been finalized", () => { + describe("when deposit has been finalized", () => { beforeAfterSnapshotWrapper() before(async () => { // Simulate deposit request finalization. await finalizeMinting(tbtcDepositData.depositKey) - // Finalize stake. + // Finalize deposit. await bitcoinDepositor .connect(thirdParty) - .finalizeStake(tbtcDepositData.depositKey) + .finalizeDeposit(tbtcDepositData.depositKey) }) it("should revert", async () => { await expect( bitcoinDepositor .connect(thirdParty) - .finalizeStake(tbtcDepositData.depositKey), + .finalizeDeposit(tbtcDepositData.depositKey), ) .to.be.revertedWithCustomError( bitcoinDepositor, - "UnexpectedStakeRequestState", - ) - .withArgs( - StakeRequestState.Finalized, - StakeRequestState.Initialized, + "UnexpectedDepositState", ) + .withArgs(DepositState.Finalized, DepositState.Initialized) }) }) }) }) }) - describe("updateMinStakeAmount", () => { + describe("updateMinDepositAmount", () => { beforeAfterSnapshotWrapper() describe("when caller is not governance", () => { @@ -530,7 +531,7 @@ describe("BitcoinDepositor", () => { it("should revert", async () => { await expect( - bitcoinDepositor.connect(thirdParty).updateMinStakeAmount(1234), + bitcoinDepositor.connect(thirdParty).updateMinDepositAmount(1234), ) .to.be.revertedWithCustomError( bitcoinDepositor, @@ -541,7 +542,7 @@ describe("BitcoinDepositor", () => { }) describe("when caller is governance", () => { - const testUpdateMinStakeAmount = (newValue: bigint) => + const testupdateMinDepositAmount = (newValue: bigint) => function () { beforeAfterSnapshotWrapper() @@ -550,17 +551,17 @@ describe("BitcoinDepositor", () => { before(async () => { tx = await bitcoinDepositor .connect(governance) - .updateMinStakeAmount(newValue) + .updateMinDepositAmount(newValue) }) - it("should emit MinStakeAmountUpdated event", async () => { + it("should emit MinDepositAmountUpdated event", async () => { await expect(tx) - .to.emit(bitcoinDepositor, "MinStakeAmountUpdated") + .to.emit(bitcoinDepositor, "MinDepositAmountUpdated") .withArgs(newValue) }) it("should update value correctly", async () => { - expect(await bitcoinDepositor.minStakeAmount()).to.be.eq(newValue) + expect(await bitcoinDepositor.minDepositAmount()).to.be.eq(newValue) }) } @@ -568,44 +569,44 @@ describe("BitcoinDepositor", () => { // Deposit dust threshold: 1000000 satoshi = 0.01 BTC // tBTC Bridge stores the dust threshold in satoshi precision, - // we need to convert it to the tBTC token precision as `updateMinStakeAmount` + // we need to convert it to the tBTC token precision as `updateMinDepositAmount` // function expects this precision. const bridgeDepositDustThreshold = to1ePrecision( defaultDepositDustThreshold, 10, ) - describe("when new stake amount is less than bridge deposit dust threshold", () => { + describe("when new deposit amount is less than bridge deposit dust threshold", () => { beforeAfterSnapshotWrapper() - const newMinStakeAmount = bridgeDepositDustThreshold - 1n + const newMinDepositAmount = bridgeDepositDustThreshold - 1n it("should revert", async () => { await expect( bitcoinDepositor .connect(governance) - .updateMinStakeAmount(newMinStakeAmount), + .updateMinDepositAmount(newMinDepositAmount), ) .to.be.revertedWithCustomError( bitcoinDepositor, - "MinStakeAmountLowerThanBridgeMinDeposit", + "MinDepositAmountLowerThanBridgeMinDeposit", ) - .withArgs(newMinStakeAmount, bridgeDepositDustThreshold) + .withArgs(newMinDepositAmount, bridgeDepositDustThreshold) }) }) describe( - "when new stake amount is equal to bridge deposit dust threshold", - testUpdateMinStakeAmount(bridgeDepositDustThreshold), + "when new deposit amount is equal to bridge deposit dust threshold", + testupdateMinDepositAmount(bridgeDepositDustThreshold), ) describe( - "when new stake amount is greater than bridge deposit dust threshold", - testUpdateMinStakeAmount(bridgeDepositDustThreshold + 1n), + "when new deposit amount is greater than bridge deposit dust threshold", + testupdateMinDepositAmount(bridgeDepositDustThreshold + 1n), ) describe( - "when new stake amount is equal max uint256", - testUpdateMinStakeAmount(MaxUint256), + "when new deposit amount is equal max uint256", + testupdateMinDepositAmount(MaxUint256), ) }) }) @@ -669,24 +670,24 @@ describe("BitcoinDepositor", () => { const extraDataValidTestData = new Map< string, { - staker: string + depositOwner: string referral: number extraData: string } >([ [ - "staker has leading zeros", + "depositOwner has leading zeros", { - staker: "0x000055d85E80A49B5930C4a77975d44f012D86C1", + depositOwner: "0x000055d85E80A49B5930C4a77975d44f012D86C1", referral: 6851, // hex: 0x1ac3 extraData: "0x000055d85e80a49b5930c4a77975d44f012d86c11ac300000000000000000000", }, ], [ - "staker has trailing zeros", + "depositOwner has trailing zeros", { - staker: "0x2d2F8BC7923F7F806Dc9bb2e17F950b42CfE0000", + depositOwner: "0x2d2F8BC7923F7F806Dc9bb2e17F950b42CfE0000", referral: 6851, // hex: 0x1ac3 extraData: "0x2d2f8bc7923f7f806dc9bb2e17f950b42cfe00001ac300000000000000000000", @@ -695,7 +696,7 @@ describe("BitcoinDepositor", () => { [ "referral is zero", { - staker: "0xeb098d6cDE6A202981316b24B19e64D82721e89E", + depositOwner: "0xeb098d6cDE6A202981316b24B19e64D82721e89E", referral: 0, extraData: "0xeb098d6cde6a202981316b24b19e64d82721e89e000000000000000000000000", @@ -704,7 +705,7 @@ describe("BitcoinDepositor", () => { [ "referral has leading zeros", { - staker: "0xeb098d6cDE6A202981316b24B19e64D82721e89E", + depositOwner: "0xeb098d6cDE6A202981316b24B19e64D82721e89E", referral: 31, // hex: 0x001f extraData: "0xeb098d6cde6a202981316b24b19e64d82721e89e001f00000000000000000000", @@ -713,7 +714,7 @@ describe("BitcoinDepositor", () => { [ "referral has trailing zeros", { - staker: "0xeb098d6cDE6A202981316b24B19e64D82721e89E", + depositOwner: "0xeb098d6cDE6A202981316b24B19e64D82721e89E", referral: 19712, // hex: 0x4d00 extraData: "0xeb098d6cde6a202981316b24b19e64d82721e89e4d0000000000000000000000", @@ -722,7 +723,7 @@ describe("BitcoinDepositor", () => { [ "referral is maximum value", { - staker: "0xeb098d6cDE6A202981316b24B19e64D82721e89E", + depositOwner: "0xeb098d6cDE6A202981316b24B19e64D82721e89E", referral: 65535, // max uint16 extraData: "0xeb098d6cde6a202981316b24b19e64d82721e89effff00000000000000000000", @@ -733,10 +734,10 @@ describe("BitcoinDepositor", () => { describe("encodeExtraData", () => { extraDataValidTestData.forEach( // eslint-disable-next-line @typescript-eslint/no-shadow - ({ staker, referral, extraData: expectedExtraData }, testName) => { + ({ depositOwner, referral, extraData: expectedExtraData }, testName) => { it(testName, async () => { expect( - await bitcoinDepositor.encodeExtraData(staker, referral), + await bitcoinDepositor.encodeExtraData(depositOwner, referral), ).to.be.equal(expectedExtraData) }) }, @@ -746,14 +747,20 @@ describe("BitcoinDepositor", () => { describe("decodeExtraData", () => { extraDataValidTestData.forEach( ( - { staker: expectedStaker, referral: expectedReferral, extraData }, + { + depositOwner: expoectedDepositOwner, + referral: expectedReferral, + extraData, + }, testName, ) => { it(testName, async () => { - const [actualStaker, actualReferral] = + const [actualDepositOwner, actualReferral] = await bitcoinDepositor.decodeExtraData(extraData) - expect(actualStaker, "invalid staker").to.be.equal(expectedStaker) + expect(actualDepositOwner, "invalid depositOwner").to.be.equal( + expoectedDepositOwner, + ) expect(actualReferral, "invalid referral").to.be.equal( expectedReferral, ) @@ -767,24 +774,26 @@ describe("BitcoinDepositor", () => { // value. const extraData = "0xeb098d6cde6a202981316b24b19e64d82721e89e1ac3105f9919321ea7d75f58" - const expectedStaker = "0xeb098d6cDE6A202981316b24B19e64D82721e89E" + const expectedDepositOwner = "0xeb098d6cDE6A202981316b24B19e64D82721e89E" const expectedReferral = 6851 // hex: 0x1ac3 - const [actualStaker, actualReferral] = + const [actualDepositOwner, actualReferral] = await bitcoinDepositor.decodeExtraData(extraData) - expect(actualStaker, "invalid staker").to.be.equal(expectedStaker) + expect(actualDepositOwner, "invalid depositOwner").to.be.equal( + expectedDepositOwner, + ) expect(actualReferral, "invalid referral").to.be.equal(expectedReferral) }) }) - async function initializeStake() { + async function initializeDeposit() { await bitcoinDepositor .connect(thirdParty) - .initializeStake( + .initializeDeposit( tbtcDepositData.fundingTxInfo, tbtcDepositData.reveal, - tbtcDepositData.staker, + tbtcDepositData.depositOwner, tbtcDepositData.referral, ) } diff --git a/core/test/BitcoinDepositor.upgrade.test.ts b/core/test/BitcoinDepositor.upgrade.test.ts index 46ea0155c..6194712d5 100644 --- a/core/test/BitcoinDepositor.upgrade.test.ts +++ b/core/test/BitcoinDepositor.upgrade.test.ts @@ -42,19 +42,19 @@ describe("BitcoinDepositor contract upgrade", () => { const newVariable = 1n let bitcoinDepositorV2: BitcoinDepositorV2 let v1InitialParameters: { - minStakeAmount: bigint + minDepositAmount: bigint depositorFeeDivisor: bigint } beforeAfterSnapshotWrapper() before(async () => { - const minStakeAmount = await bitcoinDepositor.minStakeAmount() + const minDepositAmount = await bitcoinDepositor.minDepositAmount() const depositorFeeDivisor = await bitcoinDepositor.depositorFeeDivisor() v1InitialParameters = { - minStakeAmount, + minDepositAmount, depositorFeeDivisor, } @@ -98,8 +98,8 @@ describe("BitcoinDepositor contract upgrade", () => { ) expect(await bitcoinDepositorV2.stbtc()).to.eq(await stbtc.getAddress()) - expect(await bitcoinDepositorV2.minStakeAmount()).to.eq( - v1InitialParameters.minStakeAmount, + expect(await bitcoinDepositorV2.minDepositAmount()).to.eq( + v1InitialParameters.minDepositAmount, ) expect(await bitcoinDepositorV2.depositorFeeDivisor()).to.eq( v1InitialParameters.depositorFeeDivisor, @@ -107,14 +107,14 @@ describe("BitcoinDepositor contract upgrade", () => { }) }) - describe("upgraded `updateMinStakeAmount` function", () => { - const newMinStakeAmount: bigint = to1e18(1000) + describe("upgraded `updateMinDepositAmount` function", () => { + const newMinDepositAmount: bigint = to1e18(1000) let tx: ContractTransactionResponse before(async () => { tx = await bitcoinDepositorV2 .connect(governance) - .updateMinStakeAmount(newMinStakeAmount) + .updateMinDepositAmount(newMinDepositAmount) }) it("should emit `NewEvent` event", async () => { diff --git a/core/test/data/tbtc.ts b/core/test/data/tbtc.ts index fcf97c50d..923635e4f 100644 --- a/core/test/data/tbtc.ts +++ b/core/test/data/tbtc.ts @@ -35,7 +35,7 @@ export const tbtcDepositData = { vault: "0x594cfd89700040163727828AE20B52099C58F02C", }, // 20-bytes of extraData - staker: "0xa9B38eA6435c8941d6eDa6a46b68E3e211719699", + depositOwner: "0xa9B38eA6435c8941d6eDa6a46b68E3e211719699", // 2-bytes of extraData referral: "0x5bd1", extraData: diff --git a/core/test/stBTC.test.ts b/core/test/stBTC.test.ts index 4f5bec95a..729cdcafe 100644 --- a/core/test/stBTC.test.ts +++ b/core/test/stBTC.test.ts @@ -659,7 +659,7 @@ describe("stBTC", () => { const amountToDeposit = to1e18(1) let tx: ContractTransactionResponse let amountToRedeem: bigint - let amountStaked: bigint + let amountDeposited: bigint let shares: bigint before(async () => { @@ -670,10 +670,10 @@ describe("stBTC", () => { await stbtc .connect(depositor1) .deposit(amountToDeposit, depositor1.address) - amountStaked = + amountDeposited = amountToDeposit - feeOnTotal(amountToDeposit, entryFeeBasisPoints) amountToRedeem = - amountStaked - feeOnTotal(amountStaked, exitFeeBasisPoints) + amountDeposited - feeOnTotal(amountDeposited, exitFeeBasisPoints) tx = await stbtc .connect(depositor1) .redeem(shares, thirdParty, depositor1) @@ -714,7 +714,7 @@ describe("stBTC", () => { await expect(tx).to.changeTokenBalances( tbtc, [treasury.address], - [feeOnTotal(amountStaked, exitFeeBasisPoints)], + [feeOnTotal(amountDeposited, exitFeeBasisPoints)], ) }) }) @@ -989,7 +989,7 @@ describe("stBTC", () => { ) }) - it("should transfer tBTC tokens to a Staker", async () => { + it("should transfer tBTC tokens to a deposit owner", async () => { await expect(withdrawTx).to.changeTokenBalances( tbtc, [depositor1.address], diff --git a/core/test/stBTC.upgrade.test.ts b/core/test/stBTC.upgrade.test.ts index 3bf77cbc1..f22c3f997 100644 --- a/core/test/stBTC.upgrade.test.ts +++ b/core/test/stBTC.upgrade.test.ts @@ -76,21 +76,23 @@ describe("stBTC contract upgrade", () => { }) describe("upgraded `deposit` function", () => { - let amountToStake: bigint - let staker: HardhatEthersSigner + let amountToDeposit: bigint + let depositOwner: HardhatEthersSigner let tx: ContractTransactionResponse before(async () => { - ;[staker] = await helpers.signers.getUnnamedSigners() - amountToStake = v1MinimumDepositAmount + 1n + ;[depositOwner] = await helpers.signers.getUnnamedSigners() + amountToDeposit = v1MinimumDepositAmount + 1n - await tbtc.mint(staker, amountToStake) + await tbtc.mint(depositOwner, amountToDeposit) await tbtc - .connect(staker) - .approve(await stbtcV2.getAddress(), amountToStake) + .connect(depositOwner) + .approve(await stbtcV2.getAddress(), amountToDeposit) - tx = await stbtcV2.connect(staker).deposit(amountToStake, staker) + tx = await stbtcV2 + .connect(depositOwner) + .deposit(amountToDeposit, depositOwner) }) it("should emit `NewEvent` event", async () => { diff --git a/core/types/index.ts b/core/types/index.ts index e84cb597b..19e2cf0f5 100644 --- a/core/types/index.ts +++ b/core/types/index.ts @@ -1,5 +1,5 @@ /* eslint-disable import/prefer-default-export */ -export enum StakeRequestState { +export enum DepositState { Unknown, Initialized, Finalized, From b4552b1a8fa4410b108940e22c257566447705a5 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 9 Apr 2024 17:42:29 +0200 Subject: [PATCH 51/75] Update stake mentions in docs --- core/contracts/BitcoinRedeemer.sol | 2 +- core/contracts/Dispatcher.sol | 2 +- core/contracts/stBTC.sol | 7 +++---- core/contracts/test/upgrades/stBTCV2.sol | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/core/contracts/BitcoinRedeemer.sol b/core/contracts/BitcoinRedeemer.sol index b3c51ba18..159a81dfd 100644 --- a/core/contracts/BitcoinRedeemer.sol +++ b/core/contracts/BitcoinRedeemer.sol @@ -138,7 +138,7 @@ contract BitcoinRedeemer is Ownable2StepUpgradeable, IReceiveApproval { /// setup. This contract remains upgradable to have flexibility to handle /// adjustments to tBTC Bridge changes. /// @dev Redemption data should include a `redeemer` address matching the - /// address of the staker who is redeeming the shares. In case anything + /// address of the deposit owner who is redeeming the shares. In case anything /// goes wrong during the tBTC unminting process, the redeemer will be /// able to claim the tBTC tokens back from the tBTC Bank contract. /// @param owner The owner of the stBTC tokens. diff --git a/core/contracts/Dispatcher.sol b/core/contracts/Dispatcher.sol index ec1696239..075edadc2 100644 --- a/core/contracts/Dispatcher.sol +++ b/core/contracts/Dispatcher.sol @@ -19,7 +19,7 @@ contract Dispatcher is Router, Ownable2Step { bool authorized; } - /// The main stBTC contract holding tBTC deposited by stakers. + /// The main stBTC contract holding tBTC deposits. stBTC public immutable stbtc; /// tBTC token contract. IERC20 public immutable tbtc; diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index 857ff19a4..17a78ce62 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -13,10 +13,9 @@ import {ZeroAddress} from "./utils/Errors.sol"; /// @title stBTC /// @notice This contract implements the ERC-4626 tokenized vault standard. By /// staking tBTC, users acquire a liquid staking token called stBTC, -/// commonly referred to as "shares". The staked tBTC is securely -/// deposited into Acre's vaults, where it generates yield over time. +/// commonly referred to as "shares". /// Users have the flexibility to redeem stBTC, enabling them to -/// withdraw their staked tBTC along with the accrued yield. +/// withdraw their deposited tBTC along with the accrued yield. /// @dev ERC-4626 is a standard to optimize and unify the technical parameters /// of yield-bearing vaults. This contract facilitates the minting and /// burning of shares (stBTC), which are represented as standard ERC20 @@ -126,7 +125,7 @@ contract stBTC is ERC4626Fees, PausableOwnable { // TODO: Implement a governed upgrade process that initiates an update and // then finalizes it after a delay. /// @notice Updates the dispatcher contract and gives it an unlimited - /// allowance to transfer staked tBTC. + /// allowance to transfer deposited tBTC. /// @param newDispatcher Address of the new dispatcher contract. function updateDispatcher(Dispatcher newDispatcher) external onlyOwner { if (address(newDispatcher) == address(0)) { diff --git a/core/contracts/test/upgrades/stBTCV2.sol b/core/contracts/test/upgrades/stBTCV2.sol index 85c29e96e..2ae996baf 100644 --- a/core/contracts/test/upgrades/stBTCV2.sol +++ b/core/contracts/test/upgrades/stBTCV2.sol @@ -117,7 +117,7 @@ contract stBTCV2 is ERC4626Fees, PausableOwnable { // TODO: Implement a governed upgrade process that initiates an update and // then finalizes it after a delay. /// @notice Updates the dispatcher contract and gives it an unlimited - /// allowance to transfer staked tBTC. + /// allowance to transfer deposited tBTC. /// @param newDispatcher Address of the new dispatcher contract. function updateDispatcher(Dispatcher newDispatcher) external onlyOwner { if (address(newDispatcher) == address(0)) { From 4e813640c57dbe97f1f0e3365d21622661fe025a Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 9 Apr 2024 17:52:59 +0200 Subject: [PATCH 52/75] Update SDK to to replace staker with depositOwner --- dapp/src/web3/relayer-depositor-proxy.ts | 4 +-- sdk/src/lib/contracts/bitcoin-depositor.ts | 10 +++--- sdk/src/lib/ethereum/bitcoin-depositor.ts | 26 ++++++++------- .../modules/staking/stake-initialization.ts | 1 + sdk/test/lib/ethereum/data.ts | 30 +++++++++++------ sdk/test/lib/ethereum/tbtc-depositor.test.ts | 32 +++++++++++-------- 6 files changed, 61 insertions(+), 42 deletions(-) diff --git a/dapp/src/web3/relayer-depositor-proxy.ts b/dapp/src/web3/relayer-depositor-proxy.ts index 6ef83b38e..7207df6f9 100644 --- a/dapp/src/web3/relayer-depositor-proxy.ts +++ b/dapp/src/web3/relayer-depositor-proxy.ts @@ -72,7 +72,7 @@ class RelayerDepositorProxy if (!extraData) throw new Error("Invalid extra data") - const { staker, referral } = + const { depositOwner, referral } = this.#bitcoinDepositor.decodeExtraData(extraData) // TODO: Catch and handle errors + sentry. @@ -81,7 +81,7 @@ class RelayerDepositorProxy { fundingTx, reveal, - staker: `0x${staker.identifierHex}`, + depositOwner: `0x${depositOwner.identifierHex}`, referral, }, ) diff --git a/sdk/src/lib/contracts/bitcoin-depositor.ts b/sdk/src/lib/contracts/bitcoin-depositor.ts index 749f78b73..f221f39ce 100644 --- a/sdk/src/lib/contracts/bitcoin-depositor.ts +++ b/sdk/src/lib/contracts/bitcoin-depositor.ts @@ -5,7 +5,7 @@ import { DepositorProxy } from "./depositor-proxy" export { DepositReceipt } from "@keep-network/tbtc-v2.ts" export type DecodedExtraData = { - staker: ChainIdentifier + depositOwner: ChainIdentifier referral: number } @@ -24,14 +24,14 @@ export interface BitcoinDepositor extends DepositorProxy { getTbtcVaultChainIdentifier(): Promise /** - * Encodes staker address and referral as extra data. - * @param staker The address to which the stBTC shares will be minted. + * Encodes deposit owner address and referral as extra data. + * @param depositOwner The address to which the stBTC shares will be minted. * @param referral Data used for referral program. */ - encodeExtraData(staker: ChainIdentifier, referral: number): Hex + encodeExtraData(depositOwner: ChainIdentifier, referral: number): Hex /** - * Decodes staker address and referral from extra data. + * Decodes depositOwner address and referral from extra data. * @param extraData Encoded extra data. */ decodeExtraData(extraData: string): DecodedExtraData diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index d968ee5b6..60e868680 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -84,12 +84,12 @@ class EthereumBitcoinDepositor if (!extraData) throw new Error("Invalid extra data") - const { staker, referral } = this.decodeExtraData(extraData) + const { depositOwner, referral } = this.decodeExtraData(extraData) - const tx = await this.instance.initializeStake( + const tx = await this.instance.initializeDeposit( fundingTx, reveal, - `0x${staker.identifierHex}`, + `0x${depositOwner.identifierHex}`, referral, ) @@ -98,19 +98,19 @@ class EthereumBitcoinDepositor /** * @see {BitcoinDepositor#encodeExtraData} - * @dev Packs the data to bytes32: 20 bytes of staker address and 2 bytes of + * @dev Packs the data to bytes32: 20 bytes of deposit owner address and 2 bytes of * referral, 10 bytes of trailing zeros. */ // eslint-disable-next-line class-methods-use-this - encodeExtraData(staker: ChainIdentifier, referral: number): Hex { - const stakerAddress = `0x${staker.identifierHex}` + encodeExtraData(depositOwner: ChainIdentifier, referral: number): Hex { + const depositOwnerAddress = `0x${depositOwner.identifierHex}` - if (!isAddress(stakerAddress) || stakerAddress === ZeroAddress) - throw new Error("Invalid staker address") + if (!isAddress(depositOwnerAddress) || depositOwnerAddress === ZeroAddress) + throw new Error("Invalid deposit owner address") const encodedData = solidityPacked( ["address", "uint16"], - [stakerAddress, referral], + [depositOwnerAddress, referral], ) return Hex.from(zeroPadBytes(encodedData, 32)) @@ -118,15 +118,17 @@ class EthereumBitcoinDepositor /** * @see {BitcoinDepositor#decodeExtraData} - * @dev Unpacks the data from bytes32: 20 bytes of staker address and 2 + * @dev Unpacks the data from bytes32: 20 bytes of deposit owner address and 2 * bytes of referral, 10 bytes of trailing zeros. */ // eslint-disable-next-line class-methods-use-this decodeExtraData(extraData: string): DecodedExtraData { - const staker = EthereumAddress.from(getAddress(dataSlice(extraData, 0, 20))) + const depositOwner = EthereumAddress.from( + getAddress(dataSlice(extraData, 0, 20)), + ) const referral = Number(dataSlice(extraData, 20, 22)) - return { staker, referral } + return { depositOwner, referral } } } diff --git a/sdk/src/modules/staking/stake-initialization.ts b/sdk/src/modules/staking/stake-initialization.ts index 869de4452..9ed9b31a5 100644 --- a/sdk/src/modules/staking/stake-initialization.ts +++ b/sdk/src/modules/staking/stake-initialization.ts @@ -27,6 +27,7 @@ type StakeOptions = { backoffStepMs: BackoffRetrierParameters[1] } +// TODO: Rename to `DepositInitialization` to be consistent with the naming. /** * Represents an instance of the staking flow. Staking flow requires a few steps * which should be done to stake BTC. diff --git a/sdk/test/lib/ethereum/data.ts b/sdk/test/lib/ethereum/data.ts index 597364a21..b4deab283 100644 --- a/sdk/test/lib/ethereum/data.ts +++ b/sdk/test/lib/ethereum/data.ts @@ -3,48 +3,60 @@ import { EthereumAddress } from "../../../src" // eslint-disable-next-line import/prefer-default-export export const extraDataValidTestData: { testDescription: string - staker: EthereumAddress + depositOwner: EthereumAddress referral: number extraData: string }[] = [ { - testDescription: "staker has leading zeros", - staker: EthereumAddress.from("0x000055d85E80A49B5930C4a77975d44f012D86C1"), + testDescription: "depositOwner has leading zeros", + depositOwner: EthereumAddress.from( + "0x000055d85E80A49B5930C4a77975d44f012D86C1", + ), referral: 6851, // hex: 0x1ac3 extraData: "0x000055d85e80a49b5930c4a77975d44f012d86c11ac300000000000000000000", }, { - testDescription: "staker has trailing zeros", - staker: EthereumAddress.from("0x2d2F8BC7923F7F806Dc9bb2e17F950b42CfE0000"), + testDescription: "depositOwner has trailing zeros", + depositOwner: EthereumAddress.from( + "0x2d2F8BC7923F7F806Dc9bb2e17F950b42CfE0000", + ), referral: 6851, // hex: 0x1ac3 extraData: "0x2d2f8bc7923f7f806dc9bb2e17f950b42cfe00001ac300000000000000000000", }, { testDescription: "referral is zero", - staker: EthereumAddress.from("0xeb098d6cDE6A202981316b24B19e64D82721e89E"), + depositOwner: EthereumAddress.from( + "0xeb098d6cDE6A202981316b24B19e64D82721e89E", + ), referral: 0, extraData: "0xeb098d6cde6a202981316b24b19e64d82721e89e000000000000000000000000", }, { testDescription: "referral has leading zeros", - staker: EthereumAddress.from("0xeb098d6cDE6A202981316b24B19e64D82721e89E"), + depositOwner: EthereumAddress.from( + "0xeb098d6cDE6A202981316b24B19e64D82721e89E", + ), referral: 31, // hex: 0x001f extraData: "0xeb098d6cde6a202981316b24b19e64d82721e89e001f00000000000000000000", }, { testDescription: "referral has trailing zeros", - staker: EthereumAddress.from("0xeb098d6cDE6A202981316b24B19e64D82721e89E"), + depositOwner: EthereumAddress.from( + "0xeb098d6cDE6A202981316b24B19e64D82721e89E", + ), referral: 19712, // hex: 0x4d00 extraData: "0xeb098d6cde6a202981316b24b19e64d82721e89e4d0000000000000000000000", }, { testDescription: "referral is maximum value", - staker: EthereumAddress.from("0xeb098d6cDE6A202981316b24B19e64D82721e89E"), + depositOwner: EthereumAddress.from( + "0xeb098d6cDE6A202981316b24B19e64D82721e89E", + ), referral: 65535, // max uint16 extraData: "0xeb098d6cde6a202981316b24b19e64d82721e89effff00000000000000000000", diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 7368af30a..5941d0442 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -64,7 +64,7 @@ describe("BitcoinDepositor", () => { }) describe("revealDeposit", () => { - const staker = EthereumAddress.from( + const depositOwner = EthereumAddress.from( "0x000055d85E80A49B5930C4a77975d44f012D86C1", ) const bitcoinFundingTransaction = { @@ -79,11 +79,11 @@ describe("BitcoinDepositor", () => { refundPublicKeyHash: Hex.from("28e081f285138ccbe389c1eb8985716230129f89"), blindingFactor: Hex.from("f9f0c90d00039523"), refundLocktime: Hex.from("60bcea61"), - depositor: staker, + depositor: depositOwner, } describe("when extra data is defined", () => { const extraData = { - staker, + depositOwner, referral: 6851, hex: Hex.from( "0x000055d85e80a49b5930c4a77975d44f012d86c11ac300000000000000000000", @@ -157,7 +157,7 @@ describe("BitcoinDepositor", () => { expect(mockedContractInstance.initializeStake).toHaveBeenCalledWith( btcTxInfo, revealInfo, - `0x${staker.identifierHex}`, + `0x${depositOwner.identifierHex}`, referral, ) expect(result.toPrefixedString()).toBe(mockedTx.toPrefixedString()) @@ -184,20 +184,20 @@ describe("BitcoinDepositor", () => { it.each(extraDataValidTestData)( "$testDescription", - ({ staker, referral, extraData }) => { - const result = depositor.encodeExtraData(staker, referral) + ({ depositOwner, referral, extraData }) => { + const result = depositor.encodeExtraData(depositOwner, referral) expect(spyOnSolidityPacked).toHaveBeenCalledWith( ["address", "uint16"], - [`0x${staker.identifierHex}`, referral], + [`0x${depositOwner.identifierHex}`, referral], ) expect(result.toPrefixedString()).toEqual(extraData) }, ) - describe("when staker is zero address", () => { - const staker = EthereumAddress.from(ZeroAddress) + describe("when deposit owner is zero address", () => { + const depositOwner = EthereumAddress.from(ZeroAddress) beforeEach(() => { spyOnSolidityPacked.mockClear() @@ -205,8 +205,8 @@ describe("BitcoinDepositor", () => { it("should throw an error", () => { expect(() => { - depositor.encodeExtraData(staker, 0) - }).toThrow("Invalid staker address") + depositor.encodeExtraData(depositOwner, 0) + }).toThrow("Invalid deposit owner address") expect(spyOnSolidityPacked).not.toHaveBeenCalled() }) }) @@ -219,8 +219,12 @@ describe("BitcoinDepositor", () => { it.each(extraDataValidTestData)( "$testDescription", - ({ staker: expectedStaker, extraData, referral: expectedReferral }) => { - const { staker, referral } = depositor.decodeExtraData(extraData) + ({ + depositOwner: expectedDepositOwner, + extraData, + referral: expectedReferral, + }) => { + const { depositOwner, referral } = depositor.decodeExtraData(extraData) expect(spyOnEthersDataSlice).toHaveBeenNthCalledWith( 1, @@ -236,7 +240,7 @@ describe("BitcoinDepositor", () => { 22, ) - expect(expectedStaker.equals(staker)).toBeTruthy() + expect(expectedDepositOwner.equals(depositOwner)).toBeTruthy() expect(expectedReferral).toBe(referral) }, ) From 8fc4ab974e4e27d03fb1a6d7da034110a5b16c92 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 9 Apr 2024 17:54:37 +0200 Subject: [PATCH 53/75] Remove TODOs for govern parameters There is no point introducing a govern upgrade process as the contracts are upgradable anyway. --- core/contracts/PausableOwnable.sol | 1 - core/contracts/stBTC.sol | 8 -------- 2 files changed, 9 deletions(-) diff --git a/core/contracts/PausableOwnable.sol b/core/contracts/PausableOwnable.sol index 257b9d222..7e888475b 100644 --- a/core/contracts/PausableOwnable.sol +++ b/core/contracts/PausableOwnable.sol @@ -92,7 +92,6 @@ abstract contract PausableOwnable is /// @param newPauseAdmin New account that can trigger emergency /// stop mechanism. function updatePauseAdmin(address newPauseAdmin) external onlyOwner { - // TODO: Introduce a parameters update process. if (newPauseAdmin == address(0)) { revert ZeroAddress(); } diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index 17a78ce62..004fd06f9 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -97,7 +97,6 @@ contract stBTC is ERC4626Fees, PausableOwnable { /// @notice Updates treasury wallet address. /// @param newTreasury New treasury wallet address. function updateTreasury(address newTreasury) external onlyOwner { - // TODO: Introduce a parameters update process. if (newTreasury == address(0)) { revert ZeroAddress(); } @@ -116,14 +115,11 @@ contract stBTC is ERC4626Fees, PausableOwnable { function updateMinimumDepositAmount( uint256 newMinimumDepositAmount ) external onlyOwner { - // TODO: Introduce a parameters update process. minimumDepositAmount = newMinimumDepositAmount; emit MinimumDepositAmountUpdated(newMinimumDepositAmount); } - // TODO: Implement a governed upgrade process that initiates an update and - // then finalizes it after a delay. /// @notice Updates the dispatcher contract and gives it an unlimited /// allowance to transfer deposited tBTC. /// @param newDispatcher Address of the new dispatcher contract. @@ -150,8 +146,6 @@ contract stBTC is ERC4626Fees, PausableOwnable { IERC20(asset()).forceApprove(address(dispatcher), type(uint256).max); } - // TODO: Implement a governed upgrade process that initiates an update and - // then finalizes it after a delay. /// @notice Update the entry fee basis points. /// @param newEntryFeeBasisPoints New value of the fee basis points. function updateEntryFeeBasisPoints( @@ -162,8 +156,6 @@ contract stBTC is ERC4626Fees, PausableOwnable { emit EntryFeeBasisPointsUpdated(newEntryFeeBasisPoints); } - // TODO: Implement a governed upgrade process that initiates an update and - // then finalizes it after a delay. /// @notice Update the exit fee basis points. /// @param newExitFeeBasisPoints New value of the fee basis points. function updateExitFeeBasisPoints( From 89feb846d13af65adcf21a7f87e1cf2f2b5e7876 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 9 Apr 2024 17:55:36 +0200 Subject: [PATCH 54/75] Remove TODO from updateDispatcher We no longer expect the vaults and tokens to be owned by the dispatcher. --- core/contracts/stBTC.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index 004fd06f9..d456735a0 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -133,10 +133,6 @@ contract stBTC is ERC4626Fees, PausableOwnable { emit DispatcherUpdated(oldDispatcher, address(newDispatcher)); dispatcher = newDispatcher; - // TODO: Once withdrawal/rebalancing is implemented, we need to revoke the - // approval of the vaults share tokens from the old dispatcher and approve - // a new dispatcher to manage the share tokens. - if (oldDispatcher != address(0)) { // Setting allowance to zero for the old dispatcher IERC20(asset()).forceApprove(oldDispatcher, 0); From c6acdcea3ba13ac382fa094960d31071be4cf46a Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 9 Apr 2024 18:02:41 +0200 Subject: [PATCH 55/75] Fix BitcoinDepositor tests in SDK --- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 5941d0442..11aab167c 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -21,7 +21,7 @@ describe("BitcoinDepositor", () => { const mockedContractInstance = { tbtcVault: jest.fn().mockImplementation(() => vaultAddress.identifierHex), - initializeStake: jest.fn(), + initializeDeposit: jest.fn(), } let depositor: EthereumBitcoinDepositor let depositorAddress: EthereumAddress @@ -103,7 +103,7 @@ describe("BitcoinDepositor", () => { let result: Hex beforeAll(async () => { - mockedContractInstance.initializeStake.mockReturnValue({ + mockedContractInstance.initializeDeposit.mockReturnValue({ hash: mockedTx.toPrefixedString(), }) @@ -135,7 +135,7 @@ describe("BitcoinDepositor", () => { ) }) - it("should initialize stake request", () => { + it("should initialize deposit", () => { const btcTxInfo = { version: bitcoinFundingTransaction.version.toPrefixedString(), inputVector: bitcoinFundingTransaction.inputs.toPrefixedString(), @@ -154,7 +154,7 @@ describe("BitcoinDepositor", () => { vault: `0x${vaultAddress.identifierHex}`, } - expect(mockedContractInstance.initializeStake).toHaveBeenCalledWith( + expect(mockedContractInstance.initializeDeposit).toHaveBeenCalledWith( btcTxInfo, revealInfo, `0x${depositOwner.identifierHex}`, From 7a0962d4704bdf7e2d1eb503923dda684eda559d Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 9 Apr 2024 18:10:38 +0200 Subject: [PATCH 56/75] Udpate MezoAllocator test upgrade contract --- .../test/upgrades/MezoAllocatorV2.sol | 252 +++++++++++------- core/test/MezoAllocator.upgrade.test.ts | 6 +- 2 files changed, 153 insertions(+), 105 deletions(-) diff --git a/core/contracts/test/upgrades/MezoAllocatorV2.sol b/core/contracts/test/upgrades/MezoAllocatorV2.sol index e5e862e07..d809cf194 100644 --- a/core/contracts/test/upgrades/MezoAllocatorV2.sol +++ b/core/contracts/test/upgrades/MezoAllocatorV2.sol @@ -4,69 +4,107 @@ pragma solidity ^0.8.21; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {ZeroAddress} from "../../utils/Errors.sol"; +import "../../stBTC.sol"; +import "../../interfaces/IDispatcher.sol"; +/// @title IMezoPortal +/// @dev Interface for the Mezo's Portal contract. interface IMezoPortal { + /// @notice DepositInfo keeps track of the deposit balance and unlock time. + /// Each deposit is tracked separately and associated with a specific + /// token. Some tokens can be deposited but can not be locked - in + /// that case the unlockAt is the block timestamp of when the deposit + /// was created. The same is true for tokens that can be locked but + /// the depositor decided not to lock them. + struct DepositInfo { + uint96 balance; + uint32 unlockAt; + } + + /// @notice Deposit and optionally lock tokens for the given period. + /// @dev Lock period will be normalized to weeks. If non-zero, it must not + /// be shorter than the minimum lock period and must not be longer than + /// the maximum lock period. + /// @param token token address to deposit + /// @param amount amount of tokens to deposit + /// @param lockPeriod lock period in seconds, 0 to not lock the deposit function deposit(address token, uint96 amount, uint32 lockPeriod) external; + /// @notice Withdraw deposited tokens. + /// Deposited lockable tokens can be withdrawn at any time if + /// there is no lock set on the deposit or the lock period has passed. + /// There is no way to withdraw locked deposit. Tokens that are not + /// lockable can be withdrawn at any time. Deposit can be withdrawn + /// partially. + /// @param token deposited token address + /// @param depositId id of the deposit + /// @param amount amount of the token to be withdrawn from the deposit function withdraw(address token, uint256 depositId, uint96 amount) external; + /// @notice The number of deposits created. Includes the deposits that + /// were fully withdrawn. This is also the identifier of the most + /// recently created deposit. function depositCount() external view returns (uint256); + + /// @notice Get the balance and unlock time of a given deposit. + /// @param depositor depositor address + /// @param token token address to get the balance + /// @param depositId id of the deposit + function getDeposit( + address depositor, + address token, + uint256 depositId + ) external view returns (DepositInfo memory); } -/// @dev This is a contract used to test stBTC upgradeability. It is a copy of -/// stBTC contract with some differences marked with `TEST:` comments. -contract MezoAllocatorV2 is Ownable2StepUpgradeable { +/// @notice MezoAllocator routes tBTC to/from MezoPortal. +contract MezoAllocatorV2 is IDispatcher, Ownable2StepUpgradeable { using SafeERC20 for IERC20; - /// @notice DepositInfo keeps track of the deposit Id, deposit balance, - /// creation time, and unlock time. - struct DepositInfo { - uint256 id; - uint96 balance; - uint32 createdAt; - uint32 unlockAt; - } - - /// Address of the MezoPortal contract. - address public mezoPortal; - /// tBTC token contract. + /// @notice Address of the MezoPortal contract. + IMezoPortal public mezoPortal; + /// @notice tBTC token contract. IERC20 public tbtc; - /// Contract holding tBTC deposited by stakers. - address public tbtcStorage; - - /// @notice Maintainer address which can trigger deposit flow. - address public maintainer; - - /// @notice keeps track of the deposit info. - DepositInfo public depositInfo; + /// @notice stBTC token vault contract. + stBTC public stbtc; + /// @notice Keeps track of the addresses that are allowed to trigger deposit + /// allocations. + mapping(address => bool) public isMaintainer; + /// @notice List of maintainers. + address[] public maintainers; + /// @notice keeps track of the latest deposit ID assigned in Mezo Portal. + uint256 public depositId; + /// @notice Keeps track of the total amount of tBTC allocated to MezoPortal. + uint96 public depositBalance; // TEST: New variable. uint256 public newVariable; - /// Emitted when tBTC is deposited to MezoPortal. + /// @notice Emitted when tBTC is deposited to MezoPortal. event DepositAllocated( uint256 indexed oldDepositId, uint256 indexed newDepositId, - uint256 amount + uint256 addedAmount, + uint256 newDepositAmount ); - - /// @notice Emitted when the tBTC storage address is updated. - event TbtcStorageUpdated(address indexed tbtcStorage); - + /// @notice Emitted when tBTC is withdrawn from MezoPortal. + event DepositWithdrawn(uint256 indexed depositId, uint256 amount); /// @notice Emitted when the maintainer address is updated. - event MaintainerUpdated(address indexed maintainer); - + event MaintainerAdded(address indexed maintainer); + /// @notice Emitted when the maintainer address is updated. + event MaintainerRemoved(address indexed maintainer); // TEST: New event. event NewEvent(); - /// @notice Reverts if the caller is not an authorized account. error NotAuthorized(); + /// @notice Reverts if the caller is not a maintainer. + error MaintainerNotRegistered(); + /// @notice Reverts if the caller is already a maintainer. + error MaintainerAlreadyRegistered(); - /// @notice Reverts if the address is 0. - error ZeroAddress(); - - modifier onlyMaintainerAndOwner() { - if (msg.sender != maintainer && owner() != msg.sender) { + modifier onlyMaintainer() { + if (!isMaintainer[msg.sender]) { revert NotAuthorized(); } _; @@ -80,7 +118,11 @@ contract MezoAllocatorV2 is Ownable2StepUpgradeable { /// @notice Initializes the MezoAllocator contract. /// @param _mezoPortal Address of the MezoPortal contract. /// @param _tbtc Address of the tBTC token contract. - function initialize(address _mezoPortal, IERC20 _tbtc) public initializer { + function initialize( + address _mezoPortal, + address _tbtc, + address _stbtc + ) public initializer { // TEST: Removed content of initialize function. Initialize shouldn't be // called again during the upgrade because of the `initializer` // modifier. @@ -95,88 +137,94 @@ contract MezoAllocatorV2 is Ownable2StepUpgradeable { /// deposit meaning that the previous Acre's deposit is fully withdrawn /// before a new deposit with added amount is created. This mimics a /// "top up" functionality with the difference that a new deposit id - /// is created and the previous deposit id is no longer used. - /// @dev This function can be invoked periodically by a bot. - /// @param amount Amount of tBTC to deposit to Mezo Portal. - function allocate(uint96 amount) external onlyMaintainerAndOwner { - // Free all Acre's tBTC from MezoPortal before creating a new deposit. - free(); - // slither-disable-next-line arbitrary-send-erc20 - IERC20(tbtc).safeTransferFrom(tbtcStorage, address(this), amount); - - // Add freed tBTC from the previous deposit and add the new amount. - uint96 newBalance = depositInfo.balance + amount; + /// is created and the previous deposit id is no longer in use. + /// @dev This function can be invoked periodically by a maintainer. + function allocate() external onlyMaintainer { + if (depositBalance > 0) { + // Free all Acre's tBTC from MezoPortal before creating a new deposit. + // slither-disable-next-line reentrancy-no-eth + mezoPortal.withdraw(address(tbtc), depositId, depositBalance); + } - IERC20(tbtc).forceApprove(mezoPortal, newBalance); - // 0 denotes no lock period for this deposit. The zero lock time is - // hardcoded as of biz decision. - IMezoPortal(mezoPortal).deposit(address(tbtc), newBalance, 0); + // Fetch unallocated tBTC from stBTC contract. + uint256 addedAmount = tbtc.balanceOf(address(stbtc)); + // slither-disable-next-line arbitrary-send-erc20 + tbtc.safeTransferFrom(address(stbtc), address(this), addedAmount); + + // Create a new deposit in the MezoPortal. + depositBalance = uint96(tbtc.balanceOf(address(this))); + tbtc.forceApprove(address(mezoPortal), depositBalance); + // 0 denotes no lock period for this deposit. + mezoPortal.deposit(address(tbtc), depositBalance, 0); + uint256 oldDepositId = depositId; // MezoPortal doesn't return depositId, so we have to read depositCounter - // which assignes depositId to the current deposit. - uint256 newDepositId = IMezoPortal(mezoPortal).depositCount(); - // slither-disable-next-line reentrancy-benign - uint256 oldDepositId = depositInfo.id; - depositInfo.id = newDepositId; - depositInfo.balance = newBalance; - // solhint-disable-next-line not-rely-on-time - depositInfo.createdAt = uint32(block.timestamp); - // solhint-disable-next-line not-rely-on-time - depositInfo.unlockAt = uint32(block.timestamp); + // which assigns depositId to the current deposit. + depositId = mezoPortal.depositCount(); // slither-disable-next-line reentrancy-events - emit DepositAllocated(oldDepositId, newDepositId, amount); + emit DepositAllocated( + oldDepositId, + depositId, + addedAmount, + depositBalance + ); } - /// @notice Updates the tBTC storage address. - /// @dev At first this is going to be the stBTC contract. Once Acre - /// works with more destinations for tBTC, this will be updated to - /// the new storage contract like AcreDispatcher. - /// @param _tbtcStorage Address of the new tBTC storage. + /// @notice Withdraws tBTC from MezoPortal and transfers it to stBTC. + /// This function can withdraw partial or a full amount of tBTC from + /// MezoPortal for a given deposit id. + /// @param amount Amount of tBTC to withdraw. + function withdraw(uint256 amount) external { + if (msg.sender != address(stbtc)) revert NotAuthorized(); + + emit DepositWithdrawn(depositId, amount); + mezoPortal.withdraw(address(tbtc), depositId, uint96(amount)); + // slither-disable-next-line reentrancy-benign + depositBalance -= uint96(amount); + tbtc.safeTransfer(address(stbtc), amount); + } + + /// @notice Updates the maintainer address. + /// @param maintainerToAdd Address of the new maintainer. // TEST: Modified function. - function updateTbtcStorage(address _tbtcStorage) external onlyOwner { - if (_tbtcStorage == address(0)) { + function addMaintainer(address maintainerToAdd) external onlyOwner { + if (maintainerToAdd == address(0)) { revert ZeroAddress(); } - tbtcStorage = _tbtcStorage; + if (isMaintainer[maintainerToAdd]) { + revert MaintainerAlreadyRegistered(); + } + maintainers.push(maintainerToAdd); + isMaintainer[maintainerToAdd] = true; - emit TbtcStorageUpdated(_tbtcStorage); + emit MaintainerAdded(maintainerToAdd); // TEST: Emit new event. emit NewEvent(); } - /// @notice Updates the maintainer address. - /// @param _maintainer Address of the new maintainer. - function updateMaintainer(address _maintainer) external onlyOwner { - if (_maintainer == address(0)) { - revert ZeroAddress(); + /// @notice Removes the maintainer address. + /// @param maintainerToRemove Address of the maintainer to remove. + function removeMaintainer(address maintainerToRemove) external onlyOwner { + if (!isMaintainer[maintainerToRemove]) { + revert MaintainerNotRegistered(); + } + delete (isMaintainer[maintainerToRemove]); + + for (uint256 i = 0; i < maintainers.length; i++) { + if (maintainers[i] == maintainerToRemove) { + maintainers[i] = maintainers[maintainers.length - 1]; + // slither-disable-next-line costly-loop + maintainers.pop(); + break; + } } - maintainer = _maintainer; - - emit MaintainerUpdated(_maintainer); - } - // TODO: add updatable withdrawer and onlyWithdrawer modifier (stBTC or AcreDispatcher). - /// @notice Withdraws tBTC from MezoPortal and transfers it to stBTC. - function withdraw(uint96 amount) external { - // TODO: Take the last deposit and pull the funds from it (FIFO). - // If not enough funds, take everything from that deposit and - // take the rest from the next deposit id until the amount is - // reached. Delete deposit ids that are empty. - // IMezoPortal(mezoPortal).withdraw(address(tbtc), depositId, amount); - // TODO: update depositsById and deposits data structures. - // IERC20(tbtc).safeTransfer(address(tbtcStorage), amount); + emit MaintainerRemoved(maintainerToRemove); } - /// @notice Withdraw all Acre's tBTC from MezoPortal. - function free() private { - if (depositInfo.balance > 0) { - // slither-disable-next-line reentrancy-no-eth - IMezoPortal(mezoPortal).withdraw( - address(tbtc), - depositInfo.id, - depositInfo.balance - ); - } + /// @notice Returns the total amount of tBTC allocated to MezoPortal. + function totalAssets() external view returns (uint256 totalAmount) { + return depositBalance; } } diff --git a/core/test/MezoAllocator.upgrade.test.ts b/core/test/MezoAllocator.upgrade.test.ts index 4c891e127..b547df11e 100644 --- a/core/test/MezoAllocator.upgrade.test.ts +++ b/core/test/MezoAllocator.upgrade.test.ts @@ -70,17 +70,17 @@ describe("MezoAllocator contract upgrade", () => { await mezoPortal.getAddress(), ) expect(await allocatorV2.tbtc()).to.eq(await tbtc.getAddress()) - expect(await allocatorV2.tbtcStorage()).to.eq(await stbtc.getAddress()) + expect(await allocatorV2.stbtc()).to.eq(await stbtc.getAddress()) }) }) - describe("upgraded `updateTbtcStorage` function", () => { + describe("upgraded `addMaintainer` function", () => { let tx: ContractTransactionResponse before(async () => { const newAddress = await ethers.Wallet.createRandom().getAddress() - tx = await allocatorV2.connect(governance).updateTbtcStorage(newAddress) + tx = await allocatorV2.connect(governance).addMaintainer(newAddress) }) it("should emit `NewEvent` event", async () => { From ca0891ba44c8d162da60315e5131fbcb05d2fbea Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 00:15:03 +0200 Subject: [PATCH 57/75] Adding more tests for Mezo Allocator --- core/contracts/MezoAllocator.sol | 5 + core/test/MezoAllocator.test.ts | 248 ++++++++++++++++++++++++++++++- 2 files changed, 249 insertions(+), 4 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index ebc46b677..edf078132 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -217,4 +217,9 @@ contract MezoAllocator is IDispatcher, Ownable2Step { function totalAssets() external view returns (uint256 totalAmount) { return depositBalance; } + + /// @notice Returns the list of maintainers. + function getMaintainers() external view returns (address[] memory) { + return maintainers; + } } diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index e12c90eeb..33e2057d5 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -3,7 +3,7 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import { expect } from "chai" import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" -import { ContractTransactionResponse } from "ethers" +import { ContractTransactionResponse, ZeroAddress } from "ethers" import { beforeAfterSnapshotWrapper, deployment } from "./helpers" import { @@ -20,11 +20,12 @@ const { getNamedSigners, getUnnamedSigners } = helpers.signers async function fixture() { const { tbtc, stbtc, mezoAllocator, mezoPortal } = await deployment() const { governance, maintainer } = await getNamedSigners() - const [thirdParty] = await getUnnamedSigners() + const [depositor, thirdParty] = await getUnnamedSigners() return { governance, thirdParty, + depositor, maintainer, tbtc, stbtc, @@ -40,11 +41,21 @@ describe("MezoAllocator", () => { let mezoPortal: IMezoPortal let thirdParty: HardhatEthersSigner + let depositor: HardhatEthersSigner let maintainer: HardhatEthersSigner + let governance: HardhatEthersSigner before(async () => { - ;({ thirdParty, maintainer, tbtc, stbtc, mezoAllocator, mezoPortal } = - await loadFixture(fixture)) + ;({ + thirdParty, + depositor, + maintainer, + governance, + tbtc, + stbtc, + mezoAllocator, + mezoPortal, + } = await loadFixture(fixture)) }) describe("allocate", () => { @@ -121,6 +132,235 @@ describe("MezoAllocator", () => { const depositBalance = await mezoAllocator.depositBalance() expect(depositBalance).to.equal(to1e18(11)) }) + + it("should not store any tBTC in Mezo Allocator", async () => { + expect( + await tbtc.balanceOf(await mezoAllocator.getAddress()), + ).to.equal(0) + }) + + it("should not store any tBTC in stBTC", async () => { + expect(await tbtc.balanceOf(await stbtc.getAddress())).to.equal(0) + }) + }) + }) + }) + + describe("withdraw", () => { + beforeAfterSnapshotWrapper() + + context("when a caller is not stBTC", () => { + it("should revert", async () => { + await expect( + mezoAllocator.connect(thirdParty).withdraw(1n), + ).to.be.revertedWithCustomError(mezoAllocator, "NotAuthorized") + }) + }) + + context("when the caller is stBTC contract", () => { + context("when there is no deposit", () => { + it("should revert", async () => { + await expect(stbtc.withdraw(1n, depositor, depositor)) + .to.be.revertedWithCustomError(tbtc, "ERC20InsufficientBalance") + .withArgs(await mezoPortal.getAddress(), 0, 1n) + }) + }) + + context("when there is a deposit", () => { + let tx: ContractTransactionResponse + + before(async () => { + await tbtc.mint(depositor, to1e18(5)) + await tbtc.approve(await stbtc.getAddress(), to1e18(5)) + await stbtc.connect(depositor).deposit(to1e18(5), depositor) + await mezoAllocator.connect(maintainer).allocate() + }) + + context("when the deposit is not fully withdrawn", () => { + before(async () => { + tx = await stbtc.withdraw(to1e18(2), depositor, depositor) + }) + + it("should transfer 2 tBTC back to a depositor", async () => { + await expect(tx).to.changeTokenBalances( + tbtc, + [depositor.address], + [to1e18(2)], + ) + }) + + it("should emit DepositWithdrawn event", async () => { + await expect(tx) + .to.emit(mezoAllocator, "DepositWithdrawn") + .withArgs(1, to1e18(2)) + }) + + it("should decrease tracked deposit balance amount", async () => { + const depositBalance = await mezoAllocator.depositBalance() + expect(depositBalance).to.equal(to1e18(3)) + }) + + it("should decrease Mezo Portal balance", async () => { + expect( + await tbtc.balanceOf(await mezoPortal.getAddress()), + ).to.equal(to1e18(3)) + }) + }) + + context("when the deposit is fully withdrawn", () => { + before(async () => { + tx = await stbtc.withdraw(to1e18(3), depositor, depositor) + }) + + it("should transfer 3 tBTC back to a depositor", async () => { + await expect(tx).to.changeTokenBalances( + tbtc, + [depositor.address], + [to1e18(3)], + ) + }) + + it("should emit DepositWithdrawn event", async () => { + await expect(tx) + .to.emit(mezoAllocator, "DepositWithdrawn") + .withArgs(1, to1e18(3)) + }) + + it("should decrease tracked deposit balance amount to zero", async () => { + const depositBalance = await mezoAllocator.depositBalance() + expect(depositBalance).to.equal(0) + }) + + it("should decrease Mezo Portal balance", async () => { + expect( + await tbtc.balanceOf(await mezoPortal.getAddress()), + ).to.equal(0) + }) + }) + }) + }) + }) + + describe("totalAssets", () => { + beforeAfterSnapshotWrapper() + + context("when there is no deposit", () => { + it("should return 0", async () => { + const totalAssets = await mezoAllocator.totalAssets() + expect(totalAssets).to.equal(0) + }) + }) + + context("when there is a deposit", () => { + before(async () => { + await tbtc.mint(await stbtc.getAddress(), to1e18(5)) + await mezoAllocator.connect(maintainer).allocate() + }) + + it("should return the total assets value", async () => { + const totalAssets = await mezoAllocator.totalAssets() + expect(totalAssets).to.equal(to1e18(5)) + }) + }) + }) + + describe("addMaintainer", () => { + beforeAfterSnapshotWrapper() + + context("when a caller is not a governance", () => { + it("should revert", async () => { + await expect( + mezoAllocator.connect(thirdParty).addMaintainer(depositor.address), + ).to.be.revertedWithCustomError(mezoAllocator, "OwnableUnauthorizedAccount") + }) + }) + + context("when a caller is governance", () => { + context("when a maintainer is added", () => { + let tx: ContractTransactionResponse + + before(async () => { + tx = await mezoAllocator + .connect(governance) + .addMaintainer(thirdParty.address) + }) + + it("should add a maintainer", async () => { + expect(await mezoAllocator.isMaintainer(thirdParty.address)).to.equal( + true, + ) + }) + + it("should emit MaintainerAdded event", async () => { + await expect(tx) + .to.emit(mezoAllocator, "MaintainerAdded") + .withArgs(thirdParty.address) + }) + + it("should add a new maintainer to the list", async () => { + const maintainers = await mezoAllocator.getMaintainers() + expect(maintainers).to.deep.equal([maintainer.address, thirdParty.address]) + }) + + it("should not allow to add the same maintainer twice", async () => { + await expect( + mezoAllocator.connect(governance).addMaintainer(thirdParty.address), + ).to.be.revertedWithCustomError(mezoAllocator, "MaintainerAlreadyRegistered") + }) + + it("should not allow to add a zero address as a maintainer", async () => { + await expect( + mezoAllocator.connect(governance).addMaintainer(ZeroAddress), + ).to.be.revertedWithCustomError(mezoAllocator, "ZeroAddress") + }) + }) + }) + }) + + describe("removeMaintainer", () => { + beforeAfterSnapshotWrapper() + + context("when a caller is not a governance", () => { + it("should revert", async () => { + await expect( + mezoAllocator.connect(thirdParty).removeMaintainer(depositor.address), + ).to.be.revertedWithCustomError(mezoAllocator, "OwnableUnauthorizedAccount") + }) + }) + + context("when a caller is governance", () => { + context("when a maintainer is removed", () => { + let tx: ContractTransactionResponse + + before(async () => { + await mezoAllocator.connect(governance).addMaintainer(thirdParty.address) + tx = await mezoAllocator + .connect(governance) + .removeMaintainer(thirdParty.address) + }) + + it("should remove a maintainer", async () => { + expect(await mezoAllocator.isMaintainer(thirdParty.address)).to.equal( + false, + ) + }) + + it("should emit MaintainerRemoved event", async () => { + await expect(tx) + .to.emit(mezoAllocator, "MaintainerRemoved") + .withArgs(thirdParty.address) + }) + + it("should remove a maintainer from the list", async () => { + const maintainers = await mezoAllocator.getMaintainers() + expect(maintainers).to.deep.equal([maintainer.address]) + }) + + it("should not allow to remove a maintainer twice", async () => { + await expect( + mezoAllocator.connect(governance).removeMaintainer(thirdParty.address), + ).to.be.revertedWithCustomError(mezoAllocator, "MaintainerNotRegistered") + }) }) }) }) From 74c8cc8a5f56e62713b30a249470f9de6b45dd4e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 00:30:37 +0200 Subject: [PATCH 58/75] Linting --- core/test/MezoAllocator.test.ts | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index 33e2057d5..5d012d664 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -271,7 +271,10 @@ describe("MezoAllocator", () => { it("should revert", async () => { await expect( mezoAllocator.connect(thirdParty).addMaintainer(depositor.address), - ).to.be.revertedWithCustomError(mezoAllocator, "OwnableUnauthorizedAccount") + ).to.be.revertedWithCustomError( + mezoAllocator, + "OwnableUnauthorizedAccount", + ) }) }) @@ -299,13 +302,19 @@ describe("MezoAllocator", () => { it("should add a new maintainer to the list", async () => { const maintainers = await mezoAllocator.getMaintainers() - expect(maintainers).to.deep.equal([maintainer.address, thirdParty.address]) + expect(maintainers).to.deep.equal([ + maintainer.address, + thirdParty.address, + ]) }) it("should not allow to add the same maintainer twice", async () => { await expect( mezoAllocator.connect(governance).addMaintainer(thirdParty.address), - ).to.be.revertedWithCustomError(mezoAllocator, "MaintainerAlreadyRegistered") + ).to.be.revertedWithCustomError( + mezoAllocator, + "MaintainerAlreadyRegistered", + ) }) it("should not allow to add a zero address as a maintainer", async () => { @@ -324,7 +333,10 @@ describe("MezoAllocator", () => { it("should revert", async () => { await expect( mezoAllocator.connect(thirdParty).removeMaintainer(depositor.address), - ).to.be.revertedWithCustomError(mezoAllocator, "OwnableUnauthorizedAccount") + ).to.be.revertedWithCustomError( + mezoAllocator, + "OwnableUnauthorizedAccount", + ) }) }) @@ -333,7 +345,9 @@ describe("MezoAllocator", () => { let tx: ContractTransactionResponse before(async () => { - await mezoAllocator.connect(governance).addMaintainer(thirdParty.address) + await mezoAllocator + .connect(governance) + .addMaintainer(thirdParty.address) tx = await mezoAllocator .connect(governance) .removeMaintainer(thirdParty.address) @@ -358,8 +372,13 @@ describe("MezoAllocator", () => { it("should not allow to remove a maintainer twice", async () => { await expect( - mezoAllocator.connect(governance).removeMaintainer(thirdParty.address), - ).to.be.revertedWithCustomError(mezoAllocator, "MaintainerNotRegistered") + mezoAllocator + .connect(governance) + .removeMaintainer(thirdParty.address), + ).to.be.revertedWithCustomError( + mezoAllocator, + "MaintainerNotRegistered", + ) }) }) }) From 1121527e7750d613962790a6e9608e3ac92146ee Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 00:31:27 +0200 Subject: [PATCH 59/75] Deleting Dispatcher The current implementation of Dispatcher is no longer valid. We need to remove all the related code. --- core/contracts/Dispatcher.sol | 174 --------- core/contracts/Router.sol | 37 -- core/contracts/stBTC.sol | 1 - core/contracts/test/upgrades/stBTCV2.sol | 6 +- core/deploy/02_deploy_dispatcher.ts | 29 -- .../12_mezo_allocator_update_maintainer.ts | 2 +- .../22_transfer_ownership_dispatcher.ts | 31 -- core/test/Dispatcher.test.ts | 342 ------------------ core/test/helpers/context.ts | 4 - 9 files changed, 4 insertions(+), 622 deletions(-) delete mode 100644 core/contracts/Dispatcher.sol delete mode 100644 core/contracts/Router.sol delete mode 100644 core/deploy/02_deploy_dispatcher.ts delete mode 100644 core/deploy/22_transfer_ownership_dispatcher.ts delete mode 100644 core/test/Dispatcher.test.ts diff --git a/core/contracts/Dispatcher.sol b/core/contracts/Dispatcher.sol deleted file mode 100644 index ec1696239..000000000 --- a/core/contracts/Dispatcher.sol +++ /dev/null @@ -1,174 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "@openzeppelin/contracts/access/Ownable2Step.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/interfaces/IERC4626.sol"; -import "./Router.sol"; -import "./stBTC.sol"; - -/// @title Dispatcher -/// @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, Ownable2Step { - using SafeERC20 for IERC20; - - /// Struct holds information about a vault. - struct VaultInfo { - bool authorized; - } - - /// The main stBTC contract holding tBTC deposited by stakers. - stBTC public immutable stbtc; - /// tBTC token contract. - IERC20 public immutable tbtc; - /// Address of the maintainer bot. - address public maintainer; - - /// 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 - /// implemented externally. As long as it complies with ERC4626 - /// standard and is authorized by the owner it can be plugged into - /// Acre. - address[] public vaults; - /// Mapping of vaults to their information. - mapping(address => VaultInfo) public vaultsInfo; - - /// Emitted when a vault is authorized. - /// @param vault Address of the vault. - event VaultAuthorized(address indexed vault); - - /// Emitted when a vault is deauthorized. - /// @param vault Address of the vault. - event VaultDeauthorized(address indexed vault); - - /// Emitted when tBTC is routed to a vault. - /// @param vault Address of the vault. - /// @param amount Amount of tBTC. - /// @param sharesOut Amount of received shares. - event DepositAllocated( - address indexed vault, - uint256 amount, - uint256 sharesOut - ); - - /// Emitted when the maintainer address is updated. - /// @param maintainer Address of the new maintainer. - event MaintainerUpdated(address indexed maintainer); - - /// Reverts if the vault is already authorized. - error VaultAlreadyAuthorized(); - - /// Reverts if the vault is not authorized. - error VaultUnauthorized(); - - /// Reverts if the caller is not the maintainer. - error NotMaintainer(); - - /// Reverts if the address is zero. - error ZeroAddress(); - - /// Modifier that reverts if the caller is not the maintainer. - modifier onlyMaintainer() { - if (msg.sender != maintainer) { - revert NotMaintainer(); - } - _; - } - - constructor(stBTC _stbtc, IERC20 _tbtc) Ownable(msg.sender) { - stbtc = _stbtc; - tbtc = _tbtc; - } - - /// @notice Adds a vault to the list of authorized vaults. - /// @param vault Address of the vault to add. - function authorizeVault(address vault) external onlyOwner { - if (isVaultAuthorized(vault)) { - revert VaultAlreadyAuthorized(); - } - - vaults.push(vault); - vaultsInfo[vault].authorized = true; - - 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 (!isVaultAuthorized(vault)) { - revert VaultUnauthorized(); - } - - vaultsInfo[vault].authorized = false; - - for (uint256 i = 0; i < vaults.length; i++) { - if (vaults[i] == vault) { - vaults[i] = vaults[vaults.length - 1]; - // slither-disable-next-line costly-loop - vaults.pop(); - break; - } - } - - emit VaultDeauthorized(vault); - } - - /// @notice Updates the maintainer address. - /// @param newMaintainer Address of the new maintainer. - function updateMaintainer(address newMaintainer) external onlyOwner { - if (newMaintainer == address(0)) { - revert ZeroAddress(); - } - - maintainer = newMaintainer; - - emit MaintainerUpdated(maintainer); - } - - /// TODO: make this function internal once the allocation distribution is - /// implemented - /// @notice Routes tBTC from stBTC 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. - function depositToVault( - address vault, - uint256 amount, - uint256 minSharesOut - ) public onlyMaintainer { - if (!isVaultAuthorized(vault)) { - revert VaultUnauthorized(); - } - - // slither-disable-next-line arbitrary-send-erc20 - tbtc.safeTransferFrom(address(stbtc), address(this), amount); - tbtc.forceApprove(address(vault), amount); - - uint256 sharesOut = deposit( - IERC4626(vault), - address(stbtc), - amount, - minSharesOut - ); - // slither-disable-next-line reentrancy-events - emit DepositAllocated(vault, amount, sharesOut); - } - - /// @notice Returns the list of authorized vaults. - function getVaults() public view returns (address[] memory) { - return vaults; - } - - /// @notice Returns true if the vault is authorized. - /// @param vault Address of the vault to check. - function isVaultAuthorized(address vault) public view returns (bool) { - return vaultsInfo[vault].authorized; - } - - /// TODO: implement redeem() / withdraw() functions -} diff --git a/core/contracts/Router.sol b/core/contracts/Router.sol deleted file mode 100644 index f726264de..000000000 --- a/core/contracts/Router.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.21; - -import "@openzeppelin/contracts/interfaces/IERC4626.sol"; - -/// @title Router -/// @notice Router is a contract that routes tBTC from stBTC to -/// a given vault and back. Vaults supply yield strategies with tBTC that -/// generate yield for Bitcoin holders. -abstract contract Router { - /// Thrown when amount of shares received is below the min set by caller. - /// @param vault Address of the vault. - /// @param sharesOut Amount of received shares. - /// @param minSharesOut Minimum amount of shares expected to receive. - error MinSharesError( - address vault, - uint256 sharesOut, - uint256 minSharesOut - ); - - /// @notice Routes funds from stBTC 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(address(vault), sharesOut, minSharesOut); - } - } -} diff --git a/core/contracts/stBTC.sol b/core/contracts/stBTC.sol index 0b3e27a05..976b67813 100644 --- a/core/contracts/stBTC.sol +++ b/core/contracts/stBTC.sol @@ -5,7 +5,6 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@thesis-co/solidity-contracts/contracts/token/IReceiveApproval.sol"; -import "./Dispatcher.sol"; import "./PausableOwnable.sol"; import "./lib/ERC4626Fees.sol"; import "./interfaces/IDispatcher.sol"; diff --git a/core/contracts/test/upgrades/stBTCV2.sol b/core/contracts/test/upgrades/stBTCV2.sol index 85c29e96e..cb5d655f7 100644 --- a/core/contracts/test/upgrades/stBTCV2.sol +++ b/core/contracts/test/upgrades/stBTCV2.sol @@ -5,9 +5,9 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@thesis-co/solidity-contracts/contracts/token/IReceiveApproval.sol"; -import "../../Dispatcher.sol"; import "../../PausableOwnable.sol"; import "../../lib/ERC4626Fees.sol"; +import "../../interfaces/IDispatcher.sol"; import {ZeroAddress} from "../../utils/Errors.sol"; /// @title stBTCV2 @@ -17,7 +17,7 @@ contract stBTCV2 is ERC4626Fees, PausableOwnable { using SafeERC20 for IERC20; /// Dispatcher contract that routes tBTC from stBTC to a given vault and back. - Dispatcher public dispatcher; + IDispatcher public dispatcher; /// Address of the treasury wallet, where fees should be transferred to. address public treasury; @@ -119,7 +119,7 @@ contract stBTCV2 is ERC4626Fees, PausableOwnable { /// @notice Updates the dispatcher contract and gives it an unlimited /// allowance to transfer staked tBTC. /// @param newDispatcher Address of the new dispatcher contract. - function updateDispatcher(Dispatcher newDispatcher) external onlyOwner { + function updateDispatcher(IDispatcher newDispatcher) external onlyOwner { if (address(newDispatcher) == address(0)) { revert ZeroAddress(); } diff --git a/core/deploy/02_deploy_dispatcher.ts b/core/deploy/02_deploy_dispatcher.ts deleted file mode 100644 index 4473531e9..000000000 --- a/core/deploy/02_deploy_dispatcher.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { HardhatRuntimeEnvironment } from "hardhat/types" -import type { DeployFunction } from "hardhat-deploy/types" -import { waitConfirmationsNumber } from "../helpers/deployment" - -const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const { getNamedAccounts, deployments, helpers } = hre - const { deployer } = await getNamedAccounts() - - const tbtc = await deployments.get("TBTC") - const stbtc = await deployments.get("stBTC") - - 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(dispatcher) - } - - // TODO: Add Tenderly verification -} - -export default func - -func.tags = ["Dispatcher"] -func.dependencies = ["stBTC"] diff --git a/core/deploy/12_mezo_allocator_update_maintainer.ts b/core/deploy/12_mezo_allocator_update_maintainer.ts index aa7cec484..1be91a2a9 100644 --- a/core/deploy/12_mezo_allocator_update_maintainer.ts +++ b/core/deploy/12_mezo_allocator_update_maintainer.ts @@ -21,4 +21,4 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { export default func func.tags = ["MezoAllocatorUpdateMaintainer"] -func.dependencies = ["Dispatcher"] +func.dependencies = ["MezoAllocator"] diff --git a/core/deploy/22_transfer_ownership_dispatcher.ts b/core/deploy/22_transfer_ownership_dispatcher.ts deleted file mode 100644 index 427cf388e..000000000 --- a/core/deploy/22_transfer_ownership_dispatcher.ts +++ /dev/null @@ -1,31 +0,0 @@ -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, governance } = await getNamedAccounts() - const { log } = deployments - - log(`transferring ownership of Dispatcher contract to ${governance}`) - - await deployments.execute( - "Dispatcher", - { from: deployer, log: true, waitConfirmations: 1 }, - "transferOwnership", - governance, - ) - - if (hre.network.name !== "mainnet") { - await deployments.execute( - "Dispatcher", - { from: governance, log: true, waitConfirmations: 1 }, - "acceptOwnership", - ) - } -} - -export default func - -func.tags = ["TransferOwnershipDispatcher"] -func.dependencies = ["Dispatcher"] -func.runAtTheEnd = true diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts deleted file mode 100644 index dfe7dba92..000000000 --- a/core/test/Dispatcher.test.ts +++ /dev/null @@ -1,342 +0,0 @@ -import { ethers, helpers } from "hardhat" -import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" -import { expect } from "chai" -import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" - -import { ContractTransactionResponse, ZeroAddress } from "ethers" -import { - beforeAfterEachSnapshotWrapper, - beforeAfterSnapshotWrapper, - deployment, -} from "./helpers" - -import { - Dispatcher, - TestERC4626, - StBTC as stBTC, - TestERC20, -} from "../typechain" - -import { to1e18 } from "./utils" - -const { getNamedSigners, getUnnamedSigners } = helpers.signers - -async function fixture() { - const { tbtc, stbtc, dispatcher, vault } = await deployment() - const { governance, maintainer } = await getNamedSigners() - const [thirdParty] = await getUnnamedSigners() - - return { dispatcher, governance, thirdParty, maintainer, vault, tbtc, stbtc } -} -// TODO: Remove these tests once Distpather contract is removed from the project. -describe.skip("Dispatcher", () => { - let dispatcher: Dispatcher - let vault: TestERC4626 - let tbtc: TestERC20 - let stbtc: stBTC - - let governance: HardhatEthersSigner - let thirdParty: HardhatEthersSigner - let maintainer: HardhatEthersSigner - let vaultAddress1: string - let vaultAddress2: string - let vaultAddress3: string - let vaultAddress4: string - - before(async () => { - ;({ dispatcher, governance, thirdParty, maintainer, vault, tbtc, stbtc } = - await loadFixture(fixture)) - - vaultAddress1 = await ethers.Wallet.createRandom().getAddress() - vaultAddress2 = await ethers.Wallet.createRandom().getAddress() - vaultAddress3 = await ethers.Wallet.createRandom().getAddress() - vaultAddress4 = await ethers.Wallet.createRandom().getAddress() - }) - - describe("authorizeVault", () => { - beforeAfterSnapshotWrapper() - - context("when caller is not a governance account", () => { - beforeAfterSnapshotWrapper() - - it("should revert when adding a vault", async () => { - await expect( - dispatcher.connect(thirdParty).authorizeVault(vaultAddress1), - ) - .to.be.revertedWithCustomError( - dispatcher, - "OwnableUnauthorizedAccount", - ) - .withArgs(thirdParty.address) - }) - }) - - context("when caller is a governance account", () => { - beforeAfterSnapshotWrapper() - - let tx: ContractTransactionResponse - - before(async () => { - tx = await dispatcher.connect(governance).authorizeVault(vaultAddress1) - await dispatcher.connect(governance).authorizeVault(vaultAddress2) - await dispatcher.connect(governance).authorizeVault(vaultAddress3) - }) - - it("should authorize vaults", async () => { - expect(await dispatcher.vaults(0)).to.equal(vaultAddress1) - expect(await dispatcher.vaultsInfo(vaultAddress1)).to.be.equal(true) - - expect(await dispatcher.vaults(1)).to.equal(vaultAddress2) - expect(await dispatcher.vaultsInfo(vaultAddress2)).to.be.equal(true) - - expect(await dispatcher.vaults(2)).to.equal(vaultAddress3) - expect(await dispatcher.vaultsInfo(vaultAddress3)).to.be.equal(true) - }) - - it("should not authorize the same vault twice", async () => { - await expect( - dispatcher.connect(governance).authorizeVault(vaultAddress1), - ).to.be.revertedWithCustomError(dispatcher, "VaultAlreadyAuthorized") - }) - - it("should emit an event when adding a vault", async () => { - await expect(tx) - .to.emit(dispatcher, "VaultAuthorized") - .withArgs(vaultAddress1) - }) - }) - }) - - describe("deauthorizeVault", () => { - beforeAfterSnapshotWrapper() - - before(async () => { - await dispatcher.connect(governance).authorizeVault(vaultAddress1) - await dispatcher.connect(governance).authorizeVault(vaultAddress2) - await dispatcher.connect(governance).authorizeVault(vaultAddress3) - }) - - context("when caller is not a governance account", () => { - it("should revert when adding a vault", async () => { - await expect( - dispatcher.connect(thirdParty).deauthorizeVault(vaultAddress1), - ) - .to.be.revertedWithCustomError( - dispatcher, - "OwnableUnauthorizedAccount", - ) - .withArgs(thirdParty.address) - }) - }) - - context("when caller is a governance account", () => { - beforeAfterEachSnapshotWrapper() - - it("should deauthorize vaults", async () => { - await dispatcher.connect(governance).deauthorizeVault(vaultAddress1) - - // Last vault replaced the first vault in the 'vaults' array - expect(await dispatcher.vaults(0)).to.equal(vaultAddress3) - expect(await dispatcher.vaultsInfo(vaultAddress1)).to.be.equal(false) - expect((await dispatcher.getVaults()).length).to.equal(2) - - await dispatcher.connect(governance).deauthorizeVault(vaultAddress2) - - // Last vault (vaultAddress2) was removed from the 'vaults' array - expect(await dispatcher.vaults(0)).to.equal(vaultAddress3) - expect((await dispatcher.getVaults()).length).to.equal(1) - expect(await dispatcher.vaultsInfo(vaultAddress2)).to.be.equal(false) - - await dispatcher.connect(governance).deauthorizeVault(vaultAddress3) - expect((await dispatcher.getVaults()).length).to.equal(0) - expect(await dispatcher.vaultsInfo(vaultAddress3)).to.be.equal(false) - }) - - it("should deauthorize a vault and authorize it again", async () => { - await dispatcher.connect(governance).deauthorizeVault(vaultAddress1) - expect(await dispatcher.vaultsInfo(vaultAddress1)).to.be.equal(false) - - await dispatcher.connect(governance).authorizeVault(vaultAddress1) - expect(await dispatcher.vaultsInfo(vaultAddress1)).to.be.equal(true) - }) - - it("should not deauthorize a vault that is not authorized", async () => { - await expect( - dispatcher.connect(governance).deauthorizeVault(vaultAddress4), - ).to.be.revertedWithCustomError(dispatcher, "VaultUnauthorized") - }) - - it("should emit an event when removing a vault", async () => { - await expect( - dispatcher.connect(governance).deauthorizeVault(vaultAddress1), - ) - .to.emit(dispatcher, "VaultDeauthorized") - .withArgs(vaultAddress1) - }) - }) - }) - - describe("depositToVault", () => { - beforeAfterSnapshotWrapper() - - const assetsToAllocate = to1e18(100) - const minSharesOut = to1e18(100) - - before(async () => { - await dispatcher.connect(governance).authorizeVault(vault.getAddress()) - await tbtc.mint(await stbtc.getAddress(), to1e18(100000)) - }) - - context("when caller is not maintainer", () => { - beforeAfterSnapshotWrapper() - - it("should revert when depositing to a vault", async () => { - await expect( - dispatcher - .connect(thirdParty) - .depositToVault( - await vault.getAddress(), - assetsToAllocate, - minSharesOut, - ), - ).to.be.revertedWithCustomError(dispatcher, "NotMaintainer") - }) - }) - - context("when caller is maintainer", () => { - context("when vault is not authorized", () => { - beforeAfterSnapshotWrapper() - - it("should revert", async () => { - const randomAddress = await ethers.Wallet.createRandom().getAddress() - await expect( - dispatcher - .connect(maintainer) - .depositToVault(randomAddress, assetsToAllocate, minSharesOut), - ).to.be.revertedWithCustomError(dispatcher, "VaultUnauthorized") - }) - }) - - context("when the vault is authorized", () => { - let vaultAddress: string - - before(async () => { - vaultAddress = await vault.getAddress() - }) - - context("when allocation is successful", () => { - beforeAfterSnapshotWrapper() - - let tx: ContractTransactionResponse - - before(async () => { - tx = await dispatcher - .connect(maintainer) - .depositToVault(vaultAddress, assetsToAllocate, minSharesOut) - }) - - it("should deposit tBTC to a vault", async () => { - await expect(tx).to.changeTokenBalances( - tbtc, - [stbtc, vault], - [-assetsToAllocate, assetsToAllocate], - ) - }) - - it("should mint vault's shares for stBTC contract", async () => { - await expect(tx).to.changeTokenBalances( - vault, - [stbtc], - [minSharesOut], - ) - }) - - it("should emit a DepositAllocated event", async () => { - await expect(tx) - .to.emit(dispatcher, "DepositAllocated") - .withArgs(vaultAddress, assetsToAllocate, minSharesOut) - }) - }) - - context( - "when the expected returned shares are less than the actual returned shares", - () => { - beforeAfterSnapshotWrapper() - - const sharesOut = assetsToAllocate - const minShares = to1e18(101) - - it("should emit a MinSharesError event", async () => { - await expect( - dispatcher - .connect(maintainer) - .depositToVault(vaultAddress, assetsToAllocate, minShares), - ) - .to.be.revertedWithCustomError(dispatcher, "MinSharesError") - .withArgs(vaultAddress, sharesOut, minShares) - }) - }, - ) - }) - }) - }) - - describe("updateMaintainer", () => { - beforeAfterSnapshotWrapper() - - let newMaintainer: string - - before(async () => { - newMaintainer = await ethers.Wallet.createRandom().getAddress() - }) - - context("when caller is not an owner", () => { - beforeAfterSnapshotWrapper() - - it("should revert", async () => { - await expect( - dispatcher.connect(thirdParty).updateMaintainer(newMaintainer), - ) - .to.be.revertedWithCustomError( - dispatcher, - "OwnableUnauthorizedAccount", - ) - .withArgs(thirdParty.address) - }) - }) - - context("when caller is an owner", () => { - context("when maintainer is a zero address", () => { - beforeAfterSnapshotWrapper() - - it("should revert", async () => { - await expect( - dispatcher.connect(governance).updateMaintainer(ZeroAddress), - ).to.be.revertedWithCustomError(dispatcher, "ZeroAddress") - }) - }) - - context("when maintainer is not a zero address", () => { - beforeAfterSnapshotWrapper() - - let tx: ContractTransactionResponse - - before(async () => { - tx = await dispatcher - .connect(governance) - .updateMaintainer(newMaintainer) - }) - - it("should update the maintainer", async () => { - expect(await dispatcher.maintainer()).to.be.equal(newMaintainer) - }) - - it("should emit an event when updating the maintainer", async () => { - await expect(tx) - .to.emit(dispatcher, "MaintainerUpdated") - .withArgs(newMaintainer) - }) - }) - }) - }) -}) diff --git a/core/test/helpers/context.ts b/core/test/helpers/context.ts index 68584f5ae..a9471f738 100644 --- a/core/test/helpers/context.ts +++ b/core/test/helpers/context.ts @@ -3,7 +3,6 @@ import { getDeployedContract } from "./contract" import type { StBTC as stBTC, - Dispatcher, BridgeStub, TestERC4626, TBTCVaultStub, @@ -28,8 +27,6 @@ export async function deployment() { const tbtcBridge: BridgeStub = await getDeployedContract("Bridge") const tbtcVault: TBTCVaultStub = await getDeployedContract("TBTCVault") - const dispatcher: Dispatcher = await getDeployedContract("Dispatcher") - const vault: TestERC4626 = await getDeployedContract("Vault") const mezoAllocator: MezoAllocator = await getDeployedContract("MezoAllocator") @@ -42,7 +39,6 @@ export async function deployment() { bitcoinRedeemer, tbtcBridge, tbtcVault, - dispatcher, vault, mezoAllocator, mezoPortal, From 9b45a73a99b879e6fd362553f60de79405abad98 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 10 Apr 2024 11:08:24 +0200 Subject: [PATCH 60/75] Move core/ to solidity/ In the current approach we don't exect external contracts modules to be defined so we replace the core/ directory with solidity/. This is consistent with other mono-repos we have. --- .github/workflows/reusable-sdk-build.yaml | 12 ++-- ...uild.yaml => reusable-solidity-build.yaml} | 16 ++--- .github/workflows/sdk.yaml | 12 ++-- .../workflows/{core.yaml => solidity.yaml} | 62 +++++++++---------- .pre-commit-config.yaml | 29 +++++---- .prettierignore | 2 +- README.md | 4 +- netlify.toml | 8 +-- pnpm-workspace.yaml | 2 +- sdk/package.json | 2 +- sdk/src/lib/ethereum/bitcoin-depositor.ts | 2 +- sdk/src/lib/ethereum/stbtc.ts | 2 +- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 2 +- {core => solidity}/.env | 0 {core => solidity}/.env.example | 0 {core => solidity}/.eslintignore | 0 {core => solidity}/.eslintrc | 0 {core => solidity}/.gitignore | 0 {core => solidity}/.mocharc.json | 0 {core => solidity}/.nvmrc | 0 {core => solidity}/.prettierignore | 0 {core => solidity}/.prettierrc.js | 0 {core => solidity}/.solhint.json | 0 {core => solidity}/.solhintignore | 0 {core => solidity}/.tsconfig-eslint.json | 0 {core => solidity}/README.md | 0 .../contracts/BitcoinDepositor.sol | 0 .../contracts/BitcoinRedeemer.sol | 0 .../contracts/MezoAllocator.sol | 0 .../contracts/PausableOwnable.sol | 0 .../contracts/bridge/ITBTCToken.sol | 0 .../contracts/interfaces/IDispatcher.sol | 0 .../contracts/lib/ERC4626Fees.sol | 0 {core => solidity}/contracts/stBTC.sol | 0 .../test/BitcoinDepositorHarness.sol | 0 .../contracts/test/MezoPortalStub.sol | 0 .../contracts/test/TestERC20.sol | 0 .../contracts/test/TestERC4626.sol | 0 .../contracts/test/TestTBTC.sol | 0 .../test/upgrades/BitcoinDepositorV2.sol | 0 .../contracts/test/upgrades/stBTCV2.sol | 0 {core => solidity}/contracts/utils/Errors.sol | 0 .../deploy/00_resolve_mezo_portal.ts | 0 .../deploy/00_resolve_tbtc_bridge.ts | 0 .../deploy/00_resolve_tbtc_token.ts | 0 .../deploy/00_resolve_tbtc_vault.ts | 0 .../deploy/00_resolve_testing_erc4626.ts | 0 {core => solidity}/deploy/01_deploy_stbtc.ts | 0 .../deploy/02_deploy_mezo_allocator.ts | 0 .../deploy/03_deploy_bitcoin_depositor.ts | 0 .../deploy/04_deploy_bitcoin_redeemer.ts | 0 .../deploy/11_stbtc_update_dispatcher.ts | 0 .../12_mezo_allocator_update_maintainer.ts | 0 .../13_stbtc_update_minimum_deposit_amount.ts | 0 .../deploy/14_update_pause_admin_stbtc.ts | 0 .../deploy/21_transfer_ownership_stbtc.ts | 0 ...23_transfer_ownership_bitcoin_depositor.ts | 0 .../24_transfer_ownership_bitcoin_redeemer.ts | 0 .../24_transfer_ownership_mezo_allocator.ts | 0 .../deployments/sepolia/.chainId | 0 .../deployments/sepolia/BitcoinDepositor.json | 0 .../49f0432287e96a47d66ba17ae7bf5d96.json | 0 .../b22c277b248ba02f9ec5bf62d176f9ce.json | 0 .../deployments/sepolia/stBTC.json | 0 .../external/mainnet/Bridge.json | 0 .../external/mainnet/MezoPortal.json | 0 {core => solidity}/external/mainnet/TBTC.json | 0 .../external/mainnet/TBTCVault.json | 0 .../external/sepolia/Bridge.json | 0 .../external/sepolia/MezoPortal.json | 0 {core => solidity}/external/sepolia/TBTC.json | 0 .../external/sepolia/TBTCVault.json | 0 {core => solidity}/hardhat.config.ts | 0 {core => solidity}/helpers/address.ts | 0 {core => solidity}/helpers/deployment.ts | 0 {core => solidity}/package.json | 2 +- .../scripts/fetch_external_artifacts.sh | 0 {core => solidity}/slither.config.json | 0 .../test/BitcoinDepositor.test.ts | 0 .../test/BitcoinDepositor.upgrade.test.ts | 0 .../test/BitcoinRedeemer.test.ts | 0 {core => solidity}/test/Deployment.test.ts | 0 {core => solidity}/test/MezoAllocator.test.ts | 0 {core => solidity}/test/data/tbtc.ts | 0 {core => solidity}/test/helpers.test.ts | 0 {core => solidity}/test/helpers/context.ts | 0 {core => solidity}/test/helpers/contract.ts | 0 {core => solidity}/test/helpers/index.ts | 0 {core => solidity}/test/helpers/snapshot.ts | 0 {core => solidity}/test/stBTC.test.ts | 0 {core => solidity}/test/stBTC.upgrade.test.ts | 0 {core => solidity}/test/utils/index.ts | 0 {core => solidity}/test/utils/number.ts | 0 {core => solidity}/tsconfig.export.json | 0 {core => solidity}/tsconfig.json | 0 {core => solidity}/types/index.ts | 0 96 files changed, 78 insertions(+), 79 deletions(-) rename .github/workflows/{reusable-core-build.yaml => reusable-solidity-build.yaml} (69%) rename .github/workflows/{core.yaml => solidity.yaml} (79%) rename {core => solidity}/.env (100%) rename {core => solidity}/.env.example (100%) rename {core => solidity}/.eslintignore (100%) rename {core => solidity}/.eslintrc (100%) rename {core => solidity}/.gitignore (100%) rename {core => solidity}/.mocharc.json (100%) rename {core => solidity}/.nvmrc (100%) rename {core => solidity}/.prettierignore (100%) rename {core => solidity}/.prettierrc.js (100%) rename {core => solidity}/.solhint.json (100%) rename {core => solidity}/.solhintignore (100%) rename {core => solidity}/.tsconfig-eslint.json (100%) rename {core => solidity}/README.md (100%) rename {core => solidity}/contracts/BitcoinDepositor.sol (100%) rename {core => solidity}/contracts/BitcoinRedeemer.sol (100%) rename {core => solidity}/contracts/MezoAllocator.sol (100%) rename {core => solidity}/contracts/PausableOwnable.sol (100%) rename {core => solidity}/contracts/bridge/ITBTCToken.sol (100%) rename {core => solidity}/contracts/interfaces/IDispatcher.sol (100%) rename {core => solidity}/contracts/lib/ERC4626Fees.sol (100%) rename {core => solidity}/contracts/stBTC.sol (100%) rename {core => solidity}/contracts/test/BitcoinDepositorHarness.sol (100%) rename {core => solidity}/contracts/test/MezoPortalStub.sol (100%) rename {core => solidity}/contracts/test/TestERC20.sol (100%) rename {core => solidity}/contracts/test/TestERC4626.sol (100%) rename {core => solidity}/contracts/test/TestTBTC.sol (100%) rename {core => solidity}/contracts/test/upgrades/BitcoinDepositorV2.sol (100%) rename {core => solidity}/contracts/test/upgrades/stBTCV2.sol (100%) rename {core => solidity}/contracts/utils/Errors.sol (100%) rename {core => solidity}/deploy/00_resolve_mezo_portal.ts (100%) rename {core => solidity}/deploy/00_resolve_tbtc_bridge.ts (100%) rename {core => solidity}/deploy/00_resolve_tbtc_token.ts (100%) rename {core => solidity}/deploy/00_resolve_tbtc_vault.ts (100%) rename {core => solidity}/deploy/00_resolve_testing_erc4626.ts (100%) rename {core => solidity}/deploy/01_deploy_stbtc.ts (100%) rename {core => solidity}/deploy/02_deploy_mezo_allocator.ts (100%) rename {core => solidity}/deploy/03_deploy_bitcoin_depositor.ts (100%) rename {core => solidity}/deploy/04_deploy_bitcoin_redeemer.ts (100%) rename {core => solidity}/deploy/11_stbtc_update_dispatcher.ts (100%) rename {core => solidity}/deploy/12_mezo_allocator_update_maintainer.ts (100%) rename {core => solidity}/deploy/13_stbtc_update_minimum_deposit_amount.ts (100%) rename {core => solidity}/deploy/14_update_pause_admin_stbtc.ts (100%) rename {core => solidity}/deploy/21_transfer_ownership_stbtc.ts (100%) rename {core => solidity}/deploy/23_transfer_ownership_bitcoin_depositor.ts (100%) rename {core => solidity}/deploy/24_transfer_ownership_bitcoin_redeemer.ts (100%) rename {core => solidity}/deploy/24_transfer_ownership_mezo_allocator.ts (100%) rename {core => solidity}/deployments/sepolia/.chainId (100%) rename {core => solidity}/deployments/sepolia/BitcoinDepositor.json (100%) rename {core => solidity}/deployments/sepolia/solcInputs/49f0432287e96a47d66ba17ae7bf5d96.json (100%) rename {core => solidity}/deployments/sepolia/solcInputs/b22c277b248ba02f9ec5bf62d176f9ce.json (100%) rename {core => solidity}/deployments/sepolia/stBTC.json (100%) rename {core => solidity}/external/mainnet/Bridge.json (100%) rename {core => solidity}/external/mainnet/MezoPortal.json (100%) rename {core => solidity}/external/mainnet/TBTC.json (100%) rename {core => solidity}/external/mainnet/TBTCVault.json (100%) rename {core => solidity}/external/sepolia/Bridge.json (100%) rename {core => solidity}/external/sepolia/MezoPortal.json (100%) rename {core => solidity}/external/sepolia/TBTC.json (100%) rename {core => solidity}/external/sepolia/TBTCVault.json (100%) rename {core => solidity}/hardhat.config.ts (100%) rename {core => solidity}/helpers/address.ts (100%) rename {core => solidity}/helpers/deployment.ts (100%) rename {core => solidity}/package.json (98%) rename {core => solidity}/scripts/fetch_external_artifacts.sh (100%) rename {core => solidity}/slither.config.json (100%) rename {core => solidity}/test/BitcoinDepositor.test.ts (100%) rename {core => solidity}/test/BitcoinDepositor.upgrade.test.ts (100%) rename {core => solidity}/test/BitcoinRedeemer.test.ts (100%) rename {core => solidity}/test/Deployment.test.ts (100%) rename {core => solidity}/test/MezoAllocator.test.ts (100%) rename {core => solidity}/test/data/tbtc.ts (100%) rename {core => solidity}/test/helpers.test.ts (100%) rename {core => solidity}/test/helpers/context.ts (100%) rename {core => solidity}/test/helpers/contract.ts (100%) rename {core => solidity}/test/helpers/index.ts (100%) rename {core => solidity}/test/helpers/snapshot.ts (100%) rename {core => solidity}/test/stBTC.test.ts (100%) rename {core => solidity}/test/stBTC.upgrade.test.ts (100%) rename {core => solidity}/test/utils/index.ts (100%) rename {core => solidity}/test/utils/number.ts (100%) rename {core => solidity}/tsconfig.export.json (100%) rename {core => solidity}/tsconfig.json (100%) rename {core => solidity}/types/index.ts (100%) diff --git a/.github/workflows/reusable-sdk-build.yaml b/.github/workflows/reusable-sdk-build.yaml index fe0a3d5ab..9c38cee5b 100644 --- a/.github/workflows/reusable-sdk-build.yaml +++ b/.github/workflows/reusable-sdk-build.yaml @@ -3,14 +3,14 @@ on: workflow_call: jobs: - core-build: - uses: ./.github/workflows/reusable-core-build.yaml + solidity-build: + uses: ./.github/workflows/reusable-solidity-build.yaml sdk-build: defaults: run: working-directory: ./sdk - needs: [core-build] + needs: [solidity-build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -24,11 +24,11 @@ jobs: node-version-file: "sdk/.nvmrc" cache: "pnpm" - - name: Download Core Build Artifacts + - name: Download Solidity Build Artifacts uses: actions/download-artifact@v4 with: - name: core-build - path: core/ + name: solidity-build + path: solidity/ - name: Install Dependencies run: pnpm install --prefer-offline --frozen-lockfile diff --git a/.github/workflows/reusable-core-build.yaml b/.github/workflows/reusable-solidity-build.yaml similarity index 69% rename from .github/workflows/reusable-core-build.yaml rename to .github/workflows/reusable-solidity-build.yaml index 18309d41d..08d4b5327 100644 --- a/.github/workflows/reusable-core-build.yaml +++ b/.github/workflows/reusable-solidity-build.yaml @@ -1,13 +1,13 @@ -name: Build the core package +name: Build the Solidity package on: workflow_call: jobs: - build-core: + build-solidity: runs-on: ubuntu-latest defaults: run: - working-directory: ./core + working-directory: ./solidity steps: - uses: actions/checkout@v4 @@ -17,7 +17,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version-file: "core/.nvmrc" + node-version-file: "solidity/.nvmrc" cache: "pnpm" - name: Install Dependencies @@ -29,9 +29,9 @@ jobs: - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: - name: core-build + name: solidity-build path: | - core/build/ - core/cache/ - core/typechain/ + solidity/build/ + solidity/cache/ + solidity/typechain/ if-no-files-found: error diff --git a/.github/workflows/sdk.yaml b/.github/workflows/sdk.yaml index 5898da5ee..f83606fdd 100644 --- a/.github/workflows/sdk.yaml +++ b/.github/workflows/sdk.yaml @@ -31,11 +31,11 @@ jobs: node-version-file: "sdk/.nvmrc" cache: "pnpm" - - name: Download Core Build Artifacts + - name: Download Solidity Build Artifacts uses: actions/download-artifact@v4 with: - name: core-build - path: core/ + name: solidity-build + path: solidity/ - name: Install Dependencies run: pnpm install --prefer-offline --frozen-lockfile @@ -58,11 +58,11 @@ jobs: node-version-file: "sdk/.nvmrc" cache: "pnpm" - - name: Download Core Build Artifacts + - name: Download Solidity Build Artifacts uses: actions/download-artifact@v4 with: - name: core-build - path: core/ + name: solidity-build + path: solidity/ - name: Install Dependencies run: pnpm install --prefer-offline --frozen-lockfile diff --git a/.github/workflows/core.yaml b/.github/workflows/solidity.yaml similarity index 79% rename from .github/workflows/core.yaml rename to .github/workflows/solidity.yaml index 8dbe76ae6..7586edb1a 100644 --- a/.github/workflows/core.yaml +++ b/.github/workflows/solidity.yaml @@ -1,11 +1,11 @@ -name: Core +name: Solidity on: push: branches: - main paths: - - "core/**" + - "solidity/**" pull_request: workflow_dispatch: inputs: @@ -19,14 +19,14 @@ on: defaults: run: - working-directory: ./core + working-directory: ./solidity jobs: - core-build: - uses: ./.github/workflows/reusable-core-build.yaml + solidity-build: + uses: ./.github/workflows/reusable-solidity-build.yaml - core-format: - needs: [core-build] + solidity-format: + needs: [solidity-build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -37,7 +37,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version-file: "core/.nvmrc" + node-version-file: "solidity/.nvmrc" cache: "pnpm" - name: Install Dependencies @@ -46,14 +46,14 @@ jobs: - name: Download Build Artifacts uses: actions/download-artifact@v4 with: - name: core-build - path: core/ + name: solidity-build + path: solidity/ - name: Format run: pnpm run format - core-slither: - needs: [core-build] + solidity-slither: + needs: [solidity-build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -64,7 +64,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version-file: "core/.nvmrc" + node-version-file: "solidity/.nvmrc" cache: "pnpm" - name: Install Dependencies @@ -82,14 +82,14 @@ jobs: - name: Download Build Artifacts uses: actions/download-artifact@v4 with: - name: core-build - path: core/ + name: solidity-build + path: solidity/ - name: Run Slither run: slither --hardhat-ignore-compile . - core-test: - needs: [core-build] + solidity-test: + needs: [solidity-build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -100,7 +100,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version-file: "core/.nvmrc" + node-version-file: "solidity/.nvmrc" cache: "pnpm" - name: Install Dependencies @@ -109,14 +109,14 @@ jobs: - name: Download Build Artifacts uses: actions/download-artifact@v4 with: - name: core-build - path: core/ + name: solidity-build + path: solidity/ - name: Test run: pnpm run test --no-compile - core-deploy-dry-run: - needs: [core-build] + solidity-deploy-dry-run: + needs: [solidity-build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -127,7 +127,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version-file: "core/.nvmrc" + node-version-file: "solidity/.nvmrc" cache: "pnpm" - name: Install Dependencies @@ -136,14 +136,14 @@ jobs: - name: Download Build Artifacts uses: actions/download-artifact@v4 with: - name: core-build - path: core/ + name: solidity-build + path: solidity/ - name: Deploy run: pnpm run deploy --no-compile - core-deploy-testnet: - needs: [core-deploy-dry-run] + solidity-deploy-testnet: + needs: [solidity-deploy-dry-run] if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: @@ -155,7 +155,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version-file: "core/.nvmrc" + node-version-file: "solidity/.nvmrc" cache: "pnpm" - name: Install dependencies @@ -164,8 +164,8 @@ jobs: - name: Download build artifacts uses: actions/download-artifact@v4 with: - name: core-build - path: core/ + name: solidity-build + path: solidity/ - name: Remove existing deployment artifacts for the selected network run: rm -rf deployments/${{ github.event.inputs.environment }} @@ -186,5 +186,5 @@ jobs: with: name: deployed-contracts-${{ github.event.inputs.environment }} path: | - core/deployments/${{ github.event.inputs.environment }} + solidity/deployments/${{ github.event.inputs.environment }} if-no-files-found: error diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fe78facce..f068599f0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,28 +5,28 @@ repos: - id: root-lint name: "lint root" entry: /usr/bin/env bash -c "npm run format" - exclude: (^core/|^dapp/|^website/|^subgraph/) + exclude: (^solidity/|^dapp/|^website/|^subgraph/) language: script description: "Checks code according to the package's linter configuration" - # Core - - id: core-lint-sol - name: "lint core sol" - entry: /usr/bin/env bash -c "npm --prefix ./core/ run lint:sol" - files: ^core/ + # Solidity + - id: solidity-lint-sol + name: "lint solidity sol" + entry: /usr/bin/env bash -c "npm --prefix ./solidity/ run lint:sol" + files: ^solidity/ types: [solidity] language: script description: "Checks solidity code according to the package's linter configuration" - - id: core-lint-js - name: "lint core ts/js" - entry: /usr/bin/env bash -c "npm --prefix ./core/ run lint:js" - files: ^core/ + - id: solidity-lint-js + name: "lint solidity ts/js" + entry: /usr/bin/env bash -c "npm --prefix ./solidity/ run lint:js" + files: ^solidity/ types_or: [ts, javascript] language: script description: "Checks TS/JS code according to the package's linter configuration" - - id: core-lint-config - name: "lint core json/yaml" - entry: /usr/bin/env bash -c "npm --prefix ./core/ run lint:config" - files: ^core/ + - id: solidity-lint-config + name: "lint solidity json/yaml" + entry: /usr/bin/env bash -c "npm --prefix ./solidity/ run lint:config" + files: ^solidity/ types_or: [json, yaml] language: script description: "Checks JSON/YAML code according to the package's linter configuration" @@ -90,4 +90,3 @@ repos: types_or: [json, yaml] language: script description: "Checks JSON/YAML code according to the package's linter configuration" - diff --git a/.prettierignore b/.prettierignore index 4511cab70..931aae2cc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,5 @@ # Packages that have own prettier configuration. -core/ +solidity/ dapp/ website/ sdk/ diff --git a/README.md b/README.md index b159a1fd5..dc7b0f048 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Bitcoin Liquid Staking -[![Core](https://github.com/thesis/acre/actions/workflows/core.yaml/badge.svg?branch=main&event=push)](https://github.com/thesis/acre/actions/workflows/core.yaml) +[![Solidity](https://github.com/thesis/acre/actions/workflows/solidity.yaml/badge.svg?branch=main&event=push)](https://github.com/thesis/acre/actions/workflows/solidity.yaml) ## Development @@ -51,7 +51,7 @@ commands: pre-commit run --all-files # Execute hooks for specific files (e.g. stBTC.sol): -pre-commit run --files ./core/contracts/stBTC.sol +pre-commit run --files ./solidity/contracts/stBTC.sol ``` ### Syncpack diff --git a/netlify.toml b/netlify.toml index 59e82dbf2..aea5fdaae 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,7 +1,7 @@ [build] - # Don't run builds after the changes touching only the listed paths. - ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF ':(exclude).github/' ':(exclude).vscode/' ':(exclude)core/' ':(exclude).git-blame-ignore-revs' ':(exclude).gitignore' ':(exclude).npmrc' ':(exclude).nvmrc' ':(exclude).pre-commit-config.yaml' ':(exclude).prettierignore' ':(exclude).prettierrc.js' ':(exclude).syncpackrc' ':(exclude)LICENSE' ':(exclude)README.md'" +# Don't run builds after the changes touching only the listed paths. +ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF ':(exclude).github/' ':(exclude).vscode/' ':(exclude)solidity/' ':(exclude).git-blame-ignore-revs' ':(exclude).gitignore' ':(exclude).npmrc' ':(exclude).nvmrc' ':(exclude).pre-commit-config.yaml' ':(exclude).prettierignore' ':(exclude).prettierrc.js' ':(exclude).syncpackrc' ':(exclude)LICENSE' ':(exclude)README.md'" [context.production] - # Do not run builds for the production context. - ignore = "exit 0" +# Do not run builds for the production context. +ignore = "exit 0" diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5305eb62f..e4234ab61 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,5 @@ packages: - - core/ + - solidity/ - dapp/ - website/ - sdk/ diff --git a/sdk/package.json b/sdk/package.json index 5a6759438..fbec48325 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@keep-network/tbtc-v2.ts": "2.4.0-dev.3", - "@acre-btc/core": "workspace:*", + "@acre-btc/solidity": "workspace:*", "ethers": "^6.10.0" } } diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index d968ee5b6..a3f74f22c 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -1,5 +1,5 @@ import { packRevealDepositParameters } from "@keep-network/tbtc-v2.ts" -import { BitcoinDepositor as BitcoinDepositorTypechain } from "@acre-btc/core/typechain/contracts/BitcoinDepositor" +import { BitcoinDepositor as BitcoinDepositorTypechain } from "@acre-btc/solidity/typechain/contracts/BitcoinDepositor" import { ZeroAddress, dataSlice, diff --git a/sdk/src/lib/ethereum/stbtc.ts b/sdk/src/lib/ethereum/stbtc.ts index a5087f2a6..8ef5f7187 100644 --- a/sdk/src/lib/ethereum/stbtc.ts +++ b/sdk/src/lib/ethereum/stbtc.ts @@ -1,4 +1,4 @@ -import { StBTC as StBTCTypechain } from "@acre-btc/core/typechain/contracts/StBTC" +import { StBTC as StBTCTypechain } from "@acre-btc/solidity/typechain/contracts/StBTC" import stBTC from "./artifacts/sepolia/stBTC.json" import { EthersContractConfig, diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 7368af30a..1c37cef98 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -33,7 +33,7 @@ describe("BitcoinDepositor", () => { () => mockedContractInstance as unknown as Contract, ) - // TODO: get the address from artifact imported from `core` package. + // TODO: get the address from artifact imported from `solidity` package. depositorAddress = EthereumAddress.from( await ethers.Wallet.createRandom().getAddress(), ) diff --git a/core/.env b/solidity/.env similarity index 100% rename from core/.env rename to solidity/.env diff --git a/core/.env.example b/solidity/.env.example similarity index 100% rename from core/.env.example rename to solidity/.env.example diff --git a/core/.eslintignore b/solidity/.eslintignore similarity index 100% rename from core/.eslintignore rename to solidity/.eslintignore diff --git a/core/.eslintrc b/solidity/.eslintrc similarity index 100% rename from core/.eslintrc rename to solidity/.eslintrc diff --git a/core/.gitignore b/solidity/.gitignore similarity index 100% rename from core/.gitignore rename to solidity/.gitignore diff --git a/core/.mocharc.json b/solidity/.mocharc.json similarity index 100% rename from core/.mocharc.json rename to solidity/.mocharc.json diff --git a/core/.nvmrc b/solidity/.nvmrc similarity index 100% rename from core/.nvmrc rename to solidity/.nvmrc diff --git a/core/.prettierignore b/solidity/.prettierignore similarity index 100% rename from core/.prettierignore rename to solidity/.prettierignore diff --git a/core/.prettierrc.js b/solidity/.prettierrc.js similarity index 100% rename from core/.prettierrc.js rename to solidity/.prettierrc.js diff --git a/core/.solhint.json b/solidity/.solhint.json similarity index 100% rename from core/.solhint.json rename to solidity/.solhint.json diff --git a/core/.solhintignore b/solidity/.solhintignore similarity index 100% rename from core/.solhintignore rename to solidity/.solhintignore diff --git a/core/.tsconfig-eslint.json b/solidity/.tsconfig-eslint.json similarity index 100% rename from core/.tsconfig-eslint.json rename to solidity/.tsconfig-eslint.json diff --git a/core/README.md b/solidity/README.md similarity index 100% rename from core/README.md rename to solidity/README.md diff --git a/core/contracts/BitcoinDepositor.sol b/solidity/contracts/BitcoinDepositor.sol similarity index 100% rename from core/contracts/BitcoinDepositor.sol rename to solidity/contracts/BitcoinDepositor.sol diff --git a/core/contracts/BitcoinRedeemer.sol b/solidity/contracts/BitcoinRedeemer.sol similarity index 100% rename from core/contracts/BitcoinRedeemer.sol rename to solidity/contracts/BitcoinRedeemer.sol diff --git a/core/contracts/MezoAllocator.sol b/solidity/contracts/MezoAllocator.sol similarity index 100% rename from core/contracts/MezoAllocator.sol rename to solidity/contracts/MezoAllocator.sol diff --git a/core/contracts/PausableOwnable.sol b/solidity/contracts/PausableOwnable.sol similarity index 100% rename from core/contracts/PausableOwnable.sol rename to solidity/contracts/PausableOwnable.sol diff --git a/core/contracts/bridge/ITBTCToken.sol b/solidity/contracts/bridge/ITBTCToken.sol similarity index 100% rename from core/contracts/bridge/ITBTCToken.sol rename to solidity/contracts/bridge/ITBTCToken.sol diff --git a/core/contracts/interfaces/IDispatcher.sol b/solidity/contracts/interfaces/IDispatcher.sol similarity index 100% rename from core/contracts/interfaces/IDispatcher.sol rename to solidity/contracts/interfaces/IDispatcher.sol diff --git a/core/contracts/lib/ERC4626Fees.sol b/solidity/contracts/lib/ERC4626Fees.sol similarity index 100% rename from core/contracts/lib/ERC4626Fees.sol rename to solidity/contracts/lib/ERC4626Fees.sol diff --git a/core/contracts/stBTC.sol b/solidity/contracts/stBTC.sol similarity index 100% rename from core/contracts/stBTC.sol rename to solidity/contracts/stBTC.sol diff --git a/core/contracts/test/BitcoinDepositorHarness.sol b/solidity/contracts/test/BitcoinDepositorHarness.sol similarity index 100% rename from core/contracts/test/BitcoinDepositorHarness.sol rename to solidity/contracts/test/BitcoinDepositorHarness.sol diff --git a/core/contracts/test/MezoPortalStub.sol b/solidity/contracts/test/MezoPortalStub.sol similarity index 100% rename from core/contracts/test/MezoPortalStub.sol rename to solidity/contracts/test/MezoPortalStub.sol diff --git a/core/contracts/test/TestERC20.sol b/solidity/contracts/test/TestERC20.sol similarity index 100% rename from core/contracts/test/TestERC20.sol rename to solidity/contracts/test/TestERC20.sol diff --git a/core/contracts/test/TestERC4626.sol b/solidity/contracts/test/TestERC4626.sol similarity index 100% rename from core/contracts/test/TestERC4626.sol rename to solidity/contracts/test/TestERC4626.sol diff --git a/core/contracts/test/TestTBTC.sol b/solidity/contracts/test/TestTBTC.sol similarity index 100% rename from core/contracts/test/TestTBTC.sol rename to solidity/contracts/test/TestTBTC.sol diff --git a/core/contracts/test/upgrades/BitcoinDepositorV2.sol b/solidity/contracts/test/upgrades/BitcoinDepositorV2.sol similarity index 100% rename from core/contracts/test/upgrades/BitcoinDepositorV2.sol rename to solidity/contracts/test/upgrades/BitcoinDepositorV2.sol diff --git a/core/contracts/test/upgrades/stBTCV2.sol b/solidity/contracts/test/upgrades/stBTCV2.sol similarity index 100% rename from core/contracts/test/upgrades/stBTCV2.sol rename to solidity/contracts/test/upgrades/stBTCV2.sol diff --git a/core/contracts/utils/Errors.sol b/solidity/contracts/utils/Errors.sol similarity index 100% rename from core/contracts/utils/Errors.sol rename to solidity/contracts/utils/Errors.sol diff --git a/core/deploy/00_resolve_mezo_portal.ts b/solidity/deploy/00_resolve_mezo_portal.ts similarity index 100% rename from core/deploy/00_resolve_mezo_portal.ts rename to solidity/deploy/00_resolve_mezo_portal.ts diff --git a/core/deploy/00_resolve_tbtc_bridge.ts b/solidity/deploy/00_resolve_tbtc_bridge.ts similarity index 100% rename from core/deploy/00_resolve_tbtc_bridge.ts rename to solidity/deploy/00_resolve_tbtc_bridge.ts diff --git a/core/deploy/00_resolve_tbtc_token.ts b/solidity/deploy/00_resolve_tbtc_token.ts similarity index 100% rename from core/deploy/00_resolve_tbtc_token.ts rename to solidity/deploy/00_resolve_tbtc_token.ts diff --git a/core/deploy/00_resolve_tbtc_vault.ts b/solidity/deploy/00_resolve_tbtc_vault.ts similarity index 100% rename from core/deploy/00_resolve_tbtc_vault.ts rename to solidity/deploy/00_resolve_tbtc_vault.ts diff --git a/core/deploy/00_resolve_testing_erc4626.ts b/solidity/deploy/00_resolve_testing_erc4626.ts similarity index 100% rename from core/deploy/00_resolve_testing_erc4626.ts rename to solidity/deploy/00_resolve_testing_erc4626.ts diff --git a/core/deploy/01_deploy_stbtc.ts b/solidity/deploy/01_deploy_stbtc.ts similarity index 100% rename from core/deploy/01_deploy_stbtc.ts rename to solidity/deploy/01_deploy_stbtc.ts diff --git a/core/deploy/02_deploy_mezo_allocator.ts b/solidity/deploy/02_deploy_mezo_allocator.ts similarity index 100% rename from core/deploy/02_deploy_mezo_allocator.ts rename to solidity/deploy/02_deploy_mezo_allocator.ts diff --git a/core/deploy/03_deploy_bitcoin_depositor.ts b/solidity/deploy/03_deploy_bitcoin_depositor.ts similarity index 100% rename from core/deploy/03_deploy_bitcoin_depositor.ts rename to solidity/deploy/03_deploy_bitcoin_depositor.ts diff --git a/core/deploy/04_deploy_bitcoin_redeemer.ts b/solidity/deploy/04_deploy_bitcoin_redeemer.ts similarity index 100% rename from core/deploy/04_deploy_bitcoin_redeemer.ts rename to solidity/deploy/04_deploy_bitcoin_redeemer.ts diff --git a/core/deploy/11_stbtc_update_dispatcher.ts b/solidity/deploy/11_stbtc_update_dispatcher.ts similarity index 100% rename from core/deploy/11_stbtc_update_dispatcher.ts rename to solidity/deploy/11_stbtc_update_dispatcher.ts diff --git a/core/deploy/12_mezo_allocator_update_maintainer.ts b/solidity/deploy/12_mezo_allocator_update_maintainer.ts similarity index 100% rename from core/deploy/12_mezo_allocator_update_maintainer.ts rename to solidity/deploy/12_mezo_allocator_update_maintainer.ts diff --git a/core/deploy/13_stbtc_update_minimum_deposit_amount.ts b/solidity/deploy/13_stbtc_update_minimum_deposit_amount.ts similarity index 100% rename from core/deploy/13_stbtc_update_minimum_deposit_amount.ts rename to solidity/deploy/13_stbtc_update_minimum_deposit_amount.ts diff --git a/core/deploy/14_update_pause_admin_stbtc.ts b/solidity/deploy/14_update_pause_admin_stbtc.ts similarity index 100% rename from core/deploy/14_update_pause_admin_stbtc.ts rename to solidity/deploy/14_update_pause_admin_stbtc.ts diff --git a/core/deploy/21_transfer_ownership_stbtc.ts b/solidity/deploy/21_transfer_ownership_stbtc.ts similarity index 100% rename from core/deploy/21_transfer_ownership_stbtc.ts rename to solidity/deploy/21_transfer_ownership_stbtc.ts diff --git a/core/deploy/23_transfer_ownership_bitcoin_depositor.ts b/solidity/deploy/23_transfer_ownership_bitcoin_depositor.ts similarity index 100% rename from core/deploy/23_transfer_ownership_bitcoin_depositor.ts rename to solidity/deploy/23_transfer_ownership_bitcoin_depositor.ts diff --git a/core/deploy/24_transfer_ownership_bitcoin_redeemer.ts b/solidity/deploy/24_transfer_ownership_bitcoin_redeemer.ts similarity index 100% rename from core/deploy/24_transfer_ownership_bitcoin_redeemer.ts rename to solidity/deploy/24_transfer_ownership_bitcoin_redeemer.ts diff --git a/core/deploy/24_transfer_ownership_mezo_allocator.ts b/solidity/deploy/24_transfer_ownership_mezo_allocator.ts similarity index 100% rename from core/deploy/24_transfer_ownership_mezo_allocator.ts rename to solidity/deploy/24_transfer_ownership_mezo_allocator.ts diff --git a/core/deployments/sepolia/.chainId b/solidity/deployments/sepolia/.chainId similarity index 100% rename from core/deployments/sepolia/.chainId rename to solidity/deployments/sepolia/.chainId diff --git a/core/deployments/sepolia/BitcoinDepositor.json b/solidity/deployments/sepolia/BitcoinDepositor.json similarity index 100% rename from core/deployments/sepolia/BitcoinDepositor.json rename to solidity/deployments/sepolia/BitcoinDepositor.json diff --git a/core/deployments/sepolia/solcInputs/49f0432287e96a47d66ba17ae7bf5d96.json b/solidity/deployments/sepolia/solcInputs/49f0432287e96a47d66ba17ae7bf5d96.json similarity index 100% rename from core/deployments/sepolia/solcInputs/49f0432287e96a47d66ba17ae7bf5d96.json rename to solidity/deployments/sepolia/solcInputs/49f0432287e96a47d66ba17ae7bf5d96.json diff --git a/core/deployments/sepolia/solcInputs/b22c277b248ba02f9ec5bf62d176f9ce.json b/solidity/deployments/sepolia/solcInputs/b22c277b248ba02f9ec5bf62d176f9ce.json similarity index 100% rename from core/deployments/sepolia/solcInputs/b22c277b248ba02f9ec5bf62d176f9ce.json rename to solidity/deployments/sepolia/solcInputs/b22c277b248ba02f9ec5bf62d176f9ce.json diff --git a/core/deployments/sepolia/stBTC.json b/solidity/deployments/sepolia/stBTC.json similarity index 100% rename from core/deployments/sepolia/stBTC.json rename to solidity/deployments/sepolia/stBTC.json diff --git a/core/external/mainnet/Bridge.json b/solidity/external/mainnet/Bridge.json similarity index 100% rename from core/external/mainnet/Bridge.json rename to solidity/external/mainnet/Bridge.json diff --git a/core/external/mainnet/MezoPortal.json b/solidity/external/mainnet/MezoPortal.json similarity index 100% rename from core/external/mainnet/MezoPortal.json rename to solidity/external/mainnet/MezoPortal.json diff --git a/core/external/mainnet/TBTC.json b/solidity/external/mainnet/TBTC.json similarity index 100% rename from core/external/mainnet/TBTC.json rename to solidity/external/mainnet/TBTC.json diff --git a/core/external/mainnet/TBTCVault.json b/solidity/external/mainnet/TBTCVault.json similarity index 100% rename from core/external/mainnet/TBTCVault.json rename to solidity/external/mainnet/TBTCVault.json diff --git a/core/external/sepolia/Bridge.json b/solidity/external/sepolia/Bridge.json similarity index 100% rename from core/external/sepolia/Bridge.json rename to solidity/external/sepolia/Bridge.json diff --git a/core/external/sepolia/MezoPortal.json b/solidity/external/sepolia/MezoPortal.json similarity index 100% rename from core/external/sepolia/MezoPortal.json rename to solidity/external/sepolia/MezoPortal.json diff --git a/core/external/sepolia/TBTC.json b/solidity/external/sepolia/TBTC.json similarity index 100% rename from core/external/sepolia/TBTC.json rename to solidity/external/sepolia/TBTC.json diff --git a/core/external/sepolia/TBTCVault.json b/solidity/external/sepolia/TBTCVault.json similarity index 100% rename from core/external/sepolia/TBTCVault.json rename to solidity/external/sepolia/TBTCVault.json diff --git a/core/hardhat.config.ts b/solidity/hardhat.config.ts similarity index 100% rename from core/hardhat.config.ts rename to solidity/hardhat.config.ts diff --git a/core/helpers/address.ts b/solidity/helpers/address.ts similarity index 100% rename from core/helpers/address.ts rename to solidity/helpers/address.ts diff --git a/core/helpers/deployment.ts b/solidity/helpers/deployment.ts similarity index 100% rename from core/helpers/deployment.ts rename to solidity/helpers/deployment.ts diff --git a/core/package.json b/solidity/package.json similarity index 98% rename from core/package.json rename to solidity/package.json index 4ed08daac..ec2e9e3c3 100644 --- a/core/package.json +++ b/solidity/package.json @@ -1,5 +1,5 @@ { - "name": "@acre-btc/core", + "name": "@acre-btc/solidity", "version": "0.0.1", "description": "Bitcoin Liquid Staking", "license": "GPL-3.0-only", diff --git a/core/scripts/fetch_external_artifacts.sh b/solidity/scripts/fetch_external_artifacts.sh similarity index 100% rename from core/scripts/fetch_external_artifacts.sh rename to solidity/scripts/fetch_external_artifacts.sh diff --git a/core/slither.config.json b/solidity/slither.config.json similarity index 100% rename from core/slither.config.json rename to solidity/slither.config.json diff --git a/core/test/BitcoinDepositor.test.ts b/solidity/test/BitcoinDepositor.test.ts similarity index 100% rename from core/test/BitcoinDepositor.test.ts rename to solidity/test/BitcoinDepositor.test.ts diff --git a/core/test/BitcoinDepositor.upgrade.test.ts b/solidity/test/BitcoinDepositor.upgrade.test.ts similarity index 100% rename from core/test/BitcoinDepositor.upgrade.test.ts rename to solidity/test/BitcoinDepositor.upgrade.test.ts diff --git a/core/test/BitcoinRedeemer.test.ts b/solidity/test/BitcoinRedeemer.test.ts similarity index 100% rename from core/test/BitcoinRedeemer.test.ts rename to solidity/test/BitcoinRedeemer.test.ts diff --git a/core/test/Deployment.test.ts b/solidity/test/Deployment.test.ts similarity index 100% rename from core/test/Deployment.test.ts rename to solidity/test/Deployment.test.ts diff --git a/core/test/MezoAllocator.test.ts b/solidity/test/MezoAllocator.test.ts similarity index 100% rename from core/test/MezoAllocator.test.ts rename to solidity/test/MezoAllocator.test.ts diff --git a/core/test/data/tbtc.ts b/solidity/test/data/tbtc.ts similarity index 100% rename from core/test/data/tbtc.ts rename to solidity/test/data/tbtc.ts diff --git a/core/test/helpers.test.ts b/solidity/test/helpers.test.ts similarity index 100% rename from core/test/helpers.test.ts rename to solidity/test/helpers.test.ts diff --git a/core/test/helpers/context.ts b/solidity/test/helpers/context.ts similarity index 100% rename from core/test/helpers/context.ts rename to solidity/test/helpers/context.ts diff --git a/core/test/helpers/contract.ts b/solidity/test/helpers/contract.ts similarity index 100% rename from core/test/helpers/contract.ts rename to solidity/test/helpers/contract.ts diff --git a/core/test/helpers/index.ts b/solidity/test/helpers/index.ts similarity index 100% rename from core/test/helpers/index.ts rename to solidity/test/helpers/index.ts diff --git a/core/test/helpers/snapshot.ts b/solidity/test/helpers/snapshot.ts similarity index 100% rename from core/test/helpers/snapshot.ts rename to solidity/test/helpers/snapshot.ts diff --git a/core/test/stBTC.test.ts b/solidity/test/stBTC.test.ts similarity index 100% rename from core/test/stBTC.test.ts rename to solidity/test/stBTC.test.ts diff --git a/core/test/stBTC.upgrade.test.ts b/solidity/test/stBTC.upgrade.test.ts similarity index 100% rename from core/test/stBTC.upgrade.test.ts rename to solidity/test/stBTC.upgrade.test.ts diff --git a/core/test/utils/index.ts b/solidity/test/utils/index.ts similarity index 100% rename from core/test/utils/index.ts rename to solidity/test/utils/index.ts diff --git a/core/test/utils/number.ts b/solidity/test/utils/number.ts similarity index 100% rename from core/test/utils/number.ts rename to solidity/test/utils/number.ts diff --git a/core/tsconfig.export.json b/solidity/tsconfig.export.json similarity index 100% rename from core/tsconfig.export.json rename to solidity/tsconfig.export.json diff --git a/core/tsconfig.json b/solidity/tsconfig.json similarity index 100% rename from core/tsconfig.json rename to solidity/tsconfig.json diff --git a/core/types/index.ts b/solidity/types/index.ts similarity index 100% rename from core/types/index.ts rename to solidity/types/index.ts From db83064d0ddadc26c856e375523575dc3a841199 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 10 Apr 2024 11:12:28 +0200 Subject: [PATCH 61/75] Rename @acre-btc/solidity to @acre-btc/contracts --- sdk/package.json | 2 +- sdk/src/lib/ethereum/bitcoin-depositor.ts | 2 +- sdk/src/lib/ethereum/stbtc.ts | 2 +- solidity/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/package.json b/sdk/package.json index fbec48325..e29b1f487 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@keep-network/tbtc-v2.ts": "2.4.0-dev.3", - "@acre-btc/solidity": "workspace:*", + "@acre-btc/contracts": "workspace:*", "ethers": "^6.10.0" } } diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index a3f74f22c..0facc9bba 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -1,5 +1,5 @@ import { packRevealDepositParameters } from "@keep-network/tbtc-v2.ts" -import { BitcoinDepositor as BitcoinDepositorTypechain } from "@acre-btc/solidity/typechain/contracts/BitcoinDepositor" +import { BitcoinDepositor as BitcoinDepositorTypechain } from "@acre-btc/contracts/typechain/contracts/BitcoinDepositor" import { ZeroAddress, dataSlice, diff --git a/sdk/src/lib/ethereum/stbtc.ts b/sdk/src/lib/ethereum/stbtc.ts index 8ef5f7187..4d3b04e60 100644 --- a/sdk/src/lib/ethereum/stbtc.ts +++ b/sdk/src/lib/ethereum/stbtc.ts @@ -1,4 +1,4 @@ -import { StBTC as StBTCTypechain } from "@acre-btc/solidity/typechain/contracts/StBTC" +import { StBTC as StBTCTypechain } from "@acre-btc/contracts/typechain/contracts/StBTC" import stBTC from "./artifacts/sepolia/stBTC.json" import { EthersContractConfig, diff --git a/solidity/package.json b/solidity/package.json index ec2e9e3c3..6f867cbaf 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,5 +1,5 @@ { - "name": "@acre-btc/solidity", + "name": "@acre-btc/contracts", "version": "0.0.1", "description": "Bitcoin Liquid Staking", "license": "GPL-3.0-only", From 537e79469128b408c5981099540a957970561cd3 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 10 Apr 2024 11:12:50 +0200 Subject: [PATCH 62/75] Fix linting --- .github/workflows/subgraph.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/subgraph.yaml b/.github/workflows/subgraph.yaml index 60586fad4..0460c7a30 100644 --- a/.github/workflows/subgraph.yaml +++ b/.github/workflows/subgraph.yaml @@ -121,4 +121,3 @@ jobs: - name: Tests run: pnpm run test - From f9172fc1e9e9bd1cb710a00e20f15fd29990367a Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 10 Apr 2024 11:19:46 +0200 Subject: [PATCH 63/75] Update pnpm-lock.yaml --- pnpm-lock.yaml | 371 +++++++++++++++++++++---------------------------- 1 file changed, 156 insertions(+), 215 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 122c0166f..a58ff6a75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,121 +18,6 @@ importers: specifier: ^11.2.1 version: 11.2.1 - core: - dependencies: - '@keep-network/bitcoin-spv-sol': - specifier: 3.4.0-solc-0.8 - version: 3.4.0-solc-0.8 - '@keep-network/tbtc-v2': - specifier: development - version: 1.6.0-dev.24(@keep-network/keep-core@1.8.1-dev.0) - '@openzeppelin/contracts': - specifier: ^5.0.0 - version: 5.0.0 - '@openzeppelin/contracts-upgradeable': - specifier: ^5.0.2 - version: 5.0.2(@openzeppelin/contracts@5.0.0) - '@thesis-co/solidity-contracts': - specifier: github:thesis/solidity-contracts#c315b9d - version: github.com/thesis/solidity-contracts/c315b9d - '@types/chai-as-promised': - specifier: ^7.1.8 - version: 7.1.8 - chai-as-promised: - specifier: ^7.1.1 - version: 7.1.1(chai@4.3.10) - devDependencies: - '@keep-network/hardhat-helpers': - specifier: ^0.7.1 - version: 0.7.1(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-verify@2.0.1)(@openzeppelin/hardhat-upgrades@3.0.5)(ethers@6.8.1)(hardhat-deploy@0.11.43)(hardhat@2.19.1) - '@nomicfoundation/hardhat-chai-matchers': - specifier: ^2.0.2 - version: 2.0.2(@nomicfoundation/hardhat-ethers@3.0.5)(chai@4.3.10)(ethers@6.8.1)(hardhat@2.19.1) - '@nomicfoundation/hardhat-ethers': - specifier: ^3.0.5 - version: 3.0.5(ethers@6.8.1)(hardhat@2.19.1) - '@nomicfoundation/hardhat-network-helpers': - specifier: ^1.0.9 - version: 1.0.9(hardhat@2.19.1) - '@nomicfoundation/hardhat-toolbox': - specifier: ^4.0.0 - version: 4.0.0(@nomicfoundation/hardhat-chai-matchers@2.0.2)(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-network-helpers@1.0.9)(@nomicfoundation/hardhat-verify@2.0.1)(@typechain/ethers-v6@0.5.1)(@typechain/hardhat@9.1.0)(@types/chai@4.3.11)(@types/mocha@10.0.6)(@types/node@20.9.4)(chai@4.3.10)(ethers@6.8.1)(hardhat-gas-reporter@1.0.9)(hardhat@2.19.1)(solidity-coverage@0.8.5)(ts-node@10.9.1)(typechain@8.3.2)(typescript@5.3.2) - '@nomicfoundation/hardhat-verify': - specifier: ^2.0.1 - version: 2.0.1(hardhat@2.19.1) - '@nomiclabs/hardhat-etherscan': - specifier: ^3.1.7 - version: 3.1.7(hardhat@2.19.1) - '@openzeppelin/hardhat-upgrades': - specifier: ^3.0.5 - version: 3.0.5(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-verify@2.0.1)(ethers@6.8.1)(hardhat@2.19.1) - '@thesis-co/eslint-config': - specifier: github:thesis/eslint-config#7b9bc8c - version: github.com/thesis/eslint-config/7b9bc8c(eslint@8.54.0)(prettier@3.1.0)(typescript@5.3.2) - '@typechain/ethers-v6': - specifier: ^0.5.1 - version: 0.5.1(ethers@6.8.1)(typechain@8.3.2)(typescript@5.3.2) - '@typechain/hardhat': - specifier: ^9.1.0 - version: 9.1.0(@typechain/ethers-v6@0.5.1)(ethers@6.8.1)(hardhat@2.19.1)(typechain@8.3.2) - '@types/chai': - specifier: ^4.3.11 - version: 4.3.11 - '@types/mocha': - specifier: ^10.0.6 - version: 10.0.6 - '@types/node': - specifier: ^20.9.4 - version: 20.9.4 - chai: - specifier: ^4.3.10 - version: 4.3.10 - eslint: - specifier: ^8.54.0 - version: 8.54.0 - ethers: - specifier: ^6.8.1 - version: 6.8.1 - hardhat: - specifier: ^2.19.1 - version: 2.19.1(ts-node@10.9.1)(typescript@5.3.2) - hardhat-contract-sizer: - specifier: ^2.10.0 - version: 2.10.0(hardhat@2.19.1) - hardhat-deploy: - specifier: ^0.11.43 - version: 0.11.43 - hardhat-gas-reporter: - specifier: ^1.0.9 - version: 1.0.9(hardhat@2.19.1) - prettier: - specifier: ^3.1.0 - version: 3.1.0 - prettier-plugin-solidity: - specifier: ^1.2.0 - version: 1.2.0(prettier@3.1.0) - solhint: - specifier: ^4.0.0 - version: 4.0.0 - solhint-config-thesis: - specifier: github:thesis/solhint-config - version: github.com/thesis/solhint-config/266de12d96d58f01171e20858b855ec80520de8d(solhint@4.0.0) - solidity-coverage: - specifier: ^0.8.5 - version: 0.8.5(hardhat@2.19.1) - solidity-docgen: - specifier: 0.6.0-beta.36 - version: 0.6.0-beta.36(hardhat@2.19.1) - ts-node: - specifier: ^10.9.1 - version: 10.9.1(@types/node@20.9.4)(typescript@5.3.2) - typechain: - specifier: ^8.3.2 - version: 8.3.2(typescript@5.3.2) - typescript: - specifier: ^5.3.2 - version: 5.3.2 - dapp: dependencies: '@acre-btc/sdk': @@ -170,7 +55,7 @@ importers: version: 0.23.13 axios: specifier: ^1.6.7 - version: 1.6.7 + version: 1.6.7(debug@4.3.4) ethers: specifier: ^6.10.0 version: 6.10.0 @@ -244,9 +129,9 @@ importers: sdk: dependencies: - '@acre-btc/core': + '@acre-btc/contracts': specifier: workspace:* - version: link:../core + version: link:../solidity '@keep-network/tbtc-v2.ts': specifier: 2.4.0-dev.3 version: 2.4.0-dev.3(@keep-network/keep-core@1.8.1-dev.0) @@ -285,6 +170,121 @@ importers: specifier: ^5.3.2 version: 5.3.2 + solidity: + dependencies: + '@keep-network/bitcoin-spv-sol': + specifier: 3.4.0-solc-0.8 + version: 3.4.0-solc-0.8 + '@keep-network/tbtc-v2': + specifier: development + version: 1.7.0-dev.4(@keep-network/keep-core@1.8.1-dev.0) + '@openzeppelin/contracts': + specifier: ^5.0.0 + version: 5.0.0 + '@openzeppelin/contracts-upgradeable': + specifier: ^5.0.2 + version: 5.0.2(@openzeppelin/contracts@5.0.0) + '@thesis-co/solidity-contracts': + specifier: github:thesis/solidity-contracts#c315b9d + version: github.com/thesis/solidity-contracts/c315b9d + '@types/chai-as-promised': + specifier: ^7.1.8 + version: 7.1.8 + chai-as-promised: + specifier: ^7.1.1 + version: 7.1.1(chai@4.3.10) + devDependencies: + '@keep-network/hardhat-helpers': + specifier: ^0.7.1 + version: 0.7.1(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-verify@2.0.1)(@openzeppelin/hardhat-upgrades@3.0.5)(ethers@6.10.0)(hardhat-deploy@0.11.43)(hardhat@2.19.1) + '@nomicfoundation/hardhat-chai-matchers': + specifier: ^2.0.2 + version: 2.0.2(@nomicfoundation/hardhat-ethers@3.0.5)(chai@4.3.10)(ethers@6.10.0)(hardhat@2.19.1) + '@nomicfoundation/hardhat-ethers': + specifier: ^3.0.5 + version: 3.0.5(ethers@6.10.0)(hardhat@2.19.1) + '@nomicfoundation/hardhat-network-helpers': + specifier: ^1.0.9 + version: 1.0.9(hardhat@2.19.1) + '@nomicfoundation/hardhat-toolbox': + specifier: ^4.0.0 + version: 4.0.0(@nomicfoundation/hardhat-chai-matchers@2.0.2)(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-network-helpers@1.0.9)(@nomicfoundation/hardhat-verify@2.0.1)(@typechain/ethers-v6@0.5.1)(@typechain/hardhat@9.1.0)(@types/chai@4.3.11)(@types/mocha@10.0.6)(@types/node@20.9.4)(chai@4.3.10)(ethers@6.10.0)(hardhat-gas-reporter@1.0.9)(hardhat@2.19.1)(solidity-coverage@0.8.5)(ts-node@10.9.1)(typechain@8.3.2)(typescript@5.3.2) + '@nomicfoundation/hardhat-verify': + specifier: ^2.0.1 + version: 2.0.1(hardhat@2.19.1) + '@nomiclabs/hardhat-etherscan': + specifier: ^3.1.7 + version: 3.1.7(hardhat@2.19.1) + '@openzeppelin/hardhat-upgrades': + specifier: ^3.0.5 + version: 3.0.5(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-verify@2.0.1)(ethers@6.10.0)(hardhat@2.19.1) + '@thesis-co/eslint-config': + specifier: github:thesis/eslint-config#7b9bc8c + version: github.com/thesis/eslint-config/7b9bc8c(eslint@8.54.0)(prettier@3.1.0)(typescript@5.3.2) + '@typechain/ethers-v6': + specifier: ^0.5.1 + version: 0.5.1(ethers@6.10.0)(typechain@8.3.2)(typescript@5.3.2) + '@typechain/hardhat': + specifier: ^9.1.0 + version: 9.1.0(@typechain/ethers-v6@0.5.1)(ethers@6.10.0)(hardhat@2.19.1)(typechain@8.3.2) + '@types/chai': + specifier: ^4.3.11 + version: 4.3.11 + '@types/mocha': + specifier: ^10.0.6 + version: 10.0.6 + '@types/node': + specifier: ^20.9.4 + version: 20.9.4 + chai: + specifier: ^4.3.10 + version: 4.3.10 + eslint: + specifier: ^8.54.0 + version: 8.54.0 + ethers: + specifier: ^6.8.1 + version: 6.10.0 + hardhat: + specifier: ^2.19.1 + version: 2.19.1(ts-node@10.9.1)(typescript@5.3.2) + hardhat-contract-sizer: + specifier: ^2.10.0 + version: 2.10.0(hardhat@2.19.1) + hardhat-deploy: + specifier: ^0.11.43 + version: 0.11.43 + hardhat-gas-reporter: + specifier: ^1.0.9 + version: 1.0.9(hardhat@2.19.1) + prettier: + specifier: ^3.1.0 + version: 3.1.0 + prettier-plugin-solidity: + specifier: ^1.2.0 + version: 1.2.0(prettier@3.1.0) + solhint: + specifier: ^4.0.0 + version: 4.0.0 + solhint-config-thesis: + specifier: github:thesis/solhint-config + version: github.com/thesis/solhint-config/266de12d96d58f01171e20858b855ec80520de8d(solhint@4.0.0) + solidity-coverage: + specifier: ^0.8.5 + version: 0.8.5(hardhat@2.19.1) + solidity-docgen: + specifier: 0.6.0-beta.36 + version: 0.6.0-beta.36(hardhat@2.19.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.9.4)(typescript@5.3.2) + typechain: + specifier: ^8.3.2 + version: 8.3.2(typescript@5.3.2) + typescript: + specifier: ^5.3.2 + version: 5.3.2 + subgraph: dependencies: '@graphprotocol/graph-cli': @@ -480,7 +480,7 @@ packages: '@babel/traverse': 7.23.4 '@babel/types': 7.23.4 convert-source-map: 2.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1779,7 +1779,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.4 '@babel/types': 7.23.4 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3503,7 +3503,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) espree: 9.6.1 globals: 13.23.0 ignore: 5.3.0 @@ -4158,7 +4158,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 2.0.1 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -4500,7 +4500,7 @@ packages: - '@keep-network/keep-core' dev: false - /@keep-network/hardhat-helpers@0.7.1(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-verify@2.0.1)(@openzeppelin/hardhat-upgrades@3.0.5)(ethers@6.8.1)(hardhat-deploy@0.11.43)(hardhat@2.19.1): + /@keep-network/hardhat-helpers@0.7.1(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-verify@2.0.1)(@openzeppelin/hardhat-upgrades@3.0.5)(ethers@6.10.0)(hardhat-deploy@0.11.43)(hardhat@2.19.1): resolution: {integrity: sha512-de6Gy45JukZwGgZqVuR+Zq5PSqnmvKLDJn0/KrKT5tFzGspARUf1WzhDgTTB/D7gTK04sxlrL6WJM3XQ/wZEkw==} peerDependencies: '@nomicfoundation/hardhat-ethers': ^3.0.5 @@ -4510,10 +4510,10 @@ packages: hardhat: ^2.19.4 hardhat-deploy: ^0.11.45 dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.5(ethers@6.8.1)(hardhat@2.19.1) + '@nomicfoundation/hardhat-ethers': 3.0.5(ethers@6.10.0)(hardhat@2.19.1) '@nomicfoundation/hardhat-verify': 2.0.1(hardhat@2.19.1) - '@openzeppelin/hardhat-upgrades': 3.0.5(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-verify@2.0.1)(ethers@6.8.1)(hardhat@2.19.1) - ethers: 6.8.1 + '@openzeppelin/hardhat-upgrades': 3.0.5(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-verify@2.0.1)(ethers@6.10.0)(hardhat@2.19.1) + ethers: 6.10.0 hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.3.2) hardhat-deploy: 0.11.43 dev: true @@ -4635,8 +4635,8 @@ packages: - utf-8-validate dev: false - /@keep-network/tbtc-v2@1.6.0-dev.24(@keep-network/keep-core@1.8.1-dev.0): - resolution: {integrity: sha512-Nj/TYHlVg5J1ubVlLEbO7IQiqecDmVf2DPOqkICpveFxOiIbqzVtop4CsgEPvyRBomzHDUvZ6QFxYxJj/wbJbA==} + /@keep-network/tbtc-v2@1.7.0-dev.4(@keep-network/keep-core@1.8.1-dev.0): + resolution: {integrity: sha512-+DxR5XebK0DB5WIrQyCQG2osixBYpJhOuwQtLu3EDMsi4tFAPEh5MFjWG5LYeuEtX65p19mSC4Vj69/Z3jMgrA==} engines: {node: '>= 14.0.0'} dependencies: '@keep-network/bitcoin-spv-sol': 3.4.0-solc-0.8 @@ -5128,7 +5128,7 @@ packages: - utf-8-validate dev: true - /@nomicfoundation/hardhat-chai-matchers@2.0.2(@nomicfoundation/hardhat-ethers@3.0.5)(chai@4.3.10)(ethers@6.8.1)(hardhat@2.19.1): + /@nomicfoundation/hardhat-chai-matchers@2.0.2(@nomicfoundation/hardhat-ethers@3.0.5)(chai@4.3.10)(ethers@6.10.0)(hardhat@2.19.1): resolution: {integrity: sha512-9Wu9mRtkj0U9ohgXYFbB/RQDa+PcEdyBm2suyEtsJf3PqzZEEjLUZgWnMjlFhATMk/fp3BjmnYVPrwl+gr8oEw==} peerDependencies: '@nomicfoundation/hardhat-ethers': ^3.0.0 @@ -5136,24 +5136,24 @@ packages: ethers: ^6.1.0 hardhat: ^2.9.4 dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.5(ethers@6.8.1)(hardhat@2.19.1) + '@nomicfoundation/hardhat-ethers': 3.0.5(ethers@6.10.0)(hardhat@2.19.1) '@types/chai-as-promised': 7.1.8 chai: 4.3.10 chai-as-promised: 7.1.1(chai@4.3.10) deep-eql: 4.1.3 - ethers: 6.8.1 + ethers: 6.10.0 hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.3.2) ordinal: 1.0.3 dev: true - /@nomicfoundation/hardhat-ethers@3.0.5(ethers@6.8.1)(hardhat@2.19.1): + /@nomicfoundation/hardhat-ethers@3.0.5(ethers@6.10.0)(hardhat@2.19.1): resolution: {integrity: sha512-RNFe8OtbZK6Ila9kIlHp0+S80/0Bu/3p41HUpaRIoHLm6X3WekTd83vob3rE54Duufu1edCiBDxspBzi2rxHHw==} peerDependencies: ethers: ^6.1.0 hardhat: ^2.0.0 dependencies: debug: 4.3.4(supports-color@8.1.1) - ethers: 6.8.1 + ethers: 6.10.0 hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.3.2) lodash.isequal: 4.5.0 transitivePeerDependencies: @@ -5169,7 +5169,7 @@ packages: hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.3.2) dev: true - /@nomicfoundation/hardhat-toolbox@4.0.0(@nomicfoundation/hardhat-chai-matchers@2.0.2)(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-network-helpers@1.0.9)(@nomicfoundation/hardhat-verify@2.0.1)(@typechain/ethers-v6@0.5.1)(@typechain/hardhat@9.1.0)(@types/chai@4.3.11)(@types/mocha@10.0.6)(@types/node@20.9.4)(chai@4.3.10)(ethers@6.8.1)(hardhat-gas-reporter@1.0.9)(hardhat@2.19.1)(solidity-coverage@0.8.5)(ts-node@10.9.1)(typechain@8.3.2)(typescript@5.3.2): + /@nomicfoundation/hardhat-toolbox@4.0.0(@nomicfoundation/hardhat-chai-matchers@2.0.2)(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-network-helpers@1.0.9)(@nomicfoundation/hardhat-verify@2.0.1)(@typechain/ethers-v6@0.5.1)(@typechain/hardhat@9.1.0)(@types/chai@4.3.11)(@types/mocha@10.0.6)(@types/node@20.9.4)(chai@4.3.10)(ethers@6.10.0)(hardhat-gas-reporter@1.0.9)(hardhat@2.19.1)(solidity-coverage@0.8.5)(ts-node@10.9.1)(typechain@8.3.2)(typescript@5.3.2): resolution: {integrity: sha512-jhcWHp0aHaL0aDYj8IJl80v4SZXWMS1A2XxXa1CA6pBiFfJKuZinCkO6wb+POAt0LIfXB3gA3AgdcOccrcwBwA==} peerDependencies: '@nomicfoundation/hardhat-chai-matchers': ^2.0.0 @@ -5190,17 +5190,17 @@ packages: typechain: ^8.3.0 typescript: '>=4.5.0' dependencies: - '@nomicfoundation/hardhat-chai-matchers': 2.0.2(@nomicfoundation/hardhat-ethers@3.0.5)(chai@4.3.10)(ethers@6.8.1)(hardhat@2.19.1) - '@nomicfoundation/hardhat-ethers': 3.0.5(ethers@6.8.1)(hardhat@2.19.1) + '@nomicfoundation/hardhat-chai-matchers': 2.0.2(@nomicfoundation/hardhat-ethers@3.0.5)(chai@4.3.10)(ethers@6.10.0)(hardhat@2.19.1) + '@nomicfoundation/hardhat-ethers': 3.0.5(ethers@6.10.0)(hardhat@2.19.1) '@nomicfoundation/hardhat-network-helpers': 1.0.9(hardhat@2.19.1) '@nomicfoundation/hardhat-verify': 2.0.1(hardhat@2.19.1) - '@typechain/ethers-v6': 0.5.1(ethers@6.8.1)(typechain@8.3.2)(typescript@5.3.2) - '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1)(ethers@6.8.1)(hardhat@2.19.1)(typechain@8.3.2) + '@typechain/ethers-v6': 0.5.1(ethers@6.10.0)(typechain@8.3.2)(typescript@5.3.2) + '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1)(ethers@6.10.0)(hardhat@2.19.1)(typechain@8.3.2) '@types/chai': 4.3.11 '@types/mocha': 10.0.6 '@types/node': 20.9.4 chai: 4.3.10 - ethers: 6.8.1 + ethers: 6.10.0 hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.3.2) hardhat-gas-reporter: 1.0.9(hardhat@2.19.1) solidity-coverage: 0.8.5(hardhat@2.19.1) @@ -5336,6 +5336,7 @@ packages: /@nomiclabs/hardhat-etherscan@3.1.7(hardhat@2.19.1): resolution: {integrity: sha512-tZ3TvSgpvsQ6B6OGmo1/Au6u8BrAkvs1mIC/eURA3xgIfznUZBhmpne8hv7BXUzw9xNL3fXdpOYgOQlVMTcoHQ==} + deprecated: The @nomiclabs/hardhat-etherscan package is deprecated, please use @nomicfoundation/hardhat-verify instead peerDependencies: hardhat: ^2.0.4 dependencies: @@ -5559,7 +5560,7 @@ packages: - encoding dev: true - /@openzeppelin/hardhat-upgrades@3.0.5(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-verify@2.0.1)(ethers@6.8.1)(hardhat@2.19.1): + /@openzeppelin/hardhat-upgrades@3.0.5(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-verify@2.0.1)(ethers@6.10.0)(hardhat@2.19.1): resolution: {integrity: sha512-7Klg1B6fH45+7Zxzr6d9mLqudrL9Uk6CUG5AeG5NckPfP4ZlQRo1squcQ8yJPwqDS8rQjfChiqKDelp4LTjyZQ==} hasBin: true peerDependencies: @@ -5571,7 +5572,7 @@ packages: '@nomicfoundation/hardhat-verify': optional: true dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.5(ethers@6.8.1)(hardhat@2.19.1) + '@nomicfoundation/hardhat-ethers': 3.0.5(ethers@6.10.0)(hardhat@2.19.1) '@nomicfoundation/hardhat-verify': 2.0.1(hardhat@2.19.1) '@openzeppelin/defender-admin-client': 1.52.0(debug@4.3.4) '@openzeppelin/defender-base-client': 1.52.0(debug@4.3.4) @@ -5582,7 +5583,7 @@ packages: chalk: 4.1.2 debug: 4.3.4(supports-color@8.1.1) ethereumjs-util: 7.1.5 - ethers: 6.8.1 + ethers: 6.10.0 hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.3.2) proper-lockfile: 4.1.2 undici: 6.7.1 @@ -6815,21 +6816,21 @@ packages: /@turist/time@0.0.2: resolution: {integrity: sha512-qLOvfmlG2vCVw5fo/oz8WAZYlpe5a5OurgTj3diIxJCdjRHpapC+vQCz3er9LV79Vcat+DifBjeAhOAdmndtDQ==} - /@typechain/ethers-v6@0.5.1(ethers@6.8.1)(typechain@8.3.2)(typescript@5.3.2): + /@typechain/ethers-v6@0.5.1(ethers@6.10.0)(typechain@8.3.2)(typescript@5.3.2): resolution: {integrity: sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA==} peerDependencies: ethers: 6.x typechain: ^8.3.2 typescript: '>=4.7.0' dependencies: - ethers: 6.8.1 + ethers: 6.10.0 lodash: 4.17.21 ts-essentials: 7.0.3(typescript@5.3.2) typechain: 8.3.2(typescript@5.3.2) typescript: 5.3.2 dev: true - /@typechain/hardhat@9.1.0(@typechain/ethers-v6@0.5.1)(ethers@6.8.1)(hardhat@2.19.1)(typechain@8.3.2): + /@typechain/hardhat@9.1.0(@typechain/ethers-v6@0.5.1)(ethers@6.10.0)(hardhat@2.19.1)(typechain@8.3.2): resolution: {integrity: sha512-mtaUlzLlkqTlfPwB3FORdejqBskSnh+Jl8AIJGjXNAQfRQ4ofHADPl1+oU7Z3pAJzmZbUXII8MhOLQltcHgKnA==} peerDependencies: '@typechain/ethers-v6': ^0.5.1 @@ -6837,8 +6838,8 @@ packages: hardhat: ^2.9.9 typechain: ^8.3.2 dependencies: - '@typechain/ethers-v6': 0.5.1(ethers@6.8.1)(typechain@8.3.2)(typescript@5.3.2) - ethers: 6.8.1 + '@typechain/ethers-v6': 0.5.1(ethers@6.10.0)(typechain@8.3.2)(typescript@5.3.2) + ethers: 6.10.0 fs-extra: 9.1.0 hardhat: 2.19.1(ts-node@10.9.1)(typescript@5.3.2) typechain: 8.3.2(typescript@5.3.2) @@ -7341,7 +7342,7 @@ packages: '@typescript-eslint/type-utils': 6.12.0(eslint@8.54.0)(typescript@5.3.2) '@typescript-eslint/utils': 6.12.0(eslint@8.54.0)(typescript@5.3.2) '@typescript-eslint/visitor-keys': 6.12.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) eslint: 8.54.0 graphemer: 1.4.0 ignore: 5.3.0 @@ -7386,7 +7387,7 @@ packages: '@typescript-eslint/types': 6.12.0 '@typescript-eslint/typescript-estree': 6.12.0(typescript@5.3.2) '@typescript-eslint/visitor-keys': 6.12.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) eslint: 8.54.0 typescript: 5.3.2 transitivePeerDependencies: @@ -7437,7 +7438,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.12.0(typescript@5.3.2) '@typescript-eslint/utils': 6.12.0(eslint@8.54.0)(typescript@5.3.2) - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) eslint: 8.54.0 ts-api-utils: 1.0.3(typescript@5.3.2) typescript: 5.3.2 @@ -7484,7 +7485,7 @@ packages: dependencies: '@typescript-eslint/types': 6.12.0 '@typescript-eslint/visitor-keys': 6.12.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -8302,19 +8303,9 @@ packages: /axios@0.21.4(debug@4.3.4): resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.3(debug@4.3.4) - transitivePeerDependencies: - - debug - - /axios@1.6.7: - resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} - dependencies: - follow-redirects: 1.15.5 - form-data: 4.0.0 - proxy-from-env: 1.1.0 + follow-redirects: 1.15.5(debug@4.3.4) transitivePeerDependencies: - debug - dev: false /axios@1.6.7(debug@4.3.4): resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} @@ -8324,7 +8315,6 @@ packages: proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - dev: true /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} @@ -10263,17 +10253,6 @@ packages: dependencies: ms: 2.1.3 - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - /debug@4.3.4(supports-color@8.1.1): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -11526,7 +11505,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -11860,23 +11839,6 @@ packages: transitivePeerDependencies: - bufferutil - utf-8-validate - dev: false - - /ethers@6.8.1: - resolution: {integrity: sha512-iEKm6zox5h1lDn6scuRWdIdFJUCGg3+/aQWu0F4K0GVyEZiktFkqrJbRjTn1FlYEPz7RKA707D6g5Kdk6j7Ljg==} - engines: {node: '>=14.0.0'} - dependencies: - '@adraffy/ens-normalize': 1.10.0 - '@noble/curves': 1.2.0 - '@noble/hashes': 1.3.2 - '@types/node': 18.15.13 - aes-js: 4.0.0-beta.5 - tslib: 2.4.0 - ws: 8.5.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: true /ethjs-unit@0.1.6: resolution: {integrity: sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==} @@ -12325,27 +12287,6 @@ packages: tslib: 2.6.2 dev: false - /follow-redirects@1.15.3(debug@4.3.4): - resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dependencies: - debug: 4.3.4(supports-color@8.1.1) - - /follow-redirects@1.15.5: - resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dev: false - /follow-redirects@1.15.5(debug@4.3.4): resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} engines: {node: '>=4.0'} @@ -12356,7 +12297,6 @@ packages: optional: true dependencies: debug: 4.3.4(supports-color@8.1.1) - dev: true /follow-redirects@1.5.10: resolution: {integrity: sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==} @@ -19044,7 +18984,7 @@ packages: dependencies: command-exists: 1.2.9 commander: 3.0.2 - follow-redirects: 1.15.3(debug@4.3.4) + follow-redirects: 1.15.5(debug@4.3.4) fs-extra: 0.30.0 js-sha3: 0.8.0 memorystream: 0.3.1 @@ -22116,6 +22056,7 @@ packages: /zksync-web3@0.14.4(ethers@5.7.2): resolution: {integrity: sha512-kYehMD/S6Uhe1g434UnaMN+sBr9nQm23Ywn0EUP5BfQCsbjcr3ORuS68PosZw8xUTu3pac7G6YMSnNHk+fwzvg==} + deprecated: This package has been deprecated in favor of zksync-ethers@5.0.0 peerDependencies: ethers: ^5.7.0 dependencies: From c15fe460307345c740b76c95c75d51c5cbc5cea1 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 10 Apr 2024 11:20:34 +0200 Subject: [PATCH 64/75] Add commit to blame ignore --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index ec9afb3e3..0e95d8530 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,3 +1,6 @@ # Auto-fix linting d2a058fe6cfbab6f82d0d977d1b2d8bd9f494df1 0976ac1b09b7257fe74389bafe94697059a07aed + +# Move core/ to solidity/ +9b45a73a99b879e6fd362553f60de79405abad98 From 89585dcfee1a55207136eed34be7bb6d4ccb84d0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 12:18:01 +0200 Subject: [PATCH 65/75] Releasing deposit in full from Mezo In case there will be a need of pulling all the funds from Mezo we add a releaseDeposit function that can be called by the owner only. --- core/contracts/MezoAllocator.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index edf078132..e47fed42f 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -91,6 +91,8 @@ contract MezoAllocator is IDispatcher, Ownable2Step { event MaintainerAdded(address indexed maintainer); /// @notice Emitted when the maintainer address is updated. event MaintainerRemoved(address indexed maintainer); + /// @notice Emitted when tBTC is released from MezoPortal. + event DepositReleased(uint256 indexed depositId, uint256 amount); /// @notice Reverts if the caller is not an authorized account. error NotAuthorized(); /// @notice Reverts if the caller is not a maintainer. @@ -178,6 +180,18 @@ contract MezoAllocator is IDispatcher, Ownable2Step { tbtc.safeTransfer(address(stbtc), amount); } + /// @notice Releases deposit in full from MezoPortal. + function releaseDeposit() external onlyOwner { + uint96 amount = mezoPortal + .getDeposit(address(this), address(tbtc), depositId) + .balance; + + emit DepositReleased(depositId, amount); + depositBalance = 0; + mezoPortal.withdraw(address(tbtc), depositId, amount); + tbtc.safeTransfer(address(stbtc), tbtc.balanceOf(address(this))); + } + /// @notice Updates the maintainer address. /// @param maintainerToAdd Address of the new maintainer. function addMaintainer(address maintainerToAdd) external onlyOwner { From 7a2b52b7a32d0caf15ccae6aafa90fe11b0ca625 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 12:26:14 +0200 Subject: [PATCH 66/75] Adding two additional custom errors Added NotMaintainer and NotStbtc to better understand the error if occurs. --- core/contracts/MezoAllocator.sol | 8 ++++++-- core/test/MezoAllocator.test.ts | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index e47fed42f..2a0aef8d8 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -96,13 +96,17 @@ contract MezoAllocator is IDispatcher, Ownable2Step { /// @notice Reverts if the caller is not an authorized account. error NotAuthorized(); /// @notice Reverts if the caller is not a maintainer. + error NotMaintainer(); + /// @notice Reverts if the caller is not the stBTC contract. + error NotStbtc(); + /// @notice Reverts if the caller is not a maintainer. error MaintainerNotRegistered(); /// @notice Reverts if the caller is already a maintainer. error MaintainerAlreadyRegistered(); modifier onlyMaintainer() { if (!isMaintainer[msg.sender]) { - revert NotAuthorized(); + revert NotMaintainer(); } _; } @@ -171,7 +175,7 @@ contract MezoAllocator is IDispatcher, Ownable2Step { /// MezoPortal for a given deposit id. /// @param amount Amount of tBTC to withdraw. function withdraw(uint256 amount) external { - if (msg.sender != address(stbtc)) revert NotAuthorized(); + if (msg.sender != address(stbtc)) revert NotStbtc(); emit DepositWithdrawn(depositId, amount); mezoPortal.withdraw(address(tbtc), depositId, uint96(amount)); diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index 5d012d664..08c51aa67 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -65,7 +65,7 @@ describe("MezoAllocator", () => { it("should revert", async () => { await expect( mezoAllocator.connect(thirdParty).allocate(), - ).to.be.revertedWithCustomError(mezoAllocator, "NotAuthorized") + ).to.be.revertedWithCustomError(mezoAllocator, "NotMaintainer") }) }) @@ -153,7 +153,7 @@ describe("MezoAllocator", () => { it("should revert", async () => { await expect( mezoAllocator.connect(thirdParty).withdraw(1n), - ).to.be.revertedWithCustomError(mezoAllocator, "NotAuthorized") + ).to.be.revertedWithCustomError(mezoAllocator, "NotStbtc") }) }) From 0cb112380b243d0a30e9bb83aaa3a1a051b13178 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 12:30:04 +0200 Subject: [PATCH 67/75] Renaming tag to MezoAllocatorAddMaintainer --- core/deploy/12_mezo_allocator_update_maintainer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/deploy/12_mezo_allocator_update_maintainer.ts b/core/deploy/12_mezo_allocator_update_maintainer.ts index aa7cec484..5eb82de37 100644 --- a/core/deploy/12_mezo_allocator_update_maintainer.ts +++ b/core/deploy/12_mezo_allocator_update_maintainer.ts @@ -20,5 +20,5 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { export default func -func.tags = ["MezoAllocatorUpdateMaintainer"] +func.tags = ["MezoAllocatorAddMaintainer"] func.dependencies = ["Dispatcher"] From 59c3d6fbd618e7322d3c9b1145c21f276a737617 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 13:14:47 +0200 Subject: [PATCH 68/75] Adding tests for releaseDeposit --- core/test/MezoAllocator.test.ts | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index 08c51aa67..c2759123f 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -383,4 +383,48 @@ describe("MezoAllocator", () => { }) }) }) + + describe("releaseDeposit", () => { + beforeAfterSnapshotWrapper() + + context("when a caller is not a maintainer", () => { + it("should revert", async () => { + await expect( + mezoAllocator.connect(thirdParty).releaseDeposit(), + ).to.be.revertedWithCustomError( + mezoAllocator, + "OwnableUnauthorizedAccount", + ) + }) + }) + + context("when the caller is governance", () => { + context("when there is a deposit", () => { + let tx: ContractTransactionResponse + + before(async () => { + await tbtc.mint(await stbtc.getAddress(), to1e18(5)) + await mezoAllocator.connect(maintainer).allocate() + tx = await mezoAllocator.connect(governance).releaseDeposit() + }) + + it("should emit DepositReleased event", async () => { + await expect(tx) + .to.emit(mezoAllocator, "DepositReleased") + .withArgs(1, to1e18(5)) + }) + + it("should decrease tracked deposit balance amount to zero", async () => { + const depositBalance = await mezoAllocator.depositBalance() + expect(depositBalance).to.equal(0) + }) + + it("should decrease Mezo Portal balance", async () => { + expect(await tbtc.balanceOf(await mezoPortal.getAddress())).to.equal( + 0, + ) + }) + }) + }) + }) }) From b7e40dcfcd3a8fb8191ba5da6f8b768815237c55 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 13:40:14 +0200 Subject: [PATCH 69/75] Removing test erc4626 as it is no longer used --- core/contracts/test/TestERC4626.sol | 12 --------- core/deploy/00_resolve_testing_erc4626.ts | 30 ----------------------- core/test/helpers/context.ts | 3 --- 3 files changed, 45 deletions(-) delete mode 100644 core/contracts/test/TestERC4626.sol delete mode 100644 core/deploy/00_resolve_testing_erc4626.ts diff --git a/core/contracts/test/TestERC4626.sol b/core/contracts/test/TestERC4626.sol deleted file mode 100644 index acf09928e..000000000 --- a/core/contracts/test/TestERC4626.sol +++ /dev/null @@ -1,12 +0,0 @@ -// 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) {} -} diff --git a/core/deploy/00_resolve_testing_erc4626.ts b/core/deploy/00_resolve_testing_erc4626.ts deleted file mode 100644 index c49346b06..000000000 --- a/core/deploy/00_resolve_testing_erc4626.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { HardhatRuntimeEnvironment } from "hardhat/types" -import type { DeployFunction } from "hardhat-deploy/types" -import { waitConfirmationsNumber } from "../helpers/deployment" - -const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const { getNamedAccounts, deployments } = hre - const { log } = deployments - const { deployer } = await getNamedAccounts() - const tBTC = await deployments.get("TBTC") - - log("deploying Mock ERC4626 Vault") - - await deployments.deploy("Vault", { - contract: "TestERC4626", - from: deployer, - args: [tBTC.address, "MockVault", "MV"], - log: true, - waitConfirmations: waitConfirmationsNumber(hre), - }) -} - -export default func - -func.tags = ["TestERC4626"] -func.dependencies = ["TBTC"] - -func.skip = async (hre: HardhatRuntimeEnvironment): Promise => - Promise.resolve( - hre.network.name === "mainnet" || hre.network.name === "sepolia", - ) diff --git a/core/test/helpers/context.ts b/core/test/helpers/context.ts index a9471f738..b4d864fcb 100644 --- a/core/test/helpers/context.ts +++ b/core/test/helpers/context.ts @@ -4,7 +4,6 @@ import { getDeployedContract } from "./contract" import type { StBTC as stBTC, BridgeStub, - TestERC4626, TBTCVaultStub, MezoAllocator, MezoPortalStub, @@ -27,7 +26,6 @@ export async function deployment() { const tbtcBridge: BridgeStub = await getDeployedContract("Bridge") const tbtcVault: TBTCVaultStub = await getDeployedContract("TBTCVault") - const vault: TestERC4626 = await getDeployedContract("Vault") const mezoAllocator: MezoAllocator = await getDeployedContract("MezoAllocator") const mezoPortal: MezoPortalStub = await getDeployedContract("MezoPortal") @@ -39,7 +37,6 @@ export async function deployment() { bitcoinRedeemer, tbtcBridge, tbtcVault, - vault, mezoAllocator, mezoPortal, } From c52a2da3f3f5a6435aadfec266a9ed9fc5814d43 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 13:45:05 +0200 Subject: [PATCH 70/75] Refactoring ReleaseDeposit tests --- core/test/MezoAllocator.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index c2759123f..26d6fdc69 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -387,7 +387,7 @@ describe("MezoAllocator", () => { describe("releaseDeposit", () => { beforeAfterSnapshotWrapper() - context("when a caller is not a maintainer", () => { + context("when a caller is not governance", () => { it("should revert", async () => { await expect( mezoAllocator.connect(thirdParty).releaseDeposit(), @@ -420,8 +420,10 @@ describe("MezoAllocator", () => { }) it("should decrease Mezo Portal balance", async () => { - expect(await tbtc.balanceOf(await mezoPortal.getAddress())).to.equal( - 0, + await expect(tx).to.changeTokenBalances( + tbtc, + [mezoPortal, stbtc], + [-to1e18(5), to1e18(5)], ) }) }) From 6ca6e0e42e104a3c1c9ec3193e4759b7f6eb7207 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 14:22:23 +0200 Subject: [PATCH 71/75] Smaller refactorings and cleanups --- core/contracts/MezoAllocator.sol | 16 ++++++++-------- core/test/MezoAllocator.test.ts | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 2a0aef8d8..1511bfbd4 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -93,20 +93,18 @@ contract MezoAllocator is IDispatcher, Ownable2Step { event MaintainerRemoved(address indexed maintainer); /// @notice Emitted when tBTC is released from MezoPortal. event DepositReleased(uint256 indexed depositId, uint256 amount); - /// @notice Reverts if the caller is not an authorized account. - error NotAuthorized(); /// @notice Reverts if the caller is not a maintainer. - error NotMaintainer(); + error CallerNotMaintainer(); /// @notice Reverts if the caller is not the stBTC contract. - error NotStbtc(); - /// @notice Reverts if the caller is not a maintainer. + error CallerNotStbtc(); + /// @notice Reverts if the maintainer is already registered. error MaintainerNotRegistered(); /// @notice Reverts if the caller is already a maintainer. error MaintainerAlreadyRegistered(); modifier onlyMaintainer() { if (!isMaintainer[msg.sender]) { - revert NotMaintainer(); + revert CallerNotMaintainer(); } _; } @@ -175,7 +173,7 @@ contract MezoAllocator is IDispatcher, Ownable2Step { /// MezoPortal for a given deposit id. /// @param amount Amount of tBTC to withdraw. function withdraw(uint256 amount) external { - if (msg.sender != address(stbtc)) revert NotStbtc(); + if (msg.sender != address(stbtc)) revert CallerNotStbtc(); emit DepositWithdrawn(depositId, amount); mezoPortal.withdraw(address(tbtc), depositId, uint96(amount)); @@ -185,6 +183,8 @@ contract MezoAllocator is IDispatcher, Ownable2Step { } /// @notice Releases deposit in full from MezoPortal. + /// @dev This is a special function that can be used to migrate funds during + /// allocator upgrade or in case of emergencies. function releaseDeposit() external onlyOwner { uint96 amount = mezoPortal .getDeposit(address(this), address(tbtc), depositId) @@ -232,7 +232,7 @@ contract MezoAllocator is IDispatcher, Ownable2Step { } /// @notice Returns the total amount of tBTC allocated to MezoPortal. - function totalAssets() external view returns (uint256 totalAmount) { + function totalAssets() external view returns (uint256) { return depositBalance; } diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index 26d6fdc69..d088fcee1 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -65,7 +65,7 @@ describe("MezoAllocator", () => { it("should revert", async () => { await expect( mezoAllocator.connect(thirdParty).allocate(), - ).to.be.revertedWithCustomError(mezoAllocator, "NotMaintainer") + ).to.be.revertedWithCustomError(mezoAllocator, "CallerNotMaintainer") }) }) @@ -153,7 +153,7 @@ describe("MezoAllocator", () => { it("should revert", async () => { await expect( mezoAllocator.connect(thirdParty).withdraw(1n), - ).to.be.revertedWithCustomError(mezoAllocator, "NotStbtc") + ).to.be.revertedWithCustomError(mezoAllocator, "CallerNotStbtc") }) }) From 860b16cd203a0a150660741335d34a51e233732a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 14:40:15 +0200 Subject: [PATCH 72/75] Refactoring tests around MezoAllocator --- core/test/MezoAllocator.test.ts | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/core/test/MezoAllocator.test.ts b/core/test/MezoAllocator.test.ts index d088fcee1..23467a497 100644 --- a/core/test/MezoAllocator.test.ts +++ b/core/test/MezoAllocator.test.ts @@ -79,8 +79,10 @@ describe("MezoAllocator", () => { }) it("should deposit and transfer tBTC to Mezo Portal", async () => { - expect(await tbtc.balanceOf(await mezoPortal.getAddress())).to.equal( - to1e18(6), + await expect(tx).to.changeTokenBalances( + tbtc, + [await mezoPortal.getAddress()], + [to1e18(6)], ) }) @@ -95,6 +97,11 @@ describe("MezoAllocator", () => { expect(actualDepositId).to.equal(1) }) + it("should increase tracked deposit balance amount", async () => { + const depositBalance = await mezoAllocator.depositBalance() + expect(depositBalance).to.equal(to1e18(6)) + }) + it("should emit DepositAllocated event", async () => { await expect(tx) .to.emit(mezoAllocator, "DepositAllocated") @@ -201,9 +208,11 @@ describe("MezoAllocator", () => { }) it("should decrease Mezo Portal balance", async () => { - expect( - await tbtc.balanceOf(await mezoPortal.getAddress()), - ).to.equal(to1e18(3)) + await expect(tx).to.changeTokenBalances( + tbtc, + [await mezoPortal.getAddress()], + [-to1e18(2)], + ) }) }) @@ -232,9 +241,11 @@ describe("MezoAllocator", () => { }) it("should decrease Mezo Portal balance", async () => { - expect( - await tbtc.balanceOf(await mezoPortal.getAddress()), - ).to.equal(0) + await expect(tx).to.changeTokenBalances( + tbtc, + [await mezoPortal.getAddress()], + [-to1e18(3)], + ) }) }) }) From 0d54124573b385a8f0174f65b0ae50125b1ccca9 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 10 Apr 2024 15:06:11 +0200 Subject: [PATCH 73/75] Update MezoAllocator upgrade test contract --- .../test/upgrades/MezoAllocatorV2.sol | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/core/contracts/test/upgrades/MezoAllocatorV2.sol b/core/contracts/test/upgrades/MezoAllocatorV2.sol index d809cf194..2457510a4 100644 --- a/core/contracts/test/upgrades/MezoAllocatorV2.sol +++ b/core/contracts/test/upgrades/MezoAllocatorV2.sol @@ -94,18 +94,22 @@ contract MezoAllocatorV2 is IDispatcher, Ownable2StepUpgradeable { event MaintainerAdded(address indexed maintainer); /// @notice Emitted when the maintainer address is updated. event MaintainerRemoved(address indexed maintainer); + /// @notice Emitted when tBTC is released from MezoPortal. + event DepositReleased(uint256 indexed depositId, uint256 amount); // TEST: New event. event NewEvent(); - /// @notice Reverts if the caller is not an authorized account. - error NotAuthorized(); /// @notice Reverts if the caller is not a maintainer. + error CallerNotMaintainer(); + /// @notice Reverts if the caller is not the stBTC contract. + error CallerNotStbtc(); + /// @notice Reverts if the maintainer is already registered. error MaintainerNotRegistered(); /// @notice Reverts if the caller is already a maintainer. error MaintainerAlreadyRegistered(); modifier onlyMaintainer() { if (!isMaintainer[msg.sender]) { - revert NotAuthorized(); + revert CallerNotMaintainer(); } _; } @@ -115,9 +119,6 @@ contract MezoAllocatorV2 is IDispatcher, Ownable2StepUpgradeable { _disableInitializers(); } - /// @notice Initializes the MezoAllocator contract. - /// @param _mezoPortal Address of the MezoPortal contract. - /// @param _tbtc Address of the tBTC token contract. function initialize( address _mezoPortal, address _tbtc, @@ -175,7 +176,7 @@ contract MezoAllocatorV2 is IDispatcher, Ownable2StepUpgradeable { /// MezoPortal for a given deposit id. /// @param amount Amount of tBTC to withdraw. function withdraw(uint256 amount) external { - if (msg.sender != address(stbtc)) revert NotAuthorized(); + if (msg.sender != address(stbtc)) revert CallerNotStbtc(); emit DepositWithdrawn(depositId, amount); mezoPortal.withdraw(address(tbtc), depositId, uint96(amount)); @@ -184,6 +185,20 @@ contract MezoAllocatorV2 is IDispatcher, Ownable2StepUpgradeable { tbtc.safeTransfer(address(stbtc), amount); } + /// @notice Releases deposit in full from MezoPortal. + /// @dev This is a special function that can be used to migrate funds during + /// allocator upgrade or in case of emergencies. + function releaseDeposit() external onlyOwner { + uint96 amount = mezoPortal + .getDeposit(address(this), address(tbtc), depositId) + .balance; + + emit DepositReleased(depositId, amount); + depositBalance = 0; + mezoPortal.withdraw(address(tbtc), depositId, amount); + tbtc.safeTransfer(address(stbtc), tbtc.balanceOf(address(this))); + } + /// @notice Updates the maintainer address. /// @param maintainerToAdd Address of the new maintainer. // TEST: Modified function. @@ -224,7 +239,12 @@ contract MezoAllocatorV2 is IDispatcher, Ownable2StepUpgradeable { } /// @notice Returns the total amount of tBTC allocated to MezoPortal. - function totalAssets() external view returns (uint256 totalAmount) { + function totalAssets() external view returns (uint256) { return depositBalance; } + + /// @notice Returns the list of maintainers. + function getMaintainers() external view returns (address[] memory) { + return maintainers; + } } From ba01b0787d1a2e72c166652b43508819448ef33d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 10 Apr 2024 15:20:29 +0200 Subject: [PATCH 74/75] Refactorring error comments --- core/contracts/MezoAllocator.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/contracts/MezoAllocator.sol b/core/contracts/MezoAllocator.sol index 1511bfbd4..02d194388 100644 --- a/core/contracts/MezoAllocator.sol +++ b/core/contracts/MezoAllocator.sol @@ -97,9 +97,9 @@ contract MezoAllocator is IDispatcher, Ownable2Step { error CallerNotMaintainer(); /// @notice Reverts if the caller is not the stBTC contract. error CallerNotStbtc(); - /// @notice Reverts if the maintainer is already registered. + /// @notice Reverts if the maintainer is not registered. error MaintainerNotRegistered(); - /// @notice Reverts if the caller is already a maintainer. + /// @notice Reverts if the maintainer has been already registered. error MaintainerAlreadyRegistered(); modifier onlyMaintainer() { From e85b23152e9b549bf58865cae5a12abebfa962fe Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 10 Apr 2024 16:20:45 +0200 Subject: [PATCH 75/75] Update Solidity readme --- README.md | 2 -- solidity/README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dc7b0f048..3dbae51c2 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ Bitcoin Liquid Staking -[![Solidity](https://github.com/thesis/acre/actions/workflows/solidity.yaml/badge.svg?branch=main&event=push)](https://github.com/thesis/acre/actions/workflows/solidity.yaml) - ## Development ### pnpm diff --git a/solidity/README.md b/solidity/README.md index b90952ee8..651236aa9 100644 --- a/solidity/README.md +++ b/solidity/README.md @@ -1,3 +1,48 @@ -# Acre +# Acre Contracts -Acre is a “liquid staking” solution that allows people to earn yield on their Bitcoin via yield farming on Ethereum. +Acre protocol smart contracts. + +[![Solidity](https://github.com/thesis/acre/actions/workflows/solidity.yaml/badge.svg?branch=main&event=push)](https://github.com/thesis/acre/actions/workflows/solidity.yaml) + +## Development + +### Installation + +This project uses [pnpm](https://pnpm.io/) as a package manager ([installation documentation](https://pnpm.io/installation)). + +To install the dependencies execute: + +```bash +pnpm install +``` + +### Testing + +To run the test execute: + +``` +$ pnpm test +``` + +### Deploying + +We deploy our contracts with +[hardhat-deploy](https://www.npmjs.com/package/hardhat-deploy) via + +``` +$ pnpm run deploy [--network ] +``` + +Check the `"networks"` entry of `hardhat.config.ts` for supported networks. + +## Contract Addresses + +The official mainnet and testnet contract addresses are listed below. + +### Mainnet + +TBD + +### Sepolia + +TBD