From 32b0f4d41674eca675bdc3ded6168257dfb9ac1f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 4 Dec 2023 15:59:27 +0100 Subject: [PATCH 01/36] Handling ERC4626 vaults in AcreRouter AcreRouter contract should manage ERC4626 vaults within the Acre system. Owner of the contract should be able to add or remove vaults. --- core/contracts/AcreRouter.sol | 55 +++++++++++++++++++++++++++++++++++ core/package.json | 3 ++ 2 files changed, 58 insertions(+) create mode 100644 core/contracts/AcreRouter.sol diff --git a/core/contracts/AcreRouter.sol b/core/contracts/AcreRouter.sol new file mode 100644 index 000000000..9063ed59f --- /dev/null +++ b/core/contracts/AcreRouter.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +/// @title AcreRouter +/// @notice AcreRouter is a contract that routes TBTC from stBTC (Acre) to +/// a given vault and back. Vaults supply yield strategies with TBTC that +/// generate yield for Bitcoin holders. +contract AcreRouter is OwnableUpgradeable { + struct Vault { + bool approved; + } + + /// @notice Approved vaults within the Yiern Modules that implement ERC4626 + /// standard. These vaults deposit assets to yield strategies, e.g. + /// Uniswap V3 WBTC/TBTC pool. Vault can be a part of Acre ecosystem + /// or can be implemented externally. As long as it complies with + /// ERC4626 standard and is approved by the owner it can be + /// plugged into Acre. + address[] public vaults; + mapping(address => Vault) public vaultsInfo; + + event VaultAdded(address indexed vault); + event VaultRemoved(address indexed vault); + + /// @notice Adds a vault to the list of approved vaults. + /// @param vault Address of the vault to add. + function addVault(address vault) external onlyOwner { + require(!vaultsInfo[vault].approved, "Vault already approved"); + + vaults.push(vault); + vaultsInfo[vault].approved = true; + + emit VaultAdded(vault); + } + + /// @notice Removes a vault from the list of approved vaults. + /// @param vault Address of the vault to remove. + function removeVault(address vault) external onlyOwner { + require(vaultsInfo[vault].approved, "Not a vault"); + + delete vaultsInfo[vault]; + + for (uint256 i = 0; i < vaults.length; i++) { + if (vaults[i] == vault) { + vaults[i] = vaults[vaults.length - 1]; + vaults.pop(); + break; + } + } + + emit VaultRemoved(vault); + } +} diff --git a/core/package.json b/core/package.json index 49f094744..5e0d6a7dc 100644 --- a/core/package.json +++ b/core/package.json @@ -55,5 +55,8 @@ "ts-node": "^10.9.1", "typechain": "^8.3.2", "typescript": "^5.3.2" + }, + "dependencies": { + "@openzeppelin/contracts-upgradeable": "^5.0.0" } } From 3246552ee84fea7360e641a9a7f4bb1316b2fee5 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 4 Dec 2023 16:23:23 +0100 Subject: [PATCH 02/36] Adding pnpm-lock.yaml --- pnpm-lock.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 371dfb09f..b41a02bbc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,10 @@ importers: version: 11.2.1 core: + dependencies: + '@openzeppelin/contracts-upgradeable': + specifier: ^5.0.0 + version: 5.0.0(@openzeppelin/contracts@5.0.0) devDependencies: '@nomicfoundation/hardhat-chai-matchers': specifier: ^2.0.2 @@ -4421,6 +4425,18 @@ packages: - supports-color dev: true + /@openzeppelin/contracts-upgradeable@5.0.0(@openzeppelin/contracts@5.0.0): + resolution: {integrity: sha512-D54RHzkOKHQ8xUssPgQe2d/U92mwaiBDY7qCCVGq6VqwQjsT3KekEQ3bonev+BLP30oZ0R1U6YC8/oLpizgC5Q==} + peerDependencies: + '@openzeppelin/contracts': 5.0.0 + dependencies: + '@openzeppelin/contracts': 5.0.0 + dev: false + + /@openzeppelin/contracts@5.0.0: + resolution: {integrity: sha512-bv2sdS6LKqVVMLI5+zqnNrNU/CA+6z6CmwFXm/MzmOPBRSO5reEJN7z0Gbzvs0/bv/MZZXNklubpwy3v2+azsw==} + dev: false + /@openzeppelin/defender-admin-client@1.52.0(debug@4.3.4): resolution: {integrity: sha512-CKs5mMLL7+nXyugsHaAw0aPfLwFNA+vq7ftuJ3sWUKdbQRZsJ+/189HAwp2/BJC64yUbarEeWqOh3jNpaKRJLw==} dependencies: From a3ba20f8ac2a744f8fae34c1b7f95dad463f3e7e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Dec 2023 14:23:46 +0100 Subject: [PATCH 03/36] Replacing OZ upgradable lib to a non-upgradable one Upgradability will be handled down the road in a separate PR(s) --- core/contracts/AcreRouter.sol | 13 ++++++++++--- core/package.json | 2 +- pnpm-lock.yaml | 12 ++---------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/core/contracts/AcreRouter.sol b/core/contracts/AcreRouter.sol index 9063ed59f..c7224e324 100644 --- a/core/contracts/AcreRouter.sol +++ b/core/contracts/AcreRouter.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; /// @title AcreRouter /// @notice AcreRouter is a contract that routes TBTC from stBTC (Acre) to /// a given vault and back. Vaults supply yield strategies with TBTC that /// generate yield for Bitcoin holders. -contract AcreRouter is OwnableUpgradeable { +contract AcreRouter is Ownable { struct Vault { bool approved; } @@ -24,6 +24,8 @@ contract AcreRouter is OwnableUpgradeable { event VaultAdded(address indexed vault); event VaultRemoved(address indexed vault); + constructor() Ownable(msg.sender) {} + /// @notice Adds a vault to the list of approved vaults. /// @param vault Address of the vault to add. function addVault(address vault) external onlyOwner { @@ -40,11 +42,12 @@ contract AcreRouter is OwnableUpgradeable { function removeVault(address vault) external onlyOwner { require(vaultsInfo[vault].approved, "Not a vault"); - delete vaultsInfo[vault]; + vaultsInfo[vault].approved = 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; } @@ -52,4 +55,8 @@ contract AcreRouter is OwnableUpgradeable { emit VaultRemoved(vault); } + + function vaultsLength() external view returns (uint256) { + return vaults.length; + } } diff --git a/core/package.json b/core/package.json index 5e0d6a7dc..603cb8643 100644 --- a/core/package.json +++ b/core/package.json @@ -57,6 +57,6 @@ "typescript": "^5.3.2" }, "dependencies": { - "@openzeppelin/contracts-upgradeable": "^5.0.0" + "@openzeppelin/contracts": "^5.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b41a02bbc..362ab37be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,9 +20,9 @@ importers: core: dependencies: - '@openzeppelin/contracts-upgradeable': + '@openzeppelin/contracts': specifier: ^5.0.0 - version: 5.0.0(@openzeppelin/contracts@5.0.0) + version: 5.0.0 devDependencies: '@nomicfoundation/hardhat-chai-matchers': specifier: ^2.0.2 @@ -4425,14 +4425,6 @@ packages: - supports-color dev: true - /@openzeppelin/contracts-upgradeable@5.0.0(@openzeppelin/contracts@5.0.0): - resolution: {integrity: sha512-D54RHzkOKHQ8xUssPgQe2d/U92mwaiBDY7qCCVGq6VqwQjsT3KekEQ3bonev+BLP30oZ0R1U6YC8/oLpizgC5Q==} - peerDependencies: - '@openzeppelin/contracts': 5.0.0 - dependencies: - '@openzeppelin/contracts': 5.0.0 - dev: false - /@openzeppelin/contracts@5.0.0: resolution: {integrity: sha512-bv2sdS6LKqVVMLI5+zqnNrNU/CA+6z6CmwFXm/MzmOPBRSO5reEJN7z0Gbzvs0/bv/MZZXNklubpwy3v2+azsw==} dev: false From 072f854937630ec09e8658b117b7bb6bfd3093f9 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Dec 2023 14:24:55 +0100 Subject: [PATCH 04/36] Drafting tests for AcreRouter --- core/test/AcreRouter.test.ts | 148 +++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 core/test/AcreRouter.test.ts diff --git a/core/test/AcreRouter.test.ts b/core/test/AcreRouter.test.ts new file mode 100644 index 000000000..af3e358b9 --- /dev/null +++ b/core/test/AcreRouter.test.ts @@ -0,0 +1,148 @@ +import { ethers, getUnnamedAccounts, getNamedAccounts } from "hardhat" +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" +import { Address } from "hardhat-deploy/types" +import { expect } from "chai" +import { + SnapshotRestorer, + takeSnapshot, +} from "@nomicfoundation/hardhat-toolbox/network-helpers" +import type { AcreRouter } from "../typechain" + +describe("AcreRouter", () => { + let snapshot: SnapshotRestorer + + let acreRouter: AcreRouter + let deployerSigner: HardhatEthersSigner + let governanceSigner: HardhatEthersSigner + let thirdPartySigner: HardhatEthersSigner + let vault1: Address + let vault2: Address + let vault3: Address + let vault4: Address + + // TODO: + // Put some of this setup to deployment scripts and/or helpers e.g. ownership + // transfer, acreRouter deployment etc. See: https://github.com/thesis/acre/pull/58 + // and https://github.com/thesis/acre/pull/64 + before(async () => { + const accounts = await getUnnamedAccounts() + ;[vault1, vault2, vault3, vault4] = accounts + const { deployer, governance } = await getNamedAccounts() + deployerSigner = await ethers.getSigner(deployer) + governanceSigner = await ethers.getSigner(governance) + thirdPartySigner = await ethers.getSigner(accounts[0]) + const AcreRouter = await ethers.getContractFactory("AcreRouter") + acreRouter = await AcreRouter.connect(deployerSigner).deploy() + acreRouter.transferOwnership(governance) + }) + + beforeEach(async () => { + snapshot = await takeSnapshot() + }) + + afterEach(async () => { + await snapshot.restore() + }) + + describe("addVault", () => { + context("when caller is not a governance account", () => { + it("should revert when adding a vault", async () => { + await expect( + acreRouter.connect(thirdPartySigner).addVault(vault1), + ).to.be.revertedWithCustomError( + acreRouter, + "OwnableUnauthorizedAccount", + ) + }) + }) + + context("when caller is a governance account", () => { + it("should be able to add vaults", async () => { + await acreRouter.connect(governanceSigner).addVault(vault1) + await acreRouter.connect(governanceSigner).addVault(vault2) + await acreRouter.connect(governanceSigner).addVault(vault3) + + expect(await acreRouter.vaults(0)).to.equal(vault1) + const isVault1Approved = await acreRouter.vaultsInfo(vault1) + expect(isVault1Approved).to.equal(true) + + expect(await acreRouter.vaults(1)).to.equal(vault2) + const isVault2Approved = await acreRouter.vaultsInfo(vault1) + expect(isVault2Approved).to.equal(true) + + expect(await acreRouter.vaults(2)).to.equal(vault3) + const isVault3Approved = await acreRouter.vaultsInfo(vault1) + expect(isVault3Approved).to.equal(true) + }) + + it("should not be able to add the same vault twice", async () => { + await acreRouter.connect(governanceSigner).addVault(vault1) + await expect( + acreRouter.connect(governanceSigner).addVault(vault1), + ).to.be.revertedWith("Vault already approved") + }) + + it("should emit an event when adding a vault", async () => { + await expect(acreRouter.connect(governanceSigner).addVault(vault1)) + .to.emit(acreRouter, "VaultAdded") + .withArgs(vault1) + }) + }) + }) + + describe("removeVault", () => { + beforeEach(async () => { + await acreRouter.connect(governanceSigner).addVault(vault1) + await acreRouter.connect(governanceSigner).addVault(vault2) + await acreRouter.connect(governanceSigner).addVault(vault3) + }) + + context("when caller is not a governance account", () => { + it("should revert when adding a vault", async () => { + await expect( + acreRouter.connect(thirdPartySigner).removeVault(vault1), + ).to.be.revertedWithCustomError( + acreRouter, + "OwnableUnauthorizedAccount", + ) + }) + }) + + context("when caller is a governance account", () => { + it("should be able to remove vaults", async () => { + await acreRouter.connect(governanceSigner).removeVault(vault1) + + // Last vault replaced the first vault in the 'vaults' array + expect(await acreRouter.vaults(0)).to.equal(vault3) + const isVault1Approved = await acreRouter.vaultsInfo(vault1) + expect(isVault1Approved).to.equal(false) + expect(await acreRouter.vaultsLength()).to.equal(2) + + await acreRouter.connect(governanceSigner).removeVault(vault2) + + // Last vault (vault2) was removed from the 'vaults' array + expect(await acreRouter.vaults(0)).to.equal(vault3) + expect(await acreRouter.vaultsLength()).to.equal(1) + const isVault2Approved = await acreRouter.vaultsInfo(vault2) + expect(isVault2Approved).to.equal(false) + + await acreRouter.connect(governanceSigner).removeVault(vault3) + expect(await acreRouter.vaultsLength()).to.equal(0) + const isVault3Approved = await acreRouter.vaultsInfo(vault3) + expect(isVault3Approved).to.equal(false) + }) + + it("should not be able to remove a vault that is not approved", async () => { + await expect( + acreRouter.connect(governanceSigner).removeVault(vault4), + ).to.be.revertedWith("Not a vault") + }) + + it("should emit an event when removing a vault", async () => { + await expect(acreRouter.connect(governanceSigner).removeVault(vault1)) + .to.emit(acreRouter, "VaultRemoved") + .withArgs(vault1) + }) + }) + }) +}) From cd91d0719ed5f987a5c24f1996dd28a4b9e81d1d Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 5 Dec 2023 17:21:06 +0100 Subject: [PATCH 05/36] Add helpers to get deployed contract Get contract deployed with hardhat deployment scripts to use them in tests. --- core/test/helpers/context.ts | 15 +++++++++++++++ core/test/helpers/contract.ts | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 core/test/helpers/context.ts create mode 100644 core/test/helpers/contract.ts diff --git a/core/test/helpers/context.ts b/core/test/helpers/context.ts new file mode 100644 index 000000000..7131b8264 --- /dev/null +++ b/core/test/helpers/context.ts @@ -0,0 +1,15 @@ +import { deployments } from "hardhat" + +import { getDeployedContract } from "./contract" + +import type { Acre, TestERC20 } from "../../typechain" + +// eslint-disable-next-line import/prefer-default-export +export async function deployment() { + await deployments.fixture() + + const tbtc: TestERC20 = await getDeployedContract("TBTC") + const acre: Acre = await getDeployedContract("Acre") + + return { tbtc, acre } +} diff --git a/core/test/helpers/contract.ts b/core/test/helpers/contract.ts new file mode 100644 index 000000000..88a83812a --- /dev/null +++ b/core/test/helpers/contract.ts @@ -0,0 +1,22 @@ +import { ethers } from "ethers" +import { deployments } from "hardhat" + +import type { BaseContract } from "ethers" +import { getUnnamedSigner } from "./signer" + +/** + * Get instance of a contract from Hardhat Deployments. + * @param deploymentName Name of the contract deployment. + * @returns Deployed Ethers contract instance. + */ +// eslint-disable-next-line import/prefer-default-export +export async function getDeployedContract( + deploymentName: string, +): Promise { + const { address, abi } = await deployments.get(deploymentName) + + // Use default unnamed signer from index 0 to initialize the contract runner. + const [defaultSigner] = await getUnnamedSigner() + + return new ethers.BaseContract(address, abi, defaultSigner) as T +} From 4bf5442fa800927e8b0140b9a418d906230fe649 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 5 Dec 2023 17:21:47 +0100 Subject: [PATCH 06/36] Add helpers for hardhat signers Hardhat have named signers that can be defined in the hardhat.config.ts file and unnamed signers. We need to use them separately in tests. --- core/test/helpers/index.ts | 3 +++ core/test/helpers/signer.ts | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 core/test/helpers/index.ts create mode 100644 core/test/helpers/signer.ts diff --git a/core/test/helpers/index.ts b/core/test/helpers/index.ts new file mode 100644 index 000000000..27ddcb0b9 --- /dev/null +++ b/core/test/helpers/index.ts @@ -0,0 +1,3 @@ +export * from "./context" +export * from "./contract" +export * from "./signer" diff --git a/core/test/helpers/signer.ts b/core/test/helpers/signer.ts new file mode 100644 index 000000000..c6fea1c0e --- /dev/null +++ b/core/test/helpers/signer.ts @@ -0,0 +1,37 @@ +import { ethers, getNamedAccounts, getUnnamedAccounts } from "hardhat" + +import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" + +/** + * Get named Hardhat Ethers Signers. + * @returns Map of named Hardhat Ethers Signers. + */ +export async function getNamedSigner(): Promise<{ + [name: string]: HardhatEthersSigner +}> { + const namedSigners: { [name: string]: HardhatEthersSigner } = {} + + await Promise.all( + Object.entries(await getNamedAccounts()).map(async ([name, address]) => { + namedSigners[name] = await ethers.getSigner(address) + }), + ) + + return namedSigners +} + +/** + * Get unnamed Hardhat Ethers Signers. + * @returns Array of unnamed Hardhat Ethers Signers. + */ +export async function getUnnamedSigner(): Promise { + const unnamedSigners: HardhatEthersSigner[] = [] + + await Promise.all( + (await getUnnamedAccounts()).map(async (address) => { + unnamedSigners.push(await ethers.getSigner(address)) + }), + ) + + return unnamedSigners +} From 16676e9620c86e1c03a4d8157a733fd3743f0115 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 5 Dec 2023 17:24:27 +0100 Subject: [PATCH 07/36] Improve test fixture used in Acre test - use Acre and TBTC contracts deployed with hardhat dpeloyment scripts - use unnamed signers as stakers, to not overlap with named signers (deployer and governance) --- core/test/Acre.test.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 74e764ca0..4a5960e3e 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -1,23 +1,23 @@ import { - SnapshotRestorer, - loadFixture, takeSnapshot, + loadFixture, } from "@nomicfoundation/hardhat-toolbox/network-helpers" -import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" -import { ethers } from "hardhat" import { expect } from "chai" import { ContractTransactionResponse, ZeroAddress } from "ethers" -import type { TestERC20, Acre } from "../typechain" + +import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" +import type { SnapshotRestorer } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { deployment } from "./helpers/context" +import { getUnnamedSigner } from "./helpers/signer" + import { to1e18 } from "./utils" -async function acreFixture() { - const [staker1, staker2] = await ethers.getSigners() +import type { Acre, TestERC20 } from "../typechain" - const TestERC20 = await ethers.getContractFactory("TestERC20") - const tbtc = await TestERC20.deploy() +async function fixture() { + const { tbtc, acre } = await deployment() - const Acre = await ethers.getContractFactory("Acre") - const acre = await Acre.deploy(await tbtc.getAddress()) + const [staker1, staker2] = await getUnnamedSigner() const amountToMint = to1e18(100000) tbtc.mint(staker1, amountToMint) @@ -33,7 +33,7 @@ describe("Acre", () => { let staker2: HardhatEthersSigner before(async () => { - ;({ acre, tbtc, staker1, staker2 } = await loadFixture(acreFixture)) + ;({ acre, tbtc, staker1, staker2 } = await loadFixture(fixture)) }) describe("stake", () => { From 9a9c5137224e4450f1854c83e787db63314778b3 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Dec 2023 15:34:39 +0100 Subject: [PATCH 08/36] Adding AcreRouter to deployed context --- core/test/helpers/context.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/test/helpers/context.ts b/core/test/helpers/context.ts index 7131b8264..ec4c56cbd 100644 --- a/core/test/helpers/context.ts +++ b/core/test/helpers/context.ts @@ -2,7 +2,7 @@ import { deployments } from "hardhat" import { getDeployedContract } from "./contract" -import type { Acre, TestERC20 } from "../../typechain" +import type { Acre, AcreRouter, TestERC20 } from "../../typechain" // eslint-disable-next-line import/prefer-default-export export async function deployment() { @@ -10,6 +10,7 @@ export async function deployment() { const tbtc: TestERC20 = await getDeployedContract("TBTC") const acre: Acre = await getDeployedContract("Acre") + const acreRouter: AcreRouter = await getDeployedContract("AcreRouter") - return { tbtc, acre } + return { tbtc, acre, acreRouter } } From 2ae92ecbc447c6a6fafd6b1edd8ac83f3e15dbfa Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Dec 2023 15:35:22 +0100 Subject: [PATCH 09/36] Adding AcreRouter deployment scripts --- core/deploy/02_deploy_acre_router.ts | 22 +++++++++++++++++++ .../22_transfer_ownership_acre_router.ts | 21 ++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 core/deploy/02_deploy_acre_router.ts create mode 100644 core/deploy/22_transfer_ownership_acre_router.ts diff --git a/core/deploy/02_deploy_acre_router.ts b/core/deploy/02_deploy_acre_router.ts new file mode 100644 index 000000000..be2e45b11 --- /dev/null +++ b/core/deploy/02_deploy_acre_router.ts @@ -0,0 +1,22 @@ +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() + + await deployments.deploy("AcreRouter", { + from: deployer, + args: [], + log: true, + waitConfirmations: 1, + }) + + // TODO: Add Etherscan verification + // TODO: Add Tenderly verification +} + +export default func + +func.tags = ["AcreRouter"] +func.dependencies = ["Acre"] diff --git a/core/deploy/22_transfer_ownership_acre_router.ts b/core/deploy/22_transfer_ownership_acre_router.ts new file mode 100644 index 000000000..7652e04d2 --- /dev/null +++ b/core/deploy/22_transfer_ownership_acre_router.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, governance } = await getNamedAccounts() + const { log } = deployments + + log(`transferring ownership of AcreRouter contract to ${governance}`) + + await deployments.execute( + "AcreRouter", + { from: deployer, log: true, waitConfirmations: 1 }, + "transferOwnership", + governance, + ) +} + +export default func + +func.tags = ["TransferOwnershipAcreRouter"] From fd4a2879f59ef8541e995dea24631460a2df5fef Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Dec 2023 15:36:13 +0100 Subject: [PATCH 10/36] Refactoring tests adjusting for helper methods --- core/test/AcreRouter.test.ts | 57 ++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/core/test/AcreRouter.test.ts b/core/test/AcreRouter.test.ts index af3e358b9..a457b9b16 100644 --- a/core/test/AcreRouter.test.ts +++ b/core/test/AcreRouter.test.ts @@ -1,4 +1,4 @@ -import { ethers, getUnnamedAccounts, getNamedAccounts } from "hardhat" +import { getUnnamedAccounts } from "hardhat" import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import { Address } from "hardhat-deploy/types" import { expect } from "chai" @@ -7,33 +7,28 @@ import { takeSnapshot, } from "@nomicfoundation/hardhat-toolbox/network-helpers" import type { AcreRouter } from "../typechain" +import { deployment } from "./helpers/context" +import { getNamedSigner, getUnnamedSigner } from "./helpers/signer" describe("AcreRouter", () => { let snapshot: SnapshotRestorer let acreRouter: AcreRouter - let deployerSigner: HardhatEthersSigner - let governanceSigner: HardhatEthersSigner - let thirdPartySigner: HardhatEthersSigner + let governance: HardhatEthersSigner + let thirdParty: HardhatEthersSigner let vault1: Address let vault2: Address let vault3: Address let vault4: Address - // TODO: - // Put some of this setup to deployment scripts and/or helpers e.g. ownership - // transfer, acreRouter deployment etc. See: https://github.com/thesis/acre/pull/58 - // and https://github.com/thesis/acre/pull/64 before(async () => { const accounts = await getUnnamedAccounts() ;[vault1, vault2, vault3, vault4] = accounts - const { deployer, governance } = await getNamedAccounts() - deployerSigner = await ethers.getSigner(deployer) - governanceSigner = await ethers.getSigner(governance) - thirdPartySigner = await ethers.getSigner(accounts[0]) - const AcreRouter = await ethers.getContractFactory("AcreRouter") - acreRouter = await AcreRouter.connect(deployerSigner).deploy() - acreRouter.transferOwnership(governance) + + governance = (await getNamedSigner()).governance + ;[thirdParty] = await getUnnamedSigner() + + acreRouter = (await deployment()).acreRouter }) beforeEach(async () => { @@ -48,7 +43,7 @@ describe("AcreRouter", () => { context("when caller is not a governance account", () => { it("should revert when adding a vault", async () => { await expect( - acreRouter.connect(thirdPartySigner).addVault(vault1), + acreRouter.connect(thirdParty).addVault(vault1), ).to.be.revertedWithCustomError( acreRouter, "OwnableUnauthorizedAccount", @@ -58,9 +53,9 @@ describe("AcreRouter", () => { context("when caller is a governance account", () => { it("should be able to add vaults", async () => { - await acreRouter.connect(governanceSigner).addVault(vault1) - await acreRouter.connect(governanceSigner).addVault(vault2) - await acreRouter.connect(governanceSigner).addVault(vault3) + await acreRouter.connect(governance).addVault(vault1) + await acreRouter.connect(governance).addVault(vault2) + await acreRouter.connect(governance).addVault(vault3) expect(await acreRouter.vaults(0)).to.equal(vault1) const isVault1Approved = await acreRouter.vaultsInfo(vault1) @@ -76,14 +71,14 @@ describe("AcreRouter", () => { }) it("should not be able to add the same vault twice", async () => { - await acreRouter.connect(governanceSigner).addVault(vault1) + await acreRouter.connect(governance).addVault(vault1) await expect( - acreRouter.connect(governanceSigner).addVault(vault1), + acreRouter.connect(governance).addVault(vault1), ).to.be.revertedWith("Vault already approved") }) it("should emit an event when adding a vault", async () => { - await expect(acreRouter.connect(governanceSigner).addVault(vault1)) + await expect(acreRouter.connect(governance).addVault(vault1)) .to.emit(acreRouter, "VaultAdded") .withArgs(vault1) }) @@ -92,15 +87,15 @@ describe("AcreRouter", () => { describe("removeVault", () => { beforeEach(async () => { - await acreRouter.connect(governanceSigner).addVault(vault1) - await acreRouter.connect(governanceSigner).addVault(vault2) - await acreRouter.connect(governanceSigner).addVault(vault3) + await acreRouter.connect(governance).addVault(vault1) + await acreRouter.connect(governance).addVault(vault2) + await acreRouter.connect(governance).addVault(vault3) }) context("when caller is not a governance account", () => { it("should revert when adding a vault", async () => { await expect( - acreRouter.connect(thirdPartySigner).removeVault(vault1), + acreRouter.connect(thirdParty).removeVault(vault1), ).to.be.revertedWithCustomError( acreRouter, "OwnableUnauthorizedAccount", @@ -110,7 +105,7 @@ describe("AcreRouter", () => { context("when caller is a governance account", () => { it("should be able to remove vaults", async () => { - await acreRouter.connect(governanceSigner).removeVault(vault1) + await acreRouter.connect(governance).removeVault(vault1) // Last vault replaced the first vault in the 'vaults' array expect(await acreRouter.vaults(0)).to.equal(vault3) @@ -118,7 +113,7 @@ describe("AcreRouter", () => { expect(isVault1Approved).to.equal(false) expect(await acreRouter.vaultsLength()).to.equal(2) - await acreRouter.connect(governanceSigner).removeVault(vault2) + await acreRouter.connect(governance).removeVault(vault2) // Last vault (vault2) was removed from the 'vaults' array expect(await acreRouter.vaults(0)).to.equal(vault3) @@ -126,7 +121,7 @@ describe("AcreRouter", () => { const isVault2Approved = await acreRouter.vaultsInfo(vault2) expect(isVault2Approved).to.equal(false) - await acreRouter.connect(governanceSigner).removeVault(vault3) + await acreRouter.connect(governance).removeVault(vault3) expect(await acreRouter.vaultsLength()).to.equal(0) const isVault3Approved = await acreRouter.vaultsInfo(vault3) expect(isVault3Approved).to.equal(false) @@ -134,12 +129,12 @@ describe("AcreRouter", () => { it("should not be able to remove a vault that is not approved", async () => { await expect( - acreRouter.connect(governanceSigner).removeVault(vault4), + acreRouter.connect(governance).removeVault(vault4), ).to.be.revertedWith("Not a vault") }) it("should emit an event when removing a vault", async () => { - await expect(acreRouter.connect(governanceSigner).removeVault(vault1)) + await expect(acreRouter.connect(governance).removeVault(vault1)) .to.emit(acreRouter, "VaultRemoved") .withArgs(vault1) }) From 1c855459606963149363d1c81bbafc940ad9192b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Dec 2023 16:29:00 +0100 Subject: [PATCH 11/36] Adding func-visibility rule --- core/.solhint.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/.solhint.json b/core/.solhint.json index f54af0b9a..435aa7886 100644 --- a/core/.solhint.json +++ b/core/.solhint.json @@ -1,4 +1,5 @@ { "extends": "thesis", - "plugins": [] + "plugins": [], + "rules": { "func-visibility": ["error", { "ignoreConstructors": true }] } } From 2bbf761c41e06f66c75b0ced597724e939377f04 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Dec 2023 16:29:34 +0100 Subject: [PATCH 12/36] Referencing correct vault names in tests --- core/test/AcreRouter.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/test/AcreRouter.test.ts b/core/test/AcreRouter.test.ts index a457b9b16..e19b68f07 100644 --- a/core/test/AcreRouter.test.ts +++ b/core/test/AcreRouter.test.ts @@ -62,11 +62,11 @@ describe("AcreRouter", () => { expect(isVault1Approved).to.equal(true) expect(await acreRouter.vaults(1)).to.equal(vault2) - const isVault2Approved = await acreRouter.vaultsInfo(vault1) + const isVault2Approved = await acreRouter.vaultsInfo(vault2) expect(isVault2Approved).to.equal(true) expect(await acreRouter.vaults(2)).to.equal(vault3) - const isVault3Approved = await acreRouter.vaultsInfo(vault1) + const isVault3Approved = await acreRouter.vaultsInfo(vault3) expect(isVault3Approved).to.equal(true) }) From 3a05a45e3ab62f29ad113330c3ee8b36bf827dd8 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 8 Dec 2023 13:32:35 +0100 Subject: [PATCH 13/36] Replace realpath with another resolution As per https://github.com/thesis/acre/pull/58#discussion_r1418754955 realpath is not available out of the box on OSX. Instead of installing additional package, we changed the way the path is resolved. --- core/scripts/fetch_external_artifacts.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/scripts/fetch_external_artifacts.sh b/core/scripts/fetch_external_artifacts.sh index 5c043568e..c75c98122 100755 --- a/core/scripts/fetch_external_artifacts.sh +++ b/core/scripts/fetch_external_artifacts.sh @@ -1,7 +1,10 @@ #! /bin/bash set -eou pipefail -ROOT_DIR="$(realpath "$(dirname $0)/../")" +ROOT_DIR=$( + cd "$(dirname $0)/../" + pwd -P +) TMP_DIR=${ROOT_DIR}/tmp/external-artifacts EXTERNAL_ARTIFACTS_DIR=${ROOT_DIR}/external From 08b4f01a7afbd3bc37bb6fd1c3498750c8e76583 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 11 Dec 2023 11:27:32 +0100 Subject: [PATCH 14/36] Add a custom theme for `Card` --- dapp/src/components/Overview/index.tsx | 9 +++------ dapp/src/theme/Card.ts | 15 +++++++++++++++ dapp/src/theme/index.ts | 2 ++ 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 dapp/src/theme/Card.ts diff --git a/dapp/src/components/Overview/index.tsx b/dapp/src/components/Overview/index.tsx index e60bab881..7ba50bd6e 100644 --- a/dapp/src/components/Overview/index.tsx +++ b/dapp/src/components/Overview/index.tsx @@ -5,7 +5,6 @@ import { Grid, Icon, Switch, - useColorModeValue, } from "@chakra-ui/react" import PositionDetails from "./PositionDetails" import Statistics from "./Statistics" @@ -14,8 +13,6 @@ import { USD } from "../../constants" import { ChevronRight } from "../../static/icons" export default function Overview() { - // TODO: Create a custom theme for card component - const bg = useColorModeValue("gold.100", "gold.100") return ( @@ -33,9 +30,9 @@ export default function Overview() { h="80vh" gap={4} > - - - + + + ) diff --git a/dapp/src/theme/Card.ts b/dapp/src/theme/Card.ts new file mode 100644 index 000000000..10837842d --- /dev/null +++ b/dapp/src/theme/Card.ts @@ -0,0 +1,15 @@ +import { ComponentSingleStyleConfig } from "@chakra-ui/react" + +const Card: ComponentSingleStyleConfig = { + baseStyle: { + container: { + boxShadow: "none", + borderWidth: "2px", + borderColor: "gold.100", + borderRadius: "xl", + bg: "gold.200", + }, + }, +} + +export default Card \ No newline at end of file diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index 4a6b9847e..8a46264d5 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -3,6 +3,7 @@ import { mode } from "@chakra-ui/theme-tools" import Button from "./Button" import Switch from "./Switch" import { colors, fontSizes, fontWeights, lineHeights } from "./utils" +import Card from "./Card" // Currently, there is no possibility to set all tooltips with hasArrow by defaultProps. // Let's override the defaultProps as follows. @@ -25,6 +26,7 @@ const defaultTheme = { components: { Button, Switch, + Card, }, } From 654d9d38d11d66fb04f1221bc4eb1c4439e8b5ea Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 11 Dec 2023 13:13:54 +0100 Subject: [PATCH 15/36] Update a theme for `Switch` --- dapp/src/theme/Switch.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dapp/src/theme/Switch.ts b/dapp/src/theme/Switch.ts index 52ca179b0..c62fc6616 100644 --- a/dapp/src/theme/Switch.ts +++ b/dapp/src/theme/Switch.ts @@ -3,13 +3,9 @@ import { ComponentSingleStyleConfig } from "@chakra-ui/react" const Switch: ComponentSingleStyleConfig = { baseStyle: { track: { + bg: "grey.700", _checked: { - _dark: { - bg: "grey.700", - }, - _light: { - bg: "grey.700", - }, + bg: "brand.400", }, }, }, From 305cc4167dada47e05fa69ae2bf9761e0e613126 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 11 Dec 2023 13:14:03 +0100 Subject: [PATCH 16/36] Update styles for buttons --- dapp/src/components/Header/ConnectWallet.tsx | 22 ++----- .../components/Overview/PositionDetails.tsx | 6 +- dapp/src/components/Overview/index.tsx | 17 +++--- dapp/src/static/icons/ArrowUpRight.tsx | 16 +++++ dapp/src/static/icons/ChevronRight.tsx | 13 ---- dapp/src/static/icons/index.ts | 2 +- dapp/src/theme/Button.ts | 60 ++++++++++++++----- dapp/src/theme/Card.ts | 2 +- 8 files changed, 78 insertions(+), 60 deletions(-) create mode 100644 dapp/src/static/icons/ArrowUpRight.tsx delete mode 100644 dapp/src/static/icons/ChevronRight.tsx diff --git a/dapp/src/components/Header/ConnectWallet.tsx b/dapp/src/components/Header/ConnectWallet.tsx index bec7df49e..7e5db1ec3 100644 --- a/dapp/src/components/Header/ConnectWallet.tsx +++ b/dapp/src/components/Header/ConnectWallet.tsx @@ -1,7 +1,7 @@ import React from "react" -import { Button, HStack, Icon, Text, Tooltip } from "@chakra-ui/react" +import { Button, HStack, Icon, Text } from "@chakra-ui/react" import { Account } from "@ledgerhq/wallet-api-client" -import { Bitcoin, Ethereum, Info } from "../../static/icons" +import { Bitcoin, Ethereum } from "../../static/icons" import { BITCOIN } from "../../constants" import { useRequestBitcoinAccount, @@ -12,34 +12,24 @@ import { formatSatoshiAmount, truncateAddress } from "../../utils" export type ConnectButtonsProps = { leftIcon: typeof Icon - rightIcon: typeof Icon account: Account | undefined requestAccount: () => Promise } function ConnectButton({ leftIcon, - rightIcon, account, requestAccount, }: ConnectButtonsProps) { const styles = !account - ? { color: "red.400", borderColor: "red.400" } + ? { color: "red.400", borderColor: "red.400", bg: "transparent" } : undefined return ( - + + ) diff --git a/dapp/src/components/Overview/index.tsx b/dapp/src/components/Overview/index.tsx index 7ba50bd6e..8d0f1dc94 100644 --- a/dapp/src/components/Overview/index.tsx +++ b/dapp/src/components/Overview/index.tsx @@ -1,16 +1,10 @@ import React from "react" -import { - Button, - Flex, - Grid, - Icon, - Switch, -} from "@chakra-ui/react" +import { Button, Flex, Grid, Icon, Switch } from "@chakra-ui/react" import PositionDetails from "./PositionDetails" import Statistics from "./Statistics" import TransactionHistory from "./TransactionHistory" import { USD } from "../../constants" -import { ChevronRight } from "../../static/icons" +import { ArrowUpRight } from "../../static/icons" export default function Overview() { return ( @@ -18,8 +12,11 @@ export default function Overview() { {/* TODO: Handle click actions */} Show values in {USD.symbol} - + ), +}) diff --git a/dapp/src/static/icons/ChevronRight.tsx b/dapp/src/static/icons/ChevronRight.tsx deleted file mode 100644 index 8a11116cb..000000000 --- a/dapp/src/static/icons/ChevronRight.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react" -import { createIcon } from "@chakra-ui/react" - -export const ChevronRight = createIcon({ - displayName: "ChevronRight", - viewBox: "0 0 20 20", - path: ( - - ), -}) diff --git a/dapp/src/static/icons/index.ts b/dapp/src/static/icons/index.ts index c625a102e..0752c5839 100644 --- a/dapp/src/static/icons/index.ts +++ b/dapp/src/static/icons/index.ts @@ -1,5 +1,5 @@ export * from "./Info" export * from "./Bitcoin" export * from "./Ethereum" -export * from "./ChevronRight" +export * from "./ArrowUpRight" export * from "./AcreLogo" diff --git a/dapp/src/theme/Button.ts b/dapp/src/theme/Button.ts index cb59ad1b8..7867f1c60 100644 --- a/dapp/src/theme/Button.ts +++ b/dapp/src/theme/Button.ts @@ -1,26 +1,54 @@ -import { mode } from "@chakra-ui/theme-tools" -import type { StyleFunctionProps } from "@chakra-ui/styled-system" import { ComponentSingleStyleConfig } from "@chakra-ui/react" +// TODO: Update the button styles correctly when ready const Button: ComponentSingleStyleConfig = { baseStyle: { - rounded: "none", + size: "md", + borderRadius: "lg", + }, + sizes: { + md: { + fontSize: "sm", + py: "0.5rem", + }, + lg: { + fontSize: "md", + py: "1rem", + }, }, variants: { - solid: (props: StyleFunctionProps) => ({ - // TODO: Update when the dark theme is ready - backgroundColor: mode("brand.400", "brand.400")(props), + solid: { + bg: "brand.400", color: "white", - }), - outline: (props: StyleFunctionProps) => ({ - // TODO: Update when the dark theme is ready - color: mode("grey.700", "grey.700")(props), - borderColor: mode("grey.700", "grey.700")(props), - }), - link: (props: StyleFunctionProps) => ({ - color: mode("grey.700", "grey.700")(props), - textDecoration: "underline", - }), + _hover: { + bg: "brand.500", + }, + _active: { + bg: "brand.400", + }, + }, + outline: { + color: "grey.700", + borderColor: "grey.700", + _hover: { + bg: "rgba(35, 31, 32, 0.05)", + }, + _active: { + bg: "transparent", + }, + }, + card: { + borderWidth: "2px", + borderColor: "gold.100", + borderRadius: "xl", + bg: "gold.200", + _hover: { + bg: "rgba(35, 31, 32, 0.05)", + }, + _active: { + bg: "transparent", + }, + }, }, } diff --git a/dapp/src/theme/Card.ts b/dapp/src/theme/Card.ts index 10837842d..c30266abb 100644 --- a/dapp/src/theme/Card.ts +++ b/dapp/src/theme/Card.ts @@ -12,4 +12,4 @@ const Card: ComponentSingleStyleConfig = { }, } -export default Card \ No newline at end of file +export default Card From aa133deee055d45fee2659f66052c50a19b83e5a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 13:31:09 +0100 Subject: [PATCH 17/36] Removing a rule that already exist in parent config --- core/.solhint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/.solhint.json b/core/.solhint.json index 435aa7886..7afe6405c 100644 --- a/core/.solhint.json +++ b/core/.solhint.json @@ -1,5 +1,5 @@ { "extends": "thesis", "plugins": [], - "rules": { "func-visibility": ["error", { "ignoreConstructors": true }] } + "rules": {} } From 9e0ffd9959bca489a23f44f710f0fda236d7bf4b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 13:36:23 +0100 Subject: [PATCH 18/36] Renaming AcreRouter -> Dispatcher --- .../{AcreRouter.sol => Dispatcher.sol} | 6 +- core/deploy/02_deploy_acre_router.ts | 4 +- .../22_transfer_ownership_acre_router.ts | 4 +- ...{AcreRouter.test.ts => Dispatcher.test.ts} | 76 +++++++++---------- core/test/helpers/context.ts | 6 +- 5 files changed, 48 insertions(+), 48 deletions(-) rename core/contracts/{AcreRouter.sol => Dispatcher.sol} (94%) rename core/test/{AcreRouter.test.ts => Dispatcher.test.ts} (57%) diff --git a/core/contracts/AcreRouter.sol b/core/contracts/Dispatcher.sol similarity index 94% rename from core/contracts/AcreRouter.sol rename to core/contracts/Dispatcher.sol index c7224e324..02801b743 100644 --- a/core/contracts/AcreRouter.sol +++ b/core/contracts/Dispatcher.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.21; import "@openzeppelin/contracts/access/Ownable.sol"; -/// @title AcreRouter -/// @notice AcreRouter is a contract that routes TBTC from stBTC (Acre) to +/// @title Dispatcher +/// @notice Dispatcher is a contract that routes TBTC from stBTC (Acre) to /// a given vault and back. Vaults supply yield strategies with TBTC that /// generate yield for Bitcoin holders. -contract AcreRouter is Ownable { +contract Dispatcher is Ownable { struct Vault { bool approved; } diff --git a/core/deploy/02_deploy_acre_router.ts b/core/deploy/02_deploy_acre_router.ts index be2e45b11..bf99d4d73 100644 --- a/core/deploy/02_deploy_acre_router.ts +++ b/core/deploy/02_deploy_acre_router.ts @@ -5,7 +5,7 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { getNamedAccounts, deployments } = hre const { deployer } = await getNamedAccounts() - await deployments.deploy("AcreRouter", { + await deployments.deploy("Dispatcher", { from: deployer, args: [], log: true, @@ -18,5 +18,5 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { export default func -func.tags = ["AcreRouter"] +func.tags = ["Dispatcher"] func.dependencies = ["Acre"] diff --git a/core/deploy/22_transfer_ownership_acre_router.ts b/core/deploy/22_transfer_ownership_acre_router.ts index 7652e04d2..2d7748580 100644 --- a/core/deploy/22_transfer_ownership_acre_router.ts +++ b/core/deploy/22_transfer_ownership_acre_router.ts @@ -6,10 +6,10 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { deployer, governance } = await getNamedAccounts() const { log } = deployments - log(`transferring ownership of AcreRouter contract to ${governance}`) + log(`transferring ownership of Dispatcher contract to ${governance}`) await deployments.execute( - "AcreRouter", + "Dispatcher", { from: deployer, log: true, waitConfirmations: 1 }, "transferOwnership", governance, diff --git a/core/test/AcreRouter.test.ts b/core/test/Dispatcher.test.ts similarity index 57% rename from core/test/AcreRouter.test.ts rename to core/test/Dispatcher.test.ts index e19b68f07..c9037b850 100644 --- a/core/test/AcreRouter.test.ts +++ b/core/test/Dispatcher.test.ts @@ -6,14 +6,14 @@ import { SnapshotRestorer, takeSnapshot, } from "@nomicfoundation/hardhat-toolbox/network-helpers" -import type { AcreRouter } from "../typechain" +import type { Dispatcher } from "../typechain" import { deployment } from "./helpers/context" import { getNamedSigner, getUnnamedSigner } from "./helpers/signer" -describe("AcreRouter", () => { +describe("Dispatcher", () => { let snapshot: SnapshotRestorer - let acreRouter: AcreRouter + let dispatcher: Dispatcher let governance: HardhatEthersSigner let thirdParty: HardhatEthersSigner let vault1: Address @@ -28,7 +28,7 @@ describe("AcreRouter", () => { governance = (await getNamedSigner()).governance ;[thirdParty] = await getUnnamedSigner() - acreRouter = (await deployment()).acreRouter + dispatcher = (await deployment()).dispatcher }) beforeEach(async () => { @@ -43,9 +43,9 @@ describe("AcreRouter", () => { context("when caller is not a governance account", () => { it("should revert when adding a vault", async () => { await expect( - acreRouter.connect(thirdParty).addVault(vault1), + dispatcher.connect(thirdParty).addVault(vault1), ).to.be.revertedWithCustomError( - acreRouter, + dispatcher, "OwnableUnauthorizedAccount", ) }) @@ -53,33 +53,33 @@ describe("AcreRouter", () => { context("when caller is a governance account", () => { it("should be able to add vaults", async () => { - await acreRouter.connect(governance).addVault(vault1) - await acreRouter.connect(governance).addVault(vault2) - await acreRouter.connect(governance).addVault(vault3) + await dispatcher.connect(governance).addVault(vault1) + await dispatcher.connect(governance).addVault(vault2) + await dispatcher.connect(governance).addVault(vault3) - expect(await acreRouter.vaults(0)).to.equal(vault1) - const isVault1Approved = await acreRouter.vaultsInfo(vault1) + expect(await dispatcher.vaults(0)).to.equal(vault1) + const isVault1Approved = await dispatcher.vaultsInfo(vault1) expect(isVault1Approved).to.equal(true) - expect(await acreRouter.vaults(1)).to.equal(vault2) - const isVault2Approved = await acreRouter.vaultsInfo(vault2) + expect(await dispatcher.vaults(1)).to.equal(vault2) + const isVault2Approved = await dispatcher.vaultsInfo(vault2) expect(isVault2Approved).to.equal(true) - expect(await acreRouter.vaults(2)).to.equal(vault3) - const isVault3Approved = await acreRouter.vaultsInfo(vault3) + expect(await dispatcher.vaults(2)).to.equal(vault3) + const isVault3Approved = await dispatcher.vaultsInfo(vault3) expect(isVault3Approved).to.equal(true) }) it("should not be able to add the same vault twice", async () => { - await acreRouter.connect(governance).addVault(vault1) + await dispatcher.connect(governance).addVault(vault1) await expect( - acreRouter.connect(governance).addVault(vault1), + dispatcher.connect(governance).addVault(vault1), ).to.be.revertedWith("Vault already approved") }) it("should emit an event when adding a vault", async () => { - await expect(acreRouter.connect(governance).addVault(vault1)) - .to.emit(acreRouter, "VaultAdded") + await expect(dispatcher.connect(governance).addVault(vault1)) + .to.emit(dispatcher, "VaultAdded") .withArgs(vault1) }) }) @@ -87,17 +87,17 @@ describe("AcreRouter", () => { describe("removeVault", () => { beforeEach(async () => { - await acreRouter.connect(governance).addVault(vault1) - await acreRouter.connect(governance).addVault(vault2) - await acreRouter.connect(governance).addVault(vault3) + await dispatcher.connect(governance).addVault(vault1) + await dispatcher.connect(governance).addVault(vault2) + await dispatcher.connect(governance).addVault(vault3) }) context("when caller is not a governance account", () => { it("should revert when adding a vault", async () => { await expect( - acreRouter.connect(thirdParty).removeVault(vault1), + dispatcher.connect(thirdParty).removeVault(vault1), ).to.be.revertedWithCustomError( - acreRouter, + dispatcher, "OwnableUnauthorizedAccount", ) }) @@ -105,37 +105,37 @@ describe("AcreRouter", () => { context("when caller is a governance account", () => { it("should be able to remove vaults", async () => { - await acreRouter.connect(governance).removeVault(vault1) + await dispatcher.connect(governance).removeVault(vault1) // Last vault replaced the first vault in the 'vaults' array - expect(await acreRouter.vaults(0)).to.equal(vault3) - const isVault1Approved = await acreRouter.vaultsInfo(vault1) + expect(await dispatcher.vaults(0)).to.equal(vault3) + const isVault1Approved = await dispatcher.vaultsInfo(vault1) expect(isVault1Approved).to.equal(false) - expect(await acreRouter.vaultsLength()).to.equal(2) + expect(await dispatcher.vaultsLength()).to.equal(2) - await acreRouter.connect(governance).removeVault(vault2) + await dispatcher.connect(governance).removeVault(vault2) // Last vault (vault2) was removed from the 'vaults' array - expect(await acreRouter.vaults(0)).to.equal(vault3) - expect(await acreRouter.vaultsLength()).to.equal(1) - const isVault2Approved = await acreRouter.vaultsInfo(vault2) + expect(await dispatcher.vaults(0)).to.equal(vault3) + expect(await dispatcher.vaultsLength()).to.equal(1) + const isVault2Approved = await dispatcher.vaultsInfo(vault2) expect(isVault2Approved).to.equal(false) - await acreRouter.connect(governance).removeVault(vault3) - expect(await acreRouter.vaultsLength()).to.equal(0) - const isVault3Approved = await acreRouter.vaultsInfo(vault3) + await dispatcher.connect(governance).removeVault(vault3) + expect(await dispatcher.vaultsLength()).to.equal(0) + const isVault3Approved = await dispatcher.vaultsInfo(vault3) expect(isVault3Approved).to.equal(false) }) it("should not be able to remove a vault that is not approved", async () => { await expect( - acreRouter.connect(governance).removeVault(vault4), + dispatcher.connect(governance).removeVault(vault4), ).to.be.revertedWith("Not a vault") }) it("should emit an event when removing a vault", async () => { - await expect(acreRouter.connect(governance).removeVault(vault1)) - .to.emit(acreRouter, "VaultRemoved") + await expect(dispatcher.connect(governance).removeVault(vault1)) + .to.emit(dispatcher, "VaultRemoved") .withArgs(vault1) }) }) diff --git a/core/test/helpers/context.ts b/core/test/helpers/context.ts index ec4c56cbd..34875e43d 100644 --- a/core/test/helpers/context.ts +++ b/core/test/helpers/context.ts @@ -2,7 +2,7 @@ import { deployments } from "hardhat" import { getDeployedContract } from "./contract" -import type { Acre, AcreRouter, TestERC20 } from "../../typechain" +import type { Acre, Dispatcher, TestERC20 } from "../../typechain" // eslint-disable-next-line import/prefer-default-export export async function deployment() { @@ -10,7 +10,7 @@ export async function deployment() { const tbtc: TestERC20 = await getDeployedContract("TBTC") const acre: Acre = await getDeployedContract("Acre") - const acreRouter: AcreRouter = await getDeployedContract("AcreRouter") + const dispatcher: Dispatcher = await getDeployedContract("Dispatcher") - return { tbtc, acre, acreRouter } + return { tbtc, acre, dispatcher } } From b56c0ec251325348d9be63e746480e3ca4d0df4b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 13:50:27 +0100 Subject: [PATCH 19/36] Changing a comment for vaults array --- core/contracts/Dispatcher.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/contracts/Dispatcher.sol b/core/contracts/Dispatcher.sol index 02801b743..ff2bddd18 100644 --- a/core/contracts/Dispatcher.sol +++ b/core/contracts/Dispatcher.sol @@ -12,12 +12,12 @@ contract Dispatcher is Ownable { bool approved; } - /// @notice Approved vaults within the Yiern Modules that implement ERC4626 - /// standard. These vaults deposit assets to yield strategies, e.g. - /// Uniswap V3 WBTC/TBTC pool. Vault can be a part of Acre ecosystem - /// or can be implemented externally. As long as it complies with - /// ERC4626 standard and is approved by the owner it can be - /// plugged into Acre. + /// @notice Approved 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 approved by the owner it can be plugged into + /// Acre. address[] public vaults; mapping(address => Vault) public vaultsInfo; From d7e8ac65a3a2bb5e9d42062ff5001e0690dfb75b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 13:53:09 +0100 Subject: [PATCH 20/36] Renaming Vault -> VaultInfo struct --- core/contracts/Dispatcher.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/contracts/Dispatcher.sol b/core/contracts/Dispatcher.sol index ff2bddd18..fb9dfdca0 100644 --- a/core/contracts/Dispatcher.sol +++ b/core/contracts/Dispatcher.sol @@ -8,7 +8,7 @@ import "@openzeppelin/contracts/access/Ownable.sol"; /// a given vault and back. Vaults supply yield strategies with TBTC that /// generate yield for Bitcoin holders. contract Dispatcher is Ownable { - struct Vault { + struct VaultInfo { bool approved; } @@ -19,7 +19,7 @@ contract Dispatcher is Ownable { /// standard and is approved by the owner it can be plugged into /// Acre. address[] public vaults; - mapping(address => Vault) public vaultsInfo; + mapping(address => VaultInfo) public vaultsInfo; event VaultAdded(address indexed vault); event VaultRemoved(address indexed vault); From dbb6bcc362a4e4a1de1971082345be5e0579b81c Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 11 Dec 2023 13:55:11 +0100 Subject: [PATCH 21/36] Minor UI improvements --- dapp/src/components/Header/index.tsx | 2 +- dapp/src/components/Overview/PositionDetails.tsx | 2 +- dapp/src/components/Overview/index.tsx | 14 +++++++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/dapp/src/components/Header/index.tsx b/dapp/src/components/Header/index.tsx index 09d7fc1ed..dfc46b955 100644 --- a/dapp/src/components/Header/index.tsx +++ b/dapp/src/components/Header/index.tsx @@ -5,7 +5,7 @@ import { AcreLogo } from "../../static/icons" export default function Header() { return ( - + diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index e6fbd621a..b823d65c3 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -35,7 +35,7 @@ export default function PositionDetails(props: CardProps) { {/* TODO: Handle click actions */} diff --git a/dapp/src/components/Overview/index.tsx b/dapp/src/components/Overview/index.tsx index 8d0f1dc94..ce2b8394a 100644 --- a/dapp/src/components/Overview/index.tsx +++ b/dapp/src/components/Overview/index.tsx @@ -1,17 +1,21 @@ import React from "react" -import { Button, Flex, Grid, Icon, Switch } from "@chakra-ui/react" +import { Button, Flex, Grid, HStack, Icon, Switch } from "@chakra-ui/react" import PositionDetails from "./PositionDetails" import Statistics from "./Statistics" import TransactionHistory from "./TransactionHistory" import { USD } from "../../constants" import { ArrowUpRight } from "../../static/icons" +import { TextSm } from "../Typography" export default function Overview() { return ( - - {/* TODO: Handle click actions */} - Show values in {USD.symbol} + + + {/* TODO: Handle click actions */} + + Show values in {USD.symbol} +