From 7ca8dbe2709f85b389ca2ff18baf40d254bce593 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 21 Nov 2023 12:46:06 +0100 Subject: [PATCH 01/53] Refactor for the navbar - use a stack elements in the vertical or horizontal direction - create separate components for icons - add `currencies.ts` file --- dapp/src/assets/bitcoin.svg | 16 ------- dapp/src/assets/ethereum.svg | 16 ------- dapp/src/assets/info.svg | 11 ----- dapp/src/components/Navbar/ConnectWallet.tsx | 47 ++++++++++++------- dapp/src/constants/chains.ts | 6 --- dapp/src/constants/currencies.ts | 7 +++ dapp/src/constants/index.ts | 1 + dapp/src/static/icons/Bitcoin.tsx | 39 ++++++++++++++++ dapp/src/static/icons/Ethereum.tsx | 49 ++++++++++++++++++++ dapp/src/static/icons/Info.tsx | 32 +++++++++++++ dapp/src/static/icons/index.ts | 3 ++ 11 files changed, 161 insertions(+), 66 deletions(-) delete mode 100644 dapp/src/assets/bitcoin.svg delete mode 100644 dapp/src/assets/ethereum.svg delete mode 100644 dapp/src/assets/info.svg create mode 100644 dapp/src/constants/currencies.ts create mode 100644 dapp/src/static/icons/Bitcoin.tsx create mode 100644 dapp/src/static/icons/Ethereum.tsx create mode 100644 dapp/src/static/icons/Info.tsx create mode 100644 dapp/src/static/icons/index.ts diff --git a/dapp/src/assets/bitcoin.svg b/dapp/src/assets/bitcoin.svg deleted file mode 100644 index 8b99c75c1..000000000 --- a/dapp/src/assets/bitcoin.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dapp/src/assets/ethereum.svg b/dapp/src/assets/ethereum.svg deleted file mode 100644 index 0a528dc23..000000000 --- a/dapp/src/assets/ethereum.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dapp/src/assets/info.svg b/dapp/src/assets/info.svg deleted file mode 100644 index 5842e381c..000000000 --- a/dapp/src/assets/info.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/dapp/src/components/Navbar/ConnectWallet.tsx b/dapp/src/components/Navbar/ConnectWallet.tsx index 62838f620..32a0b9261 100644 --- a/dapp/src/components/Navbar/ConnectWallet.tsx +++ b/dapp/src/components/Navbar/ConnectWallet.tsx @@ -1,9 +1,14 @@ import React, { useContext } from "react" -import { Box, Button, Image, Text } from "@chakra-ui/react" +import { + Button, + HStack, + Icon, + Text, + Tooltip, + useColorModeValue, +} from "@chakra-ui/react" import { Account } from "@ledgerhq/wallet-api-client" -import BitcoinIcon from "../../assets/bitcoin.svg" -import EthereumIcon from "../../assets/ethereum.svg" -import InfoIcon from "../../assets/info.svg" +import { Bitcoin, Ethereum, Info } from "../../static/icons" import { BITCOIN } from "../../constants" import { useRequestBitcoinAccount, @@ -13,8 +18,8 @@ import { LedgerLiveAppContext } from "../../contexts/LedgerLiveAppContext" import { formatSatoshiAmount, truncateAddress } from "../../utils" export type ConnectButtonsProps = { - leftIcon: string - rightIcon: string + leftIcon: typeof Icon + rightIcon: typeof Icon account: Account | null requestAccount: () => Promise } @@ -26,13 +31,21 @@ function ConnectButton({ requestAccount, }: ConnectButtonsProps) { const styles = !account ? { color: "error", borderColor: "error" } : undefined + const colorRightIcon = useColorModeValue("black", "grey.80") + return ( + ) } diff --git a/dapp/src/static/icons/ChevronRight.tsx b/dapp/src/static/icons/ChevronRight.tsx new file mode 100644 index 000000000..7013170cf --- /dev/null +++ b/dapp/src/static/icons/ChevronRight.tsx @@ -0,0 +1,24 @@ +import React from "react" +import { createIcon } from "@chakra-ui/react" + +export const ChevronRight = createIcon({ + displayName: "ChevronRight", + viewBox: "0 0 16 16", + path: ( + + + + ), +}) diff --git a/dapp/src/static/icons/index.ts b/dapp/src/static/icons/index.ts index b3c64db44..66c6e3aa9 100644 --- a/dapp/src/static/icons/index.ts +++ b/dapp/src/static/icons/index.ts @@ -1,3 +1,4 @@ export * from "./Info" export * from "./Bitcoin" export * from "./Ethereum" +export * from "./ChevronRight" diff --git a/dapp/src/theme/Button.ts b/dapp/src/theme/Button.ts index ac143f407..51a7cbf7a 100644 --- a/dapp/src/theme/Button.ts +++ b/dapp/src/theme/Button.ts @@ -14,6 +14,10 @@ const Button = { color: mode("black", "grey.80")(props), borderColor: mode("black", "grey.50")(props), }), + link: (props: StyleFunctionProps) => ({ + color: mode("black", "grey.50")(props), + textDecoration: "underline", + }), }, } diff --git a/dapp/src/theme/Switch.ts b/dapp/src/theme/Switch.ts new file mode 100644 index 000000000..1074491b4 --- /dev/null +++ b/dapp/src/theme/Switch.ts @@ -0,0 +1,16 @@ +const Switch = { + baseStyle: { + track: { + _checked: { + _dark: { + bg: "purple", + }, + _light: { + bg: "grey.200", + }, + }, + }, + }, +} + +export default Switch diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index 454c51fc1..bedf1ea49 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -1,14 +1,19 @@ -import { StyleFunctionProps, extendTheme } from "@chakra-ui/react" +import { StyleFunctionProps, Tooltip, extendTheme } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" import Button from "./Button" +import Switch from "./Switch" import { colors } from "./utils" +// Currently, there is no possibility to set all tooltips with hasArrow by defaultProps. +// Let's override the defaultProps as follows. +Tooltip.defaultProps = { ...Tooltip.defaultProps, hasArrow: true } + const defaultTheme = { colors, styles: { global: (props: StyleFunctionProps) => ({ "html, body, #root, #root > div": { - backgroundColor: mode("lightGrey", "darkGrey")(props), + backgroundColor: mode("grey.100", "grey.300")(props), color: mode("black", "grey.80")(props), minHeight: "100vh", }, @@ -16,6 +21,7 @@ const defaultTheme = { }, components: { Button, + Switch, }, } diff --git a/dapp/src/theme/utils/colors.ts b/dapp/src/theme/utils/colors.ts index 5ea1f1720..4d8352bb2 100644 --- a/dapp/src/theme/utils/colors.ts +++ b/dapp/src/theme/utils/colors.ts @@ -8,7 +8,9 @@ export const colors = { grey: { 50: "rgba(255, 255, 255, 0.50)", 80: "rgba(255, 255, 255, 0.80)", + 100: "#ECECEC", + 200: "#37393C", + 300: "#1D1E20", + 400: "#1A1B1D", }, - lightGrey: "#ECECEC", - darkGrey: "#1A1B1D", } From a5b4a407c69f2cc93f97f6ab91f0fd71609d1ade Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 21 Nov 2023 12:56:40 +0100 Subject: [PATCH 03/53] Split the site into 3 main elements --- dapp/src/DApp.tsx | 18 +++---- .../components/Overview/PositionDetails.tsx | 51 +++++++++++++++++++ dapp/src/components/Overview/Statistics.tsx | 10 ++++ .../Overview/TransactionHistory.tsx | 10 ++++ dapp/src/components/Overview/index.tsx | 27 ++++++++++ 5 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 dapp/src/components/Overview/PositionDetails.tsx create mode 100644 dapp/src/components/Overview/Statistics.tsx create mode 100644 dapp/src/components/Overview/TransactionHistory.tsx create mode 100644 dapp/src/components/Overview/index.tsx diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index 7042edff1..77c6b995f 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -1,25 +1,19 @@ -import React, { useContext } from "react" -import { ChakraProvider, Box, Text } from "@chakra-ui/react" +import React from "react" +import { ChakraProvider, Box } from "@chakra-ui/react" import { useDetectThemeMode } from "./hooks" import { LedgerWalletAPIProvider } from "./providers" import theme from "./theme" -import { - LedgerLiveAppContext, - LedgerLiveAppProvider, -} from "./contexts/LedgerLiveAppContext" +import { LedgerLiveAppProvider } from "./contexts/LedgerLiveAppContext" import Navbar from "./components/Navbar" +import Overview from "./components/Overview" function DApp() { useDetectThemeMode() - const { btcAccount, ethAccount } = useContext(LedgerLiveAppContext) - return ( - + -

Ledger live - Acre dApp

- {btcAccount && Account: {btcAccount.address}} - {ethAccount && Account: {ethAccount.address}} +
) } diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx new file mode 100644 index 000000000..596b225bb --- /dev/null +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -0,0 +1,51 @@ +import React from "react" +import { + Text, + Button, + Box, + VStack, + HStack, + Tooltip, + Icon, + useColorModeValue, +} from "@chakra-ui/react" +import { BITCOIN, FIAT_CURRENCY_USD } from "../../constants" +import { Info } from "../../static/icons" + +export default function PositionDetails() { + return ( + + + Your positions + + + 34.75 + {BITCOIN.token} + + + + 1.245.148,1 + {FIAT_CURRENCY_USD} + + {/* TODO: Add correct text for tooltip */} + + + + + + + {/* Unset alignItems to get a full-width button. */} + + {/* TODO: Handle click actions */} + + + + + ) +} diff --git a/dapp/src/components/Overview/Statistics.tsx b/dapp/src/components/Overview/Statistics.tsx new file mode 100644 index 000000000..b0c2dc0d6 --- /dev/null +++ b/dapp/src/components/Overview/Statistics.tsx @@ -0,0 +1,10 @@ +import React from "react" +import { Text, Box } from "@chakra-ui/react" + +export default function Statistics() { + return ( + + Pool stats + + ) +} diff --git a/dapp/src/components/Overview/TransactionHistory.tsx b/dapp/src/components/Overview/TransactionHistory.tsx new file mode 100644 index 000000000..312233938 --- /dev/null +++ b/dapp/src/components/Overview/TransactionHistory.tsx @@ -0,0 +1,10 @@ +import React from "react" +import { Text, Box } from "@chakra-ui/react" + +export default function TransactionHistory() { + return ( + + Transaction history + + ) +} diff --git a/dapp/src/components/Overview/index.tsx b/dapp/src/components/Overview/index.tsx new file mode 100644 index 000000000..88213cd78 --- /dev/null +++ b/dapp/src/components/Overview/index.tsx @@ -0,0 +1,27 @@ +import React from "react" +import { Grid, GridItem, useColorModeValue } from "@chakra-ui/react" +import PositionDetails from "./PositionDetails" +import Statistics from "./Statistics" +import TransactionHistory from "./TransactionHistory" + +export default function Overview() { + const bg = useColorModeValue("white", "grey.400") + return ( + + + + + + + + + + + + ) +} From 287e9529dc039e80b58a8d340dab723752ccbe64 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 22 Nov 2023 14:15:46 +0100 Subject: [PATCH 04/53] Grid update for overview page --- dapp/src/components/Overview/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dapp/src/components/Overview/index.tsx b/dapp/src/components/Overview/index.tsx index 88213cd78..2d3a8ac42 100644 --- a/dapp/src/components/Overview/index.tsx +++ b/dapp/src/components/Overview/index.tsx @@ -8,18 +8,18 @@ export default function Overview() { const bg = useColorModeValue("white", "grey.400") return ( - + - + - + From 568bc1d71a6e96c35f66601d3dfc63f4d5469bec Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 27 Nov 2023 14:32:52 +0100 Subject: [PATCH 05/53] Fix an issue after merging --- dapp/src/components/Overview/PositionDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index 596b225bb..e5a426efb 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -26,7 +26,7 @@ export default function PositionDetails() { 34.75 - {BITCOIN.token} + {BITCOIN.symbol} From 0abcd5d631c2eaf07535845c1d77a703876d2b94 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 28 Nov 2023 16:47:18 +0100 Subject: [PATCH 06/53] Create a USD currency --- dapp/src/components/Navbar/index.tsx | 4 ++-- dapp/src/components/Overview/PositionDetails.tsx | 4 ++-- dapp/src/constants/currency.ts | 8 ++++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/dapp/src/components/Navbar/index.tsx b/dapp/src/components/Navbar/index.tsx index 23c4edb9e..f524f6fe5 100644 --- a/dapp/src/components/Navbar/index.tsx +++ b/dapp/src/components/Navbar/index.tsx @@ -2,7 +2,7 @@ import React from "react" import { Box, Button, HStack, Icon, Switch } from "@chakra-ui/react" import ConnectWallet from "./ConnectWallet" import { ChevronRight } from "../../static/icons" -import { FIAT_CURRENCY_USD } from "../../constants" +import { USD } from "../../constants" export default function Navbar() { return ( @@ -12,7 +12,7 @@ export default function Navbar() { {/* TODO: Handle click actions */} - Show values in {FIAT_CURRENCY_USD} + Show values in {USD.symbol} diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index e5a426efb..9e5c4be71 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -9,7 +9,7 @@ import { Icon, useColorModeValue, } from "@chakra-ui/react" -import { BITCOIN, FIAT_CURRENCY_USD } from "../../constants" +import { BITCOIN, USD } from "../../constants" import { Info } from "../../static/icons" export default function PositionDetails() { @@ -31,7 +31,7 @@ export default function PositionDetails() { 1.245.148,1 - {FIAT_CURRENCY_USD} + {USD.symbol} {/* TODO: Add correct text for tooltip */} diff --git a/dapp/src/constants/currency.ts b/dapp/src/constants/currency.ts index dee8507e4..feecf3127 100644 --- a/dapp/src/constants/currency.ts +++ b/dapp/src/constants/currency.ts @@ -12,10 +12,14 @@ export const ETHEREUM: Currency = { decimals: 18, } +export const USD: Currency = { + name: "United States Dollar", + symbol: "USD", + decimals: 10, +} + export const CURRENCY_ID_BITCOIN = import.meta.env.VITE_USE_TESTNET === "true" ? "bitcoin_testnet" : "bitcoin" export const CURRENCY_ID_ETHEREUM = import.meta.env.VITE_USE_TESTNET === "true" ? "ethereum_goerli" : "ethereum" - -export const FIAT_CURRENCY_USD = "USD" From 526052644750a8cc7989e1d4e9606c409c26659d Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 28 Nov 2023 18:50:50 +0100 Subject: [PATCH 07/53] Improve the use of `VStack`, `HStack` and `Flex` The Stack component and the Flex component have their children spaced out evenly but the key difference is that the Stack won't span the entire width of the container whereas the Flex will. Let's use the Stack component when it is a simple row or column of items. If we need to customize the items let's use Flex. More info: https://chakra-ui.com/docs/components/stack#notes-on-stack-vs-flex --- dapp/src/DApp.tsx | 6 ++--- dapp/src/components/Navbar/ConnectWallet.tsx | 2 +- dapp/src/components/Navbar/index.tsx | 14 +++++------ .../components/Overview/PositionDetails.tsx | 25 +++++++------------ 4 files changed, 20 insertions(+), 27 deletions(-) diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index 453c6c172..427d36de1 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -1,5 +1,5 @@ import React from "react" -import { ChakraProvider, Box } from "@chakra-ui/react" +import { ChakraProvider, Flex } from "@chakra-ui/react" import { useDetectThemeMode } from "./hooks" import theme from "./theme" import { LedgerWalletAPIProvider, WalletContextProvider } from "./contexts" @@ -10,10 +10,10 @@ function DApp() { useDetectThemeMode() return ( - + - + ) } diff --git a/dapp/src/components/Navbar/ConnectWallet.tsx b/dapp/src/components/Navbar/ConnectWallet.tsx index 14a20eae5..c632fb6e5 100644 --- a/dapp/src/components/Navbar/ConnectWallet.tsx +++ b/dapp/src/components/Navbar/ConnectWallet.tsx @@ -59,7 +59,7 @@ export default function ConnectWallet() { const { btcAccount, ethAccount } = useWalletContext() return ( - + Balance diff --git a/dapp/src/components/Navbar/index.tsx b/dapp/src/components/Navbar/index.tsx index f524f6fe5..24cd41aaf 100644 --- a/dapp/src/components/Navbar/index.tsx +++ b/dapp/src/components/Navbar/index.tsx @@ -1,22 +1,22 @@ import React from "react" -import { Box, Button, HStack, Icon, Switch } from "@chakra-ui/react" +import { Button, Flex, Icon, Switch } from "@chakra-ui/react" import ConnectWallet from "./ConnectWallet" import { ChevronRight } from "../../static/icons" import { USD } from "../../constants" export default function Navbar() { return ( - - + + - - + + {/* TODO: Handle click actions */} Show values in {USD.symbol} - - + + ) } diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index 9e5c4be71..8415c8910 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -3,32 +3,26 @@ import { Text, Button, Box, - VStack, HStack, Tooltip, Icon, useColorModeValue, + Flex, } from "@chakra-ui/react" import { BITCOIN, USD } from "../../constants" import { Info } from "../../static/icons" export default function PositionDetails() { return ( - + Your positions - + 34.75 {BITCOIN.symbol} - + 1.245.148,1 {USD.symbol} @@ -37,15 +31,14 @@ export default function PositionDetails() { - - + +
- {/* Unset alignItems to get a full-width button. */} - + {/* TODO: Handle click actions */} - - + + ) } From 32b0f4d41674eca675bdc3ded6168257dfb9ac1f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 4 Dec 2023 15:59:27 +0100 Subject: [PATCH 08/53] 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 09/53] 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 05e1a959c02c25368da0e8f2bc87c7f2dae13758 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 6 Dec 2023 08:14:26 +0100 Subject: [PATCH 10/53] Rename `Navbar` to `Header` --- dapp/src/DApp.tsx | 4 ++-- dapp/src/components/{Navbar => Header}/ConnectWallet.tsx | 0 dapp/src/components/{Navbar => Header}/index.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename dapp/src/components/{Navbar => Header}/ConnectWallet.tsx (100%) rename dapp/src/components/{Navbar => Header}/index.tsx (94%) diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index 427d36de1..de6788fb1 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -3,7 +3,7 @@ import { ChakraProvider, Flex } from "@chakra-ui/react" import { useDetectThemeMode } from "./hooks" import theme from "./theme" import { LedgerWalletAPIProvider, WalletContextProvider } from "./contexts" -import Navbar from "./components/Navbar" +import Header from "./components/Header" import Overview from "./components/Overview" function DApp() { @@ -11,7 +11,7 @@ function DApp() { return ( - +
) diff --git a/dapp/src/components/Navbar/ConnectWallet.tsx b/dapp/src/components/Header/ConnectWallet.tsx similarity index 100% rename from dapp/src/components/Navbar/ConnectWallet.tsx rename to dapp/src/components/Header/ConnectWallet.tsx diff --git a/dapp/src/components/Navbar/index.tsx b/dapp/src/components/Header/index.tsx similarity index 94% rename from dapp/src/components/Navbar/index.tsx rename to dapp/src/components/Header/index.tsx index 24cd41aaf..df7e8d8ff 100644 --- a/dapp/src/components/Navbar/index.tsx +++ b/dapp/src/components/Header/index.tsx @@ -4,7 +4,7 @@ import ConnectWallet from "./ConnectWallet" import { ChevronRight } from "../../static/icons" import { USD } from "../../constants" -export default function Navbar() { +export default function Header() { return ( From 457f9facf08e1c8aec16f34dd4dbad3cdfd2b6f3 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 6 Dec 2023 08:16:16 +0100 Subject: [PATCH 11/53] Wrap the main content under `main` tag --- dapp/src/DApp.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index de6788fb1..e0e45ef02 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -12,7 +12,9 @@ function DApp() { return (
- +
+ +
) } From 2dca2bf2cc1c0b7fbebe85bab934e5a91e0bf519 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 6 Dec 2023 08:57:52 +0100 Subject: [PATCH 12/53] Refactor for spacing between component --- dapp/src/DApp.tsx | 6 +-- dapp/src/components/Header/ConnectWallet.tsx | 6 +-- dapp/src/components/Header/index.tsx | 17 ++---- .../components/Overview/PositionDetails.tsx | 9 ++-- dapp/src/components/Overview/index.tsx | 53 +++++++++++++------ 5 files changed, 49 insertions(+), 42 deletions(-) diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index e0e45ef02..085b6fbe8 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -1,5 +1,5 @@ import React from "react" -import { ChakraProvider, Flex } from "@chakra-ui/react" +import { ChakraProvider } from "@chakra-ui/react" import { useDetectThemeMode } from "./hooks" import theme from "./theme" import { LedgerWalletAPIProvider, WalletContextProvider } from "./contexts" @@ -10,12 +10,12 @@ function DApp() { useDetectThemeMode() return ( - + <>
- + ) } diff --git a/dapp/src/components/Header/ConnectWallet.tsx b/dapp/src/components/Header/ConnectWallet.tsx index c632fb6e5..8b35a010b 100644 --- a/dapp/src/components/Header/ConnectWallet.tsx +++ b/dapp/src/components/Header/ConnectWallet.tsx @@ -37,7 +37,7 @@ function ConnectButton({ - + + ) } diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index 8415c8910..972db661f 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -2,7 +2,6 @@ import React from "react" import { Text, Button, - Box, HStack, Tooltip, Icon, @@ -14,9 +13,9 @@ import { Info } from "../../static/icons" export default function PositionDetails() { return ( - - - Your positions + + + Your positions 34.75 @@ -33,7 +32,7 @@ export default function PositionDetails() { - + {/* TODO: Handle click actions */} diff --git a/dapp/src/components/Overview/index.tsx b/dapp/src/components/Overview/index.tsx index 2d3a8ac42..38f7d0068 100644 --- a/dapp/src/components/Overview/index.tsx +++ b/dapp/src/components/Overview/index.tsx @@ -1,27 +1,46 @@ import React from "react" -import { Grid, GridItem, useColorModeValue } from "@chakra-ui/react" +import { + Button, + Flex, + Grid, + GridItem, + Icon, + Switch, + useColorModeValue, +} 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" export default function Overview() { const bg = useColorModeValue("white", "grey.400") return ( - - - - - - - - - - - + + + {/* TODO: Handle click actions */} + Show values in {USD.symbol} + + + + + + + + + + + + + + ) } From 5fafc571129383702a03da343824916db71f08ab Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 6 Dec 2023 09:30:49 +0100 Subject: [PATCH 13/53] Don't multiple svgs tag `createIcon` add the svg tag by default. Let's remove the unnecessary extra tag. --- dapp/src/static/icons/Bitcoin.tsx | 54 ++++++++---------- dapp/src/static/icons/ChevronRight.tsx | 21 ++----- dapp/src/static/icons/Ethereum.tsx | 76 ++++++++++++-------------- dapp/src/static/icons/Info.tsx | 34 +++--------- 4 files changed, 70 insertions(+), 115 deletions(-) diff --git a/dapp/src/static/icons/Bitcoin.tsx b/dapp/src/static/icons/Bitcoin.tsx index 0bcd4ea51..c25918520 100644 --- a/dapp/src/static/icons/Bitcoin.tsx +++ b/dapp/src/static/icons/Bitcoin.tsx @@ -4,36 +4,28 @@ import { createIcon } from "@chakra-ui/react" export const Bitcoin = createIcon({ displayName: "Bitcoin", viewBox: "0 0 28 28", - path: ( - - - - - - + path: [ + + + + - - - - - - - - - - ), + , + + + + + + + + , + ], }) diff --git a/dapp/src/static/icons/ChevronRight.tsx b/dapp/src/static/icons/ChevronRight.tsx index 7013170cf..8a11116cb 100644 --- a/dapp/src/static/icons/ChevronRight.tsx +++ b/dapp/src/static/icons/ChevronRight.tsx @@ -3,22 +3,11 @@ import { createIcon } from "@chakra-ui/react" export const ChevronRight = createIcon({ displayName: "ChevronRight", - viewBox: "0 0 16 16", + viewBox: "0 0 20 20", path: ( - - - + ), }) diff --git a/dapp/src/static/icons/Ethereum.tsx b/dapp/src/static/icons/Ethereum.tsx index 5fd1e69f0..b3c5c5633 100644 --- a/dapp/src/static/icons/Ethereum.tsx +++ b/dapp/src/static/icons/Ethereum.tsx @@ -4,46 +4,38 @@ import { createIcon } from "@chakra-ui/react" export const Ethereum = createIcon({ displayName: "Ethereum", viewBox: "0 0 28 28", - path: ( - - - - - - - - - - - - - - - - - ), + path: [ + + + + + + + + + , + + + + + , + ], }) diff --git a/dapp/src/static/icons/Info.tsx b/dapp/src/static/icons/Info.tsx index bef23adae..364e278d7 100644 --- a/dapp/src/static/icons/Info.tsx +++ b/dapp/src/static/icons/Info.tsx @@ -3,30 +3,12 @@ import { createIcon } from "@chakra-ui/react" export const Info = createIcon({ displayName: "Info", - viewBox: "0 0 16 16", - path: ( - - - - - - - - - - - - ), + viewBox: "0 0 20 20", + path: [ + , + , + ], }) From 832da1e33689daa9beb06ec4655f75e981a5aadf Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 6 Dec 2023 09:52:03 +0100 Subject: [PATCH 14/53] Add types for chakra ui component styles --- dapp/src/theme/Button.ts | 3 ++- dapp/src/theme/Switch.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dapp/src/theme/Button.ts b/dapp/src/theme/Button.ts index 51a7cbf7a..0a3947861 100644 --- a/dapp/src/theme/Button.ts +++ b/dapp/src/theme/Button.ts @@ -1,7 +1,8 @@ import { mode } from "@chakra-ui/theme-tools" import type { StyleFunctionProps } from "@chakra-ui/styled-system" +import { ComponentSingleStyleConfig } from "@chakra-ui/react" -const Button = { +const Button: ComponentSingleStyleConfig = { baseStyle: { rounded: "none", }, diff --git a/dapp/src/theme/Switch.ts b/dapp/src/theme/Switch.ts index 1074491b4..1529dda02 100644 --- a/dapp/src/theme/Switch.ts +++ b/dapp/src/theme/Switch.ts @@ -1,4 +1,6 @@ -const Switch = { +import { ComponentSingleStyleConfig } from "@chakra-ui/react" + +const Switch: ComponentSingleStyleConfig = { baseStyle: { track: { _checked: { From a3ba20f8ac2a744f8fae34c1b7f95dad463f3e7e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Dec 2023 14:23:46 +0100 Subject: [PATCH 15/53] 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 16/53] 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 e0e49feb6729436af99ceb01a6104dcb65c24400 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 7 Dec 2023 11:09:00 +0100 Subject: [PATCH 17/53] Define basic color palette --- dapp/src/components/Navbar/ConnectWallet.tsx | 4 +- dapp/src/theme/Button.ts | 8 ++- dapp/src/theme/index.ts | 5 +- dapp/src/theme/utils/colors.ts | 62 ++++++++++++++++---- 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/dapp/src/components/Navbar/ConnectWallet.tsx b/dapp/src/components/Navbar/ConnectWallet.tsx index 9e761ea9c..701f1f9d4 100644 --- a/dapp/src/components/Navbar/ConnectWallet.tsx +++ b/dapp/src/components/Navbar/ConnectWallet.tsx @@ -25,7 +25,9 @@ function ConnectButton({ account, requestAccount, }: ConnectButtonsProps) { - const styles = !account ? { color: "error", borderColor: "error" } : undefined + const styles = !account + ? { color: "red.400", borderColor: "red.400" } + : undefined return ( - - + + ) } diff --git a/dapp/src/components/Overview/Statistics.tsx b/dapp/src/components/Overview/Statistics.tsx index b0c2dc0d6..bad05e4da 100644 --- a/dapp/src/components/Overview/Statistics.tsx +++ b/dapp/src/components/Overview/Statistics.tsx @@ -1,10 +1,13 @@ import React from "react" -import { Text, Box } from "@chakra-ui/react" +import { CardBody, Card } from "@chakra-ui/react" +import { TextMd } from "../Typography" export default function Statistics() { return ( - - Pool stats - + + + Pool stats + + ) } diff --git a/dapp/src/components/Overview/TransactionHistory.tsx b/dapp/src/components/Overview/TransactionHistory.tsx index 312233938..b8c0da7fd 100644 --- a/dapp/src/components/Overview/TransactionHistory.tsx +++ b/dapp/src/components/Overview/TransactionHistory.tsx @@ -1,10 +1,13 @@ import React from "react" -import { Text, Box } from "@chakra-ui/react" +import { CardBody, Card } from "@chakra-ui/react" +import { TextMd } from "../Typography" export default function TransactionHistory() { return ( - - Transaction history - + + + Transaction history + + ) } From 1ff9cfb03b43fc49d9ecad2b38b2958367b038ea Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 09:27:31 +0100 Subject: [PATCH 28/53] Use a `templateAreas` for grid --- dapp/src/components/Overview/index.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dapp/src/components/Overview/index.tsx b/dapp/src/components/Overview/index.tsx index 38f7d0068..6a4810a84 100644 --- a/dapp/src/components/Overview/index.tsx +++ b/dapp/src/components/Overview/index.tsx @@ -26,18 +26,20 @@ export default function Overview() { - + - + - + From be3d4a5d2829d25ac5dae844ec08d32d623ff072 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 09:28:54 +0100 Subject: [PATCH 29/53] Move the tooltip on the position details card --- dapp/src/components/Overview/PositionDetails.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index e82d49620..8fb074fe3 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -8,6 +8,7 @@ import { CardBody, Card, CardFooter, + HStack, } from "@chakra-ui/react" import { BITCOIN, USD } from "../../constants" import { Info } from "../../static/icons" @@ -16,16 +17,18 @@ export default function PositionDetails() { return ( - Your positions + + Your positions + {/* TODO: Add correct text for tooltip */} + + + + 34.75 {BITCOIN.symbol} 1.245.148,1 {USD.symbol} - {/* TODO: Add correct text for tooltip */} - - - From b10ca854028bbf4a9fee434748bb35f725a452a0 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 10:42:17 +0100 Subject: [PATCH 30/53] Set the page header for dApp --- dapp/public/acre.svg | 4 ++-- dapp/src/components/Header/ConnectWallet.tsx | 2 +- dapp/src/components/Header/index.tsx | 12 ++++++++---- dapp/src/static/icons/AcreLogo.tsx | 13 +++++++++++++ dapp/src/static/icons/index.ts | 1 + 5 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 dapp/src/static/icons/AcreLogo.tsx diff --git a/dapp/public/acre.svg b/dapp/public/acre.svg index 59e8dbf1c..557d8a50e 100644 --- a/dapp/public/acre.svg +++ b/dapp/public/acre.svg @@ -1,4 +1,4 @@ - - + + diff --git a/dapp/src/components/Header/ConnectWallet.tsx b/dapp/src/components/Header/ConnectWallet.tsx index 8b35a010b..380645281 100644 --- a/dapp/src/components/Header/ConnectWallet.tsx +++ b/dapp/src/components/Header/ConnectWallet.tsx @@ -60,7 +60,7 @@ export default function ConnectWallet() { return ( - + Balance {!btcAccount || btcAccount?.balance.isZero() diff --git a/dapp/src/components/Header/index.tsx b/dapp/src/components/Header/index.tsx index 838ed37b2..56e427097 100644 --- a/dapp/src/components/Header/index.tsx +++ b/dapp/src/components/Header/index.tsx @@ -1,11 +1,15 @@ import React from "react" -import { Flex } from "@chakra-ui/react" +import { Box, Flex, Icon } from "@chakra-ui/react" import ConnectWallet from "./ConnectWallet" +import { AcreLogo } from "../../static/icons" export default function Header() { return ( - - - + + + + + + ) } diff --git a/dapp/src/static/icons/AcreLogo.tsx b/dapp/src/static/icons/AcreLogo.tsx new file mode 100644 index 000000000..94059f58a --- /dev/null +++ b/dapp/src/static/icons/AcreLogo.tsx @@ -0,0 +1,13 @@ +import React from "react" +import { createIcon } from "@chakra-ui/react" + +export const AcreLogo = createIcon({ + displayName: "AcreLogo", + viewBox: "0 0 120 71", + path: ( + + ), +}) diff --git a/dapp/src/static/icons/index.ts b/dapp/src/static/icons/index.ts index 66c6e3aa9..c625a102e 100644 --- a/dapp/src/static/icons/index.ts +++ b/dapp/src/static/icons/index.ts @@ -2,3 +2,4 @@ export * from "./Info" export * from "./Bitcoin" export * from "./Ethereum" export * from "./ChevronRight" +export * from "./AcreLogo" From da2d27f5b933f2cb0604e8199f828058638dbe35 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 10:58:34 +0100 Subject: [PATCH 31/53] Use a `CardProps` for grid items By `CardProps` we can avoid unnecessary wrappers in grid and control content from the parent component. --- dapp/src/components/Overview/PositionDetails.tsx | 6 ++++-- dapp/src/components/Overview/Statistics.tsx | 7 ++++--- dapp/src/components/Overview/TransactionHistory.tsx | 7 ++++--- dapp/src/components/Overview/index.tsx | 13 +++---------- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index 8fb074fe3..80ca4b7bc 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-props-no-spreading */ import React from "react" import { Text, @@ -9,13 +10,14 @@ import { Card, CardFooter, HStack, + CardProps, } from "@chakra-ui/react" import { BITCOIN, USD } from "../../constants" import { Info } from "../../static/icons" -export default function PositionDetails() { +export default function PositionDetails(props: CardProps) { return ( - + Your positions diff --git a/dapp/src/components/Overview/Statistics.tsx b/dapp/src/components/Overview/Statistics.tsx index bad05e4da..a1efc1199 100644 --- a/dapp/src/components/Overview/Statistics.tsx +++ b/dapp/src/components/Overview/Statistics.tsx @@ -1,10 +1,11 @@ +/* eslint-disable react/jsx-props-no-spreading */ import React from "react" -import { CardBody, Card } from "@chakra-ui/react" +import { CardBody, Card, CardProps } from "@chakra-ui/react" import { TextMd } from "../Typography" -export default function Statistics() { +export default function Statistics(props: CardProps) { return ( - + Pool stats diff --git a/dapp/src/components/Overview/TransactionHistory.tsx b/dapp/src/components/Overview/TransactionHistory.tsx index b8c0da7fd..9dabb919c 100644 --- a/dapp/src/components/Overview/TransactionHistory.tsx +++ b/dapp/src/components/Overview/TransactionHistory.tsx @@ -1,10 +1,11 @@ +/* eslint-disable react/jsx-props-no-spreading */ import React from "react" -import { CardBody, Card } from "@chakra-ui/react" +import { CardBody, Card, CardProps } from "@chakra-ui/react" import { TextMd } from "../Typography" -export default function TransactionHistory() { +export default function TransactionHistory(props: CardProps) { return ( - + Transaction history diff --git a/dapp/src/components/Overview/index.tsx b/dapp/src/components/Overview/index.tsx index 6a4810a84..d5fde912e 100644 --- a/dapp/src/components/Overview/index.tsx +++ b/dapp/src/components/Overview/index.tsx @@ -3,7 +3,6 @@ import { Button, Flex, Grid, - GridItem, Icon, Switch, useColorModeValue, @@ -33,15 +32,9 @@ export default function Overview() { h="80vh" gap={4} > - - - - - - - - - + + + ) From 649d0ba76293ad1dab5c81e0ccd4b7769009b90d Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 11:12:48 +0100 Subject: [PATCH 32/53] Disable eslint rule - allow use of spread operator --- dapp/.eslintrc | 5 ++++- dapp/src/components/Overview/PositionDetails.tsx | 1 - dapp/src/components/Overview/Statistics.tsx | 1 - dapp/src/components/Overview/TransactionHistory.tsx | 1 - dapp/src/components/Typography/index.tsx | 1 - 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/dapp/.eslintrc b/dapp/.eslintrc index 441891d36..b654e6b24 100644 --- a/dapp/.eslintrc +++ b/dapp/.eslintrc @@ -3,6 +3,9 @@ "extends": ["@thesis-co"], "rules": { "import/no-extraneous-dependencies": "off", - "import/prefer-default-export": "off" + "import/prefer-default-export": "off", + // Regarding the fact that we are using Chakra UI lib let's disable this rule. + // This will allow us to easily pass props from the parent component. + "react/jsx-props-no-spreading": "off" } } diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index 80ca4b7bc..e07b4a1c1 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import React from "react" import { Text, diff --git a/dapp/src/components/Overview/Statistics.tsx b/dapp/src/components/Overview/Statistics.tsx index a1efc1199..221f22f60 100644 --- a/dapp/src/components/Overview/Statistics.tsx +++ b/dapp/src/components/Overview/Statistics.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import React from "react" import { CardBody, Card, CardProps } from "@chakra-ui/react" import { TextMd } from "../Typography" diff --git a/dapp/src/components/Overview/TransactionHistory.tsx b/dapp/src/components/Overview/TransactionHistory.tsx index 9dabb919c..7b03b3b9f 100644 --- a/dapp/src/components/Overview/TransactionHistory.tsx +++ b/dapp/src/components/Overview/TransactionHistory.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import React from "react" import { CardBody, Card, CardProps } from "@chakra-ui/react" import { TextMd } from "../Typography" diff --git a/dapp/src/components/Typography/index.tsx b/dapp/src/components/Typography/index.tsx index 6e1231239..1ffbd9ff1 100644 --- a/dapp/src/components/Typography/index.tsx +++ b/dapp/src/components/Typography/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import React from "react" import { Text, TextProps } from "@chakra-ui/react" From a22e660faee59b31426f12874c75b97474e08b67 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 11:46:09 +0100 Subject: [PATCH 33/53] Add a `Segment` font --- dapp/src/DApp.tsx | 2 + dapp/src/components/GlobalStyles/index.tsx | 47 ++++++++++++++++++ dapp/src/components/Overview/Statistics.tsx | 2 +- .../Overview/TransactionHistory.tsx | 2 +- .../{ => shared}/Typography/index.tsx | 0 dapp/src/fonts/Segment-Black.otf | Bin 0 -> 32381 bytes dapp/src/fonts/Segment-Bold.otf | Bin 0 -> 33069 bytes dapp/src/fonts/Segment-Medium.otf | Bin 0 -> 32825 bytes dapp/src/fonts/Segment-Regular.otf | Bin 0 -> 32521 bytes dapp/src/fonts/Segment-SemiBold.otf | Bin 0 -> 33161 bytes dapp/src/theme/index.ts | 3 +- dapp/src/theme/utils/fonts.ts | 6 +++ 12 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 dapp/src/components/GlobalStyles/index.tsx rename dapp/src/components/{ => shared}/Typography/index.tsx (100%) create mode 100644 dapp/src/fonts/Segment-Black.otf create mode 100644 dapp/src/fonts/Segment-Bold.otf create mode 100644 dapp/src/fonts/Segment-Medium.otf create mode 100644 dapp/src/fonts/Segment-Regular.otf create mode 100644 dapp/src/fonts/Segment-SemiBold.otf diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index f38c6379e..ec668e5e8 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -5,6 +5,7 @@ import theme from "./theme" import { LedgerWalletAPIProvider, WalletContextProvider } from "./contexts" import Header from "./components/Header" import Overview from "./components/Overview" +import GlobalStyles from "./components/GlobalStyles" function DApp() { useDetectThemeMode() @@ -24,6 +25,7 @@ function DAppProviders() { + diff --git a/dapp/src/components/GlobalStyles/index.tsx b/dapp/src/components/GlobalStyles/index.tsx new file mode 100644 index 000000000..355c6555d --- /dev/null +++ b/dapp/src/components/GlobalStyles/index.tsx @@ -0,0 +1,47 @@ +import React from "react" +import { Global } from "@emotion/react" + +import SegmentRegular from "../../fonts/Segment-Regular.otf" +import SegmentMedium from "../../fonts/Segment-Medium.otf" +import SegmentSemiBold from "../../fonts/Segment-SemiBold.otf" +import SegmentBold from "../../fonts/Segment-Bold.otf" +import SegmentBlack from "../../fonts/Segment-Black.otf" + +export default function GlobalStyles() { + return ( + + ) +} diff --git a/dapp/src/components/Overview/Statistics.tsx b/dapp/src/components/Overview/Statistics.tsx index 221f22f60..8b9c5979e 100644 --- a/dapp/src/components/Overview/Statistics.tsx +++ b/dapp/src/components/Overview/Statistics.tsx @@ -1,6 +1,6 @@ import React from "react" import { CardBody, Card, CardProps } from "@chakra-ui/react" -import { TextMd } from "../Typography" +import { TextMd } from "../shared/Typography" export default function Statistics(props: CardProps) { return ( diff --git a/dapp/src/components/Overview/TransactionHistory.tsx b/dapp/src/components/Overview/TransactionHistory.tsx index 7b03b3b9f..e0ff1fb26 100644 --- a/dapp/src/components/Overview/TransactionHistory.tsx +++ b/dapp/src/components/Overview/TransactionHistory.tsx @@ -1,6 +1,6 @@ import React from "react" import { CardBody, Card, CardProps } from "@chakra-ui/react" -import { TextMd } from "../Typography" +import { TextMd } from "../shared/Typography" export default function TransactionHistory(props: CardProps) { return ( diff --git a/dapp/src/components/Typography/index.tsx b/dapp/src/components/shared/Typography/index.tsx similarity index 100% rename from dapp/src/components/Typography/index.tsx rename to dapp/src/components/shared/Typography/index.tsx diff --git a/dapp/src/fonts/Segment-Black.otf b/dapp/src/fonts/Segment-Black.otf new file mode 100644 index 0000000000000000000000000000000000000000..11d290a0017ec9817f94046c0d5a445e66b58ef8 GIT binary patch literal 32381 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 oV@EMaAKr%gfdBvi literal 0 HcmV?d00001 diff --git a/dapp/src/fonts/Segment-Regular.otf b/dapp/src/fonts/Segment-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..124d0b60ec3ebb5d1c20d42199b56edf2a919225 GIT binary patch literal 32521 zcmdSBcU%<7_9$LGGu?wdIx1tMGWHA^BncQ10|;UO1ui1an*?>bkpnS6$bf zv##svnh*mbhoK*VAl`>pR_p8}7aD`+M*A`Tg@_sP3xjs_N>hbE;0PsDAyT zNIf!_s7Z8ac(_B|Zi~|i(M1!|?P^%x9^D8b3?Z{0;N7NMSl4ik4J#1R|1_psb&HPb z8}7i;({PFIu>P7sS?pP)T;~hv3 z@)K!Ko_@?#HCEz1)X7*D^HpP&@;&4_Od{#QR1+VjANHpc>8jG;drbHa9j3LUG4qDF zt0PEPMvvt(NNcr<1gX+VsG1{QI{D}7kWVmww9cTL`j^@P;!MBNg;7mbs>E*3};>uFJ>_5%Phb!2t!eAIDpUBkWhvv-BkB5M3UjE zzN8})g=062bX0vtLU26gc$g}VbduZ0@#%^oh;hI%&c^Q@fO9+u>qX;xnqqs)NQ_Fv zIl%E#2M|%+1LsN}D|ejxL8P;4D{09b#rsCA8&5jR->d46?~up$5E-Z1M4}KTS~KfO zYgG*PX(~Qjk7dUZA5}f#&Qy{p>|b-njWoya^ict6rFOkMkT51N8w(9o? z0}|#B#yOpi^)`}W@;Iy7K7r@VhgP_uoT19Pg(Hn{vNpybmVf7`kGauBuR+llsJrKlx8yoiJd}aE-*jW|+bl zra2WaXJEqmL{9wspX%Q;{%cxI0ud*JNC(o9WRgyZ@4+O5bV0lfBV9>2nM9IEB1tAG zWHL!5X(XLYA%nmKaDQA`m;$ zm^2|xi9OjfqYBWkOgEhSxWko&&f%$g0v%>$!M~W93%V559BD> zN2ZbK5Cg!AF_=cVGK+oGL@_%0}x6b87H!ltjD#sAFc>5n8xHe(}WSo8(c$w#kKN1 z!99rlhAXB8ccfD!j%*;S$v85Dj3K+oVKSD)U#fCdxvLSD_mK?pFN9x$(7Ya@YxX1F2#g)mm}!Er zW`()4h)2IEa3>)??M3+iU4gz2<^#4ruwn2cdIh+~LhNpZ>PJ~46QrXJDhQr&-7xThawvLF}oIE0#U3Ki;_3w8OP9 z4A+?$T;oP#FOzWPn~7t;636&k9Mi)%8ka~u?gmeZgkRs7abtX$Af_u5&5U4@m`r9a zvxHg2tY@|{`(#S_DxIk3y77k{=JmzKqY6I5?AVmC{48N5}Gz8 zDJeERVN!BR>ZGw##-w+hk~&E)(K4`AYV6pAF|mmW$?0+9QezX9+>pSO*wpld*u=33 z@$re1rpG0Zi%Uq3Pe@KkpB9^fZA?{qkUS|}xy#-29TS^|4Jh4H;>r^xHhx;l__$;x zIyyCW%*43##JKo$YZ{YjiLq(p|Nb;JVchuI$5=e>uflTbZx3Trm0C$t64Mh>5~o=c z3CUBeeHojOkuWw+$;6L|Oa8MNCG}S`f4zQsT^yDc?lak|nv<3=Rmn(;%fN4xYsV?wmt)BZwGE{tPDxApvuAP1V?TXyT+*M>Pvfif z_-})(q(6d0gV3Up($p&R<_GXC=lZP5Nsp{>=HSkQIi) z2E?VNA?P^x`yx!%VwgiMhSerXvWz)e+$(HxkGIEN+!c50*0s1Z1TiNOsfMX!98*iLLub|5>NO<*UnGuXxK3U)2KjXlVoWG}MU+52oI`wROkOF0c! zkF(>BD+NxM0gjoBE^%^dqE2gZ(@9GO-R6v%Xw0jLW_6XDGoWpWG$K=H z?y!#SquaY<``*2iwcC)OSY5{NpJTJ^HI8b*k=0eHtL>_EIV*D*osftXnu|`N2?p5x8Q1p};RPHJ z!0#=!qh8e0g*K*s)XxPPgC}^s1v~IF>!6<91!&8{CGG-k%l^XAFWp2>ynev$UyqM8 zXJ{Q}iZ4vMdBRU;_UC>VCQ{bLFVaUR^=fVDrENZ-1+7c#4*dCrN$RDeYU*i%HPBlC zMwg;(*FD5PX26yl-SVswm{ka~N_2BYsl6^I@9EQic{+{MKG)Ko(|J%E>P9`@P_=I9 z<_xKO0gj-w6hGBPq-ENGdpft53s9L01g7iy2)ea}UR| z^6OiBO#m zmkRAMS$24Bk;(96_1a}wW-ObzVh*j-Wh@m<*i8HQ_)aE0bGj7DD%6lOPx!Sg`{{8H zaZ+?rq|rAd|K~+!sRg(Ei>xocum=x6s7Jl2XRrufCfW%u2tC9H#*+KJ+kIiiZst@; z>syN$$u5VDEp4K;V@J}xHS z3^`ipL=OvZZ-ye6aNR>)DTyq5_uI8nAZC3gbE_P-l&f!riolj#I&)%&sZF`GG%!)r z3Dkuqvy~k9Kr+R^w&f5?05d;5@WlrhSbbYD06Yay!{UY8R+xfB6IQmT_LusAz5K)2 zC91^0z7NLaAIAEy`YLq}ycOtRY_%;6$Da$dg~2eG;++o1ALi4+a&sfk(a|MtrV1|N zWfTm71bY~t3xlt}GPUD6xDKSTbc8*A46LtG55lfjYw2ZhfXfK)Rn8*q%Z6&drVj+k zt<>ViVY#g?A`G`=c+v&zLk{*QiMow7(E=>C1hI5a73|T%VkszH)ZNvd`*!o8FHNg< z>|MCrSfE{+u`I(XTe{jL>WnyrF?UQ0CFE;d@`$YHd(u1ZaZtS^R zY!4>NyrClgh&OdG>7cP7qv*aJ+xH2A&TtDH%4o(P|Kjv5 z&IB4;2K4i4{xrQj!+c}Lr5S_l`{^|CqP|ozmq^A^Tb&RH8NdIFX0+GJ^*;Q{=>@4< zxuqK?b%Xi>Mgq0^4vszd4?Le}GQ91QFsf$|e3}iZt*Z>L3v``D>d;x#DUg7(l8iGXLxN7Y#yxuU$lqVoZH~EU zJo&?s>&wi8xWrjAbo6VjOYac><)W@8j&03R7mFSBm+V;G=01NFOqzw@TwSGx5WAY_ z)*qs^y72hd&c?3YhEdfDvmViTYdmRiBXk$=!f`&Q) z*Pke@jy{k^WxzUIacClVXGk3}CN*Lzq(*8;(w&4BvAhL7rP?c10o!209ahEE4jgi&0cekPX1ymBAK9p(L0frH?E(?#9S=BQ;9| z7P$l5ppj$5jTJdlWZaqMEVACnJt8@a)VItmY$izJB7cXh0@ByW<}+(l$U2a@glr+m zHy}%aTrY0mxSKO;37JDk3WL-;G8N1^2AN;vdU2aa8XZ{@Rw}NCe($dJfkS}D`8hI6DSdcy-xrCGxSy+37Fq_gX zMw*)(S0UGp0!wm=K?Z`HA><+<=T%6Ilk0?BVaQEFt`TyFkbD(E4)CrDrHiDLkRnDF ze3uYX&5+9kSrKwnMUW+&PLNGUDJQ8y##2R-7{*y9YFeV8ArQqPU9fL&nE)n~8O5Ao zs+gy$rmF6$;i?s?gQ^p%_v+E={hEdvZ%r#req#4qOD6 z!X4)xaiHy{P1f$vKDP<58Dg{0=DN*;I;uLY>crJauCt2w;A8k{{9D}=U6~H*vUMBP z?OS(R-Boor*F9PHLfxBnOX@zX`>yUE^|*Qs>Y3|#)$35NcfASqmexB^FRxx@y;pjp zK0@C|zrTLJ`ib?Y)?ZiuRQBou5(?Fxixpoay#gD+pW;;XSX+QAKW?j2JTJWUEO`%JG%FFALc&MJ=b6+$-0s*t@OwQ15u}4DT%OQ{MUBuYGKNTKO#W$?66Aa)BzR&U2=H;Eien-s@*0a!fDAnAjW+NR#o6?rJ^ACfj zv=xjo>%lFnL^@i6yM^Q_)2=X`gAw2c&5>M)qarxcab`N5tLh+t2M2y{oM{j?pr#t? zZl)fbe#Ivu5?g5Wz=zi-B5|Ev2urca01>j`ViCOvOQ5vG;*Y%_J+A~N7sEFtGTF7H zaz0OE!GpHImqkNU{Ehq~Ys0clCVI64WN@^x3$E7G-xYWW0RLAYK)}PhV?s}w9&KQO z?df`j3Pu`7o6$CO2!4tSzWPED+>`e{sSdvVf>eiIsKPCf-nZ0&`>i=W&6D|nNuoY; zUP%pZl2YR;^==p>P<~ByOK5BayQ*q1nx&boEi<)k2S>N-&@cPpXC}Q;Z6S{vD$Lgw zSHoAdm<1I_b0q=3gM8#eIC_;nW#Os3-RcKqmDDhD@e-ki!5qoIM5~95t|E49J`S@Z z{~`K;bVds)mNV=H4kk%wSj!pgsiT%=TJmV7l*i)tVEJlE8z4#-I7w@{z*gg@ST1lD z8|eb>{dxrwWa3r-9e(+jC2&lhN4TME{O~1o(SqT{Q)mQ+=Fh0XOxJ#h=BjEC)PlLP z%e?{vqa)j!X)w;p)Y_I1tX$>^_it=Jdck}xA~(19754nK0~Jr~VJLNj)^sTT+^BK~ zyu(maji3SV_p}l93D4{~W~iAOzz`J;4QK;4W$cI<1C7+j5j)@mKEKH~v;G?PbWI5y zQF?mC+S4`ASqtuT4f~SAM)$C#2QL-=Vh;wF$Fz}&uK3jJHeA__n83iE5!mZCoPOWD zBI#NYlV1hbWJ0&QK3kCrXf3`E?2kXf!v~Gu&6&nrx$=<0ayx{)8+8E9Q(Xl zppGvn2d;VdwqH7I>a2C_)5obt#MZn_Q`?!WLO@L*<5J7MCsw0(QGZQHjmZ3a)D z(7246$1ePJYpEFOD+Ft^HZEHOu0Py^#wKZ7pk;^FX+o0AY7=y=SuYqK>{IG)o0yoI zHgV$iw7q8iSbS5T?l@(Agb#hnAy8Xb1l{N&_60{L!5J6{qgcvtbSj<3N+Gp98{1t( zx*SJd?kga~A%`#1>&wewX}S8I9rCSQ<&PCV-96Sn*hF(Nsjd8>;^7@@vJ9?jK^^&+ zYP$4@3r#THm=b-Wi?LVq#9kB3m9Bh*|7-<4y29KC*dJt$?s@1pP znbVc&3vB^;1QJPiR3? z1h}A$Ih^;Ota-O_G2_e?fgj?uunLX{$dZ>@S@Jh|VTkf`1 z3*|L&2m}|$bweZk zW-gs+G(712+3p-O9G3Bb0Y`1nES4JDYxP<43Yc2~yexXqlwXuZ53PB9AFB*!J2L3~ zEKAuk4*E+=Wj4~?;zr#iH(adJyNJ-+?g0mlq^8iw(v-z$ODxomqjfFKX%neA8^X~h z7}a64EvFxZ6&l&0Bw`5C6hq`SyeA5qy1)|FgTtk@H?@V{7=PXRaAOkawt;Y zvdT0(P^K{okb0<<(-oV;Fm;n?`W8_f6CvK6V}mwk=Yrh^aF z;;dhs3${`@%DAAR`jI35xn&Vs&V4Q|VjVshg>kzk9XPma*C9{dq^HBA1X#%Al)ZYO zhWS#0P{yr*g=`y+M%b;O3vmkMq#g!s%7EZ<@GXbVnapMAEPx|7lLn)N=o<)TamSv4 zi3i+zIpNH$Drcz!w-$Wm#21)w#3!9`HfGSyENq6`4$F6{w2mCy<)SjMoIDUQK%JogspM_j*`Hju>PQA5G^ICMHT6?N{8TTT|MAvA0ts8iJ&P^L! zdsISx`uXG@_TkZEBj=ksNtXmVSsS@!=&oG*+%w0ntTE|f*f75AKKR{N!$8SSc-Beu z^6J~&+xtTIXJ)C9#X)%9N%ZvUhdDWzW0|=|D4!{PSu-+lJv+scES%mGKX71D{E(qZ zhtHkcbNH0mG6r%5jGaEc8=oY{4;g|_OnOTrX?6xIL^bSnncqkQ3rbi zUpbRHvv;_e;0%1*gI8#iTvmVnQ3gbpLm%9)wmnckfuB*X)Qv`0BEj3OoOXjgZD}8^ z-cpA;D8i1sSHc*&4uvpRVI8ZddRSA=AR^*7(-K$S!Yu(?<}d=?;ap+770eDQU^a+0 zoW)a3B2QCkYAjxdi*y)mxLB;oS}Lx^mM}Yxrw*~A12t7J(pYK|DlD<>a64#%Pegdo zQ_K*tXDQQpIcJqx ziV$)!?#GSAe0>I=jTw))W0=(yg+6qh@R&QQB=8x`#)r3+@ATu4tXwBt#z+3vs&okT z%z(8xXd`H6rPNw%l_t`)n79mUv78>)nXjQI^A>um-{P7u`1{dk&whXG%$e9@g9gQp z9&FOj=3#=gQlK4aLOc%%EM}a=Gzx4JQbjpUtCvDU*aHojOE{#La)kuzkRHGxy$VkR zL}DCWd7KgD3#ns}#z0W5uB(yoN@lO`pY^q=7tHCl|o z7f4?e+oFK2p=0H%N<$IUmktY8*Pq*yYs|ft7>?UfuZ7F0m$^@ccKK!YSBU%?yzGH> zyXDxtWsFCL31Teg1;djAyLKP=)Of+y%L zqU+3X)$YaByN{n-?dtB{tE;bnaT^% z)QQ!1kh)~R`Ap`xVG)lU84zy>PiX2eq zs4Hp$g`Vd|<>sFL;rjI-drY1~COHZ7bwsk+Q=F@4yU8bT+ zT|w&!(LYSc+qUcTHQ$)#O ziaQ9bKe-|jE|vn>KrRC{305FSa7yRFQ(jN7%&*FSqZDa}bzS)c%Ly_p)1XM9G7RyG%K| z4_~-p4_c4gRAn+er)qB`!6(y49e z`A25`?Rh2A^}k4WXSt+f_olha~~h@U~i7VHwVL(7r4YUW3*v@~;kLz_$<|Jkf5CWwMHt-gs4>gd#`P16xQ zlR`V$S1b~KF3fxQ_*OSR|FDH(gv_uW@5dkU6>nActALfVRQxGiMtPCx#e*~~wmz8c^mL>_* z_?rtf*>Uj8bz4j{ims`iak;d3sEFbnf8k+4j}|RDcMtFj&wu>*P9BauJt~Dwm3Ovq=CU`NIEnA*?SrQMMVGXzP!g_A!f59$*+d}zAaP&RS@sN<*Y&-}RW zq)ER`=C(7LmC_^iO1t}Wq;G151%J)zcN{2Vqy-QSvYEtXVKJUuDp0moE;7S|cA ztfJbt*Pl#er{rzXc64c;l@1Xpn*3LN4sov4BClr=a7ZX(j#Zm6;GMU9RAY!ELiv#ngWyC1Db z9jF=AJK%oz?!GHWk?Tf1WIOGZF=6{c6{ef2`yeBKsSHVD=1QQmbMHWkycR5R6?fZJ zbLG+vh_u{b&vVdAx>1I)YK}6>D}yb^X|Qx0NqjwQQY2gpWI^Iv4yH>6m|-!nZ)LI7 zEsjo?>agSSW~swUw_0nF5tv?FgNk}cT`6=+O=;D}n33`Mmzm}c+J)P;FWznh=DX^L z2hA{1Iw$ie9u`OHBRR^FFS<{^V`PRbz`Irs&2FmUfb@kxo6cp#~8Wr5$qdC5#HY@#;!3pIy%f2P5SS$N?~*N0f zh9}qHs8GQz&sd%@#lG+S)}2j3(wtKITKoCdBY6jw?q7Pq3|-4;56vGF71POufMOwlfNNx+7cFEOzDSm9gedoIXQ*A~_3Hl51BN zXl|c>_dxFblcTy?<#npbt!_|_krKy9s#|7W7QJ)yu}L9QapkZ?o3ZQQ?C*>U!)!)|8Ccq?sAfPB^R`AUz%=@bPD2>zBrPqncxhoOP&yS} zv65GjWhtkBPR$uL+lrWyN_c^@+J?3gYPP}458EnhUR&0Dc&*j1faxcP!xGvEmX1Dl z@1$+^so#DmKk@vO;n3V{X##3MUn38X+WXhQpa8Rp8xUEW??wr2Vmf8_uIM33NK{1u zPEMTyaO_o`9=SM_fYBsm~uvx3WggSxtg~f%? zzJz&?h$b}^E^?EzCQr7H>3ox;!q$9h1AazO7hHaQ)ZBJG8<{dwxoKd8CDe1 zHQbJ@9Xsq{O(9*uRj>cPG`A)Bl`RP{SgV zZ6}@JXqubo%F#L>SRo5KNPDwP(802o)gMD5i@ndBkXYdr2Sy9aRv>oQM0Z+8gHf(q z7s^=Lg3}LymXO-H&{lFTH;g(8mjtkIhRxKi98xX4wR-Own*ye!#zuhq1@t}+{Q@cB zD)3jCV>hABP4zLUS_nTs@Xqb?=WgBVf39m-|3O_%H|_TC*s*`#j>!`yPEMIPVe-y> zNOta*+GZD=y_{`(4914tF}yw|Efao%!+|qx%XN`qpT8vT@y<88c?h%rH+Jw4dAl?X--kGp46b$ym43jHJ0VAOjL}Y)`zG zH|P^JO_3x!K@+dSHl)ZLa6#{tl?S~pL)jJEbHI1Yhue_aE#L5Nnh5qEh6#C8r@eNb z-MMY|zVGdG1|N=!95r_6V$=0G?7q);e6_)7c)MZSg2XvXW~5Fr8{Sb{s$vIdE!}Di zg5miY`dL1l=P;Yc_e?u~8FpW)xN!3lb2hJ{SW+Qaj+i4@?FlUFKMZ3JBUt49cqC{4 z^xfmuo42gqy!C+nnc)Zf^cgXJu<80-cKeEN*KRUyUN>v{^jXu=&ExtW=5}t(OwX7; zEhRN={T_4oIc)zQWI{iuLpMUerFAF!Gv!2KepMV2M5|<#^ zTmml!@MuRBm+^Z@CXRe+AX;=hIA#^me!C0>vc_e(*TB(U+M})9csTbf6wYl}xoO$9 zr8WiHtZB>Vo2YSlR4?jeZ`q@5n;9Ryz+`wZeQ8Ek`U)F8)k?ctB3tlXUw%f0Y0Q+1 zFs%jiqJ~cJ zWf9kEx6gg(Y!6<);8N)i0l%OfofqvK7~-C6>IB~wQIqz@!Fx~#>f6I0+7y@L5vcfV zin_u1&^tX}Xu*#M^!dG`*j=(@T5G_K7^OvQqwdf@U^!I7Hgyc%WT23CxYu>I{nOGm~IPW@(r8cqD=g=OtUxTIBnCyHA%~xkjTZGmJ z+@T)m54@_XN_a{2v|fToYZJ5py->U+Y|y}YVI4cpzjN;F>N~||eIGiiSo#tg+FqD~Wo~9GDnojEdZsk2XKORxLv=YgN{%Rrc+;`OD(x zIMK+t)Z532zEcJ~(3TwEJ%V7si)GijmZ${z-j|EyM1=G)qmm14^!`qtO>GGwtZNRB{%5@Ilez>?07T=O} zh0*0GJ8|RvIQ>pnF;5zY(kfJUyaJy)!+_GkrTXi>o!ck&73HxF%_s`&Mo0u!LFxw&E9tlCS5aD{&oc# zKm*4sZ6kvxd54;xh6eA7G`4l@O;H>DTklG<)D~&Nn`ff?_8lDEqsORY_oK}BbB`ap zX|%MlyYa*5kX&;})X332jXefmzHs5p<(o*Qza1iiKA8VZlzNBri)E#LrtM`2YK_Ky zS%wFf;e*f)tpm#rvos%7{InEyX$@DP^?kNNwmEDa!ooNByfw|i#kT{pjAJuxkHV-x z)a#5kJUCig9rPDiT3s$_<+=?nTI)85VPPZI4Wt)1eNMHsw%TgU=~j#?_WFHIQ@WPS z`Zkwcv}NnkZN@)MJKZ{ff$A@qx7_ZqZfX>}YRZ%qX+}DdI-&AopjonqJpv81Z1+7g z@|UI0nu6<+C$j#;_|1l;OTIPxTh5}&MB4sj@Nu6NUQMa%N?#oRHpb~QR;-+6)=%2( z!56`XBH6SW6@~vo3b%zFI0BDW2G1XX4s@Q6WGe$690O5h4@0C3p*%MxI5;W>O@D1* z1E=4RRS2sKAhXc6x*S%P4=6WC)pq65mt5hQQ+ID%8yOZJHL9m+c<>KM78I5aZQC|z zOegbz&N&>^2QCkqln%WWx)aT3a`qg$aNQnkJZ{NUuq+Hh9%pWWgwz%)6klV{LbeHs zA%(NI;|OitwggA0`5SD*C$y(YNij_VF%N5FWJAmR{d@{E~CG8 z5$_>*;+;VAE?>6&|BPtn@(jRbjp)8)N+NRh}f>LK5aUt*e89i zm@XfkneP+9e&0KM{ZQka?(B~z=1)0lju^Er;%)wSvAd(c2yA*Jao`WFjnw3W_$E@9 z-~6Gmd4=R9{B*CohiA8LEdsh;fBxiJ(NnV?jdPBbLohCo?W-g&TqD~{YQdsLBL6>) zd}h#99_=|Ou`8pF5CYA$e_H!!P-pLE#?9^+SF5vo<|s-d?F#4(?SCrnw9r+$m&E(~ zDfX0BbA;E9;xRk4KCt+4|8A533u^jVg`tj_qE$Y$ggp)_vD1 zUd*Y214yv`F<(gW>dj|E{eQi@`vcT#`hRbi6$3i729KU$)_)E*f6Bzc=D!QYl_FND z_~`#G6`#sO>}V0yNV$p{Dc7n-LS;}J)Dg9jbwt6aB095Fl$A}G8^;I5BK?68QEJvx ziH#H6hw!MAHD}4{p;puRH1NUQ`9+MlPHA-kYTL>!DE<8g2$DOwb9g#*tO!W%pd!s& zt5$ofsMRoKRja*1wc4QIS$wZVJ`Zh=u2;z$_;nP|4oC6q@LKWg>Vf=Jt9W*!RXi&j zVW9vSHD`(h8KVD-2sy7-1UX)IvrNUZ3YHmXr)zB$lFZxdCvpaCvBc z=q{7tUC!=9ig>yCKg7!=C|+KU@@3;E0kip^0_HYhvVa-;^hsoFu2f%##X<+D{D;uk zPD4xS46Qy7Ma4;#>S|e3yr)_b72E%+h>Fq3py9wFvZ(lLSyY@Oi;B}~Ma5@jQSo&Y z759i}-KFo3H*g`&n>UgFe<>q=B+H1qE)??=qbAai$3dyE3W@v6LgIUfJEm7wA#n%_ ziR-o;Bfbm((`5dkRY)9=z@I;R_pVh)92(kR782(HcIZ#(@b*K0Nrx|$mY{TaF-nIY z-ACzgaCd)yt91CGS)UEIsOv`BZj{g-X*=NoTK9#QA#G=FL(OAR^C&}XmI7N=&10^j zdCZ;11L~vyK?eZZ%m3dH0?UyO_^%tj)W+_=(Fgix;Ddxfa4sNK|1Z%Z8jp74 zAq*Dbk|i-MMEZG+Q<{ibInu8JCcogIm9$6j62qN&G%)?mQS|21q)mf(cZA`b6*){B z@$a>FO1Fh`-2U}zH=2%36sI6td8w~)_{g!5=$qp%{(>?|2t}c!HxFqN8VhenM~T$! z6MaNFz)luP`-u8J{OaGCWWEny^gAPTb>`i8G?{egs~ypD(Q3AcMv7W$cXA|;noD$x zlAjj3;F`cKsagV@;R(7wp|}?}@CwkN=o6dvMKPL-J8$jq|76gJMqk=YexjIR8l7<| z2AztOjyv(sGN7K-Pp7X~g_Nw~BNf4$AYJMv+?vK)OGJwC;ERMMYKEQoDi1zVYzZ#d zO{@17|EoM)NKmh@LZj3FCzqIiyhSZd%H+4iTrmE4yw(q6_6%ig|JKF(IJP-ezdDG& zwHA*;yyA$`mN((o%PuJx(}(hjH9Le(a`!^{Ll}LGUKW0wJh6%;Vdifhd{?v>y&=e+ zAJM!uQ=YT3s!a~!hAMTLBagma)ayBtSgkwqra(~(6NZVkGj|9_*aioxzX-_%wGfl0 znEJ`X*Gp8a{DMSeIu^IWr627O@~82s)&dUvCu_iV;tW%9Ab-txwU78likU7>QA*%& z_WIKcsTGetNN1()ii2Db~7SzGQqJ`U6nCNavAIWDb4c>R*&zDbk5L3$yZy`O5Z)x>`LKDR_ zocY;OACw^tk-_VeP+bYC5xt6Si5UxtZurRyEURoKv_|L&vKzP4Sfa~k$cD4BxJX)nM- zXfDbsBJ>;FXQrzz2wproHpy;^vSX8eG+&HE-O7*uS4}U$pL^`g=X02O2{Rr1_~M#g zsN=q=7<=KG@u*z=$VId)MU{`1HoW02(i6&~UtIY4z%43YiFo_`6%yi017&#}19q*% z5D{neUI2ejsrWw|^J$kr1K})pKv{Q^#p%|Cr>oe4M+H9;z*SF=*odQxY%90C z>N6Z(X^;p9?hA%@|5$Vf^F4X!SzuiiFxtJq3W6O(gg{(yefS$UCWxImeFqVN2|Wr@ z9Yn+LK0JIiOfH4H+|eVybyx+L#@Kjo$sb z`Is$@B?r0mbJ=#w6D_q&rz|IgpE+rXo7jT0I7A9F`N5s}GiIp<&ADphG}G?PPBhR; zi61V8GLOe=R(;G8ZF5_Swi2{ zp53zXunFoeXFoJS%W67Q>lhd1WsXFTO8vYw($Y21XASCL*Jw-ld|m#xyXw}fIo3$J zBkVDdc&l%?4*k#sJo}{?p6}8e&vR*m9&BNFy2E%pQzjEV$rhnU*q7*RwiC~WID;PI z`J|XslP7p41&}|Oj?7ogNjx{ z@M-Er>hRswX>XYhg>f7on^&1VNsi$d*e&Ve)!J6KhVVW_T6itR^t|m+Kxn`|q zqh^ohnC6`3n&z&iT=TQ$rAA^2Yr{5Ro3c)<7u$;M$cCZkcr-hJ9VV~rvd6ceQ`j^e zIydSNjdbX^d5e>`U#nBN>@BYQv}o(l$ZEW7%oMY7*KP z?3kM(3iD?#&oT$0E&9^6Q0LxJFyp4JEkAs?$W)^36n_}a#KN^8b~#Gx%$~Y5%Y-G@ zE?&yn_*v+&ov9dnSE^6r>ifSn@YY#LAFy%Y z%c|f7N-ch#Cv@b^fuii&UQ7S{-Su7LjIQr96rb&{a*iPFfD4>IOkXHZK zp$0PbE^oDy5a7^I((}I<;_Kv{{WCsU)XDxY543m&0V$saYijDV#ajKAHk-}8)(=?! zqy3pnTfRSia?+5m(VzV9mZ|bl2i_aeD4VDJRJ@_&SvLVsRnWKPl>-&@GV&nhrbDm& z#b(L3J+GU055n)=l`qV3*)CWolui*VjXJc9`ED+n#%x2=7`I=IctQhG%JRtzvO95S zP6zs;^1GmCU^eL0(jwg?{vHZyGtkQHoQ_Hg$s^CE4o2j_0B8^G`@sM@LZ?N&ShXPa zD!26F^pjA0lY!3J53+TDcBAYQ^vpr2bSK>bbI#uV=Wf{l*0O}w(+NnoBX1NCdHFsX zr0s$`!c|?U?BuWOCKgmeVWql2CzQ;FFKT|rBPR5-M1LKUX^))wTb#bE<{i41_Zhb5 z(kv5N>$~erDDoL6+`o45A+8oC)Xm9;!tzNCJwp6qkUZ8|m!$C~Jsv)hTMQlXcm?YB z$vYb7Xa8qBIz-3TIz)5w28C>f@cXLPf2`8#x1sT3+C-E7lR32PAiWhGq_?7D_3I3& z6D~|Bz-bA1PR?Y$X0%5bu8Br<pyRFlC!Z-IU*$g) z#uFmXXYmA*@Y*!OGBH2E|&O&!e9 zqtCN7uEO}b+PE6e{@GO<*PtJEO>LYd_IOg4wLC{mjDKz1hBUyl{;c_RFn?riye@HJ zX4RgTQhR#JKTcK&3aWkh>GYzY0KWjgpzz4H{sI4X>dHUr{adYm(^BFlCL|_Dq$Z?| zPmXnnNWjBu(i0|HlM}5e-!YStaB$+#?K=st$>?Vuqok(bK`pW9+&utO@-c4-=$4*L z9PrpRUp#zG{srOhpEZ5{t!@w=o8+g2Ae@XyoE-Vkw*R@#|NeddbG?2zeJR-2iP%&5 zc)AGebprN#JjP?O-w~Kf#piMOg$Y>d?@#4Ae?9X>*LeAmJDe@$4^Jq=Gfn@?pA3z4 zYx7lj;++c5+G6k&twR_)jHhEU=$(EN<3BPEc>a`pwwp}<5IhsF9s1p?!onl_5q)B8 zdNRV2Ms5dBN0Z;F!qi`B2J`Sdy)h|iDa1{=dQXf?O(p@#wOvwd>O|5>xrSj~x&6Pc zDkW~c%1`8z2h@lG4e;zeYua9U6N7H=D$G}Dx;K;07Uc1b8aa(83~1z22C&EN@H8L! zOhBV@BA|#9+5-|#oRw4m+;G^Z;5mJn_zS=T31*?g{2VeDPw4v$e{GZ#_1Yq3 zw*pTW{2V>}JK*^N-(Xu?(49UQM{Pek)*r-QD0=xHL3;3e9J{XQ)_)ey1kA=?1j!*6 z5QwgzKYwp>o7}}yz@FhLWCQW+peA@Wf<2yKmO>bHU9}5-L0xeWL zPcgj4@E(H&2L#84u|bf0h)?A6^(=UnnGK#-)(G2kz!SW@(b2yh(}@YgQ;DLP0eCLa zXl6W<%%n5Z@jS0Z%yQ;SW-YUk+0N``4l~E`EV>*#*DH^?hc>bgn8(a><}D*Jf2cGn zUe!Qlr)s8hQhBI+Rju$WqhM9IDpD1r8l)Pb8mpS1N>OE~W~%0^mf#sjt5xe&n^iki z`;o3ap*pL&sJe#d%`#+zstaB-lxq&2?8%T{RAG31rV7Ptff8S?T#HqBG6Q*p)E`6c z$mN+HYFss${>n8|-4Ns5l(?sI^--?NmHeS{oXk_b!!m=F{FO?+yK?QTMm9j9@OZ z51EVH9c{XHuj*`_?)aO}Kh!zt!gTSvwYs;u__~cS{#N(4o-JO(l>B)8!v<~*f*ac5 zuU$hs+X1%ewi|7;ZL1A71~)^PA>KCIkZAalsn*5IK^Na>t(}Uwh%s!>PGT5!x%AL2jBP}-}JNchH$)h$2aW3VC`c$_U{c+SB*dBVW~WPf06S3q1gT& zrRQ&OU#N#|IAid~vYoNjp%^w{{LIJq_@?(rB4|FI!&2vn9p;+wEzWrN#NhS6Iy?K= ztg1VX-`n?=7TQvIEvz8EEm$aTI<~Qjh+tCXbwLP=Bt*xHh@!+9#`zDwK`;0eTmso_+I4^n3P2&K&4%tsFh1Mv)>B3)y^d&2CZSJ2 z?_<-yLzMi8Qxn$o0G%d*$>3%%1xy8vU^*o=(VrRAGc%i_ z-zoZ>qQ7b99-f~Mtrvg?!9!pP`ImxaU^!Sxn)tAZa69>T5bor97tik|{vv66z+SMA z>;2#$I83`Pfi5`E1@*h2eixGZ8>k~) zg%~cxa3O{Zt#F|gtMNRXkUocSAp;lqdkxS)pT3WNr_ccpZ3&=w>`XvzCK8HAlfYy{ z^)ysZL-jON4bkZ?bXqpWH(Irrv=*>}w6$O(*hyZi(<$UOhB|4elZGPF@6dT0oCW8= zd2o^RPiRXBdcmjQ61dK@Sz7M^7ZiX(P=p*6gAz~*s=#nC0#t(L)uR69RbHc8`sAvCq?`O_s@cJ z;5@iUnyhvabT0-apcGVr;a~))1~ovIUEZV0v`&`GTICEDEOO*S781&T$Zzb2bB9QK zi@vEBkHJN+PoJvLot#chB)tJl0+WHYWF8j06${=A8=GIDn<|uyWEZAD!~i zDIb0E(I+4Jdq|XrM0rS*heUZulsBl3WwRa<<{@Do66PUc9(?oQn+M-K_~yYk559Tu z&4X_qeDlz;Hgv2F-g!uwhm?8fn1_ye=$MC&dCWUh@#ZOhPNP*#gtMXWkLZ>3X9J<- zXIIiwpB}c;!;t^Rp0kni(>b2zywulws0=lA!$*1McKFy158J6V#3sh@Fo1snd<)=P zfE~WB3-pH+iTKz6IF97&{na2V-~`Bs>ffzV-J`9f=*3 zcS;++ouqv0oy4~Qz6J0tfNueO3*ei)(_nURfOmpFL-9oI@TVRASWkp?gEt|(3E@r1 z_*vQ_t(cZQ3dI6=(+QQjjZ|-dKl`~S$+S|Kq8CTi3-m6-_(`K7jecY+G>WlfuTFA> zCwHmM7YK2CN()Mos$9<$~ zEEF0C##3%R8lm!KE9Mf;1I?7ZFsW}b@fM(xWH-(dKL^f(>&8R0nzoctLMC~Nf3`?m zqm&Ii!Bga5jEJT2p;;F`BD|rGuogmNAXx~}vYXPT^s^G(#!9RueIv+Q291BLRj?L8 zV?rN`Ad74LhqboyA;Ar_UVg+!>wUD|N9%pG-p7Z8Xn2T*hiG`{7J{Mp5bHmDG&^L% zsTi8zbDdJcGEfdGxE@AW39#+XaKaIw8q|PV?vEtI8#y--jsm0cY-6}aF5zMi9PCKE zZh);0u+@Px1xy8vKqJl>;79PFnL2X%dp~9A{UqmsbNbw0f35BY&M(Qki!!9Y(%FMR zp7~X9g!nOFN0goDZ0x*2{uF5^;LuydPm*?u@HC~Y}MfVyml3yoc9 z?2ZOwz*y3Gm(d6S+Z?0+G5Q~)|1ozr+I;#QAz2ZU6(Ly> zk`*CY5t0=lSrL*IAz42Cw(rb7J!?Zs+Kpusx8*CvHPw1QRzMowiiT(Ce;S!eBTt>k z(!lphy;I8K=ie(cXt>^7q~Q_t(3r2gpLR#kBZ3|gHOg-4-7zwrc{kxca1iJ{G5?Mj zLX#e7Vm+|D?-}yswKsw;kr{)3On3pv179Iu23!T#z;)W6MVtPg_d(vRjR)pE(9B32 zFg8oxsErg^^b=4R9c7+NBkqONKXBA7A3Sh$UFJLv1-6j$37+3d{7G^=h1_iePm^Be zZ04+7%JC#LeTv$)as4zkJ;S|nas++lY$es^ipk}ZD*HB{ysEVErDW!y6xE@jLER`)b(;6p+C zhO>yV&SIWz0lBxFCEQyImVxC!Z#-+rr*X`7VBd1&X?;e@J|ktHk#g>Brw7XVcwHZg z+8Kl6Q08^6Q(QBvh==vjQ6C-6%@)`<8#`N&d%MwWfsZ%!@uoiB)W@6pgUl8jz`hM- zw!qHW)MXE#t7f)oyb)ua@C1->bb#~VJMbe z@7|hK?bH~-%;$#mIO_9V<0)O^zX_zxCWoERm5u5@pBvDVfSzb>L-w`*d~QgK*`Eh| z3NC@`j6dsuW@R*AT)+%|k@?b9#al@!S2uHY3#+Q?*>|If*i7c^Z|5`UPf6R%(Z~nU zXSlbY<1X{H@^1FDI>z_7Hg@;etV~{T+ zch81!h99*57C*{MT0|_)#fMUWt>?y1WV`G=q`Th_>*R2!HG34ab3U5w=1|{&en|YF zKb59BMeBS9o{y4x)c%hAoxx+&8bL+I;G(1QTdFB{o^ojDSAPA|^o*|a&*rjYE0UjS zYtknrgs6 zYVs!YWo{h$J&SMh`Z70xHIz%qyPTuJX!azd*|(tAud|C)%6XHg^~LVHM!%;U{cbY) zJ;&(xYT7r~w9lm_TG61DHd--IMylFcXxdt0+FEGZT4>r@Ok2nBgm(0+rp5Is#e8djBSIttcpDqkwiwdz+(- zoyShnt5Y1s(B?F~mi4PJ)N$GUOzV8EIM?Xib&gW@JS%j{+3Bp*sbHNLTB|3HHyX{Cr$6`arVnfGTxICBo z=W&!^?VD-YJscIR!I)1A?&TQH8jJDxIf*uY{3@tqts>+dDoa6ki^ByVZIm+4p^aA}7?`sV2D-G{m!}~(R`y#{p5r+3QhTk=Y-^GUCMeKh! z)~PZ4t~C4}!ESdA+I|C0Yu(Q(_t63Ce)vvGIzteo)2{oGRj8n^!{K8s{LA0B>wrpW z=P=|zJJXG%wWHRp+x)ciYjggsc3yM#nDa}IvPYWpdvpH7<_(Xnb6z#)*PeK4{T3%> z&TnsJYcl5@b3V6C<(xO?Zfzvzd}Pjl)z)&(zs&iwZO?4o=EUZFRoj}m1?J2)CCIhy zV=mP0c}lTot!h&HbyiPvXZ4OP`^;IZbz~29F79_OF>!k?HF0~kHQ95yNiT;k_2|S@ zEaDGXpY$M$*M7pvhb>6kPD)kD8*MAx+wHach^^h-f*#Nd_YLE+#w5Hu z313dacWem-J<0VIXtAF0mDWD}oO*x7TBY;IozKc8zT~kd$sdtC$=ctYH2aQpm|X+k zXO+^Q*o)-PY)KNZ8tHGyy+u&k9War0F!xTo4Q|Z@s=R?kW zoewzgVHXvbWpXZKN*5Z-V{GPn3wJ-{UI9{E$P;_XDc;+AE~Cl9y9@*;SYexzcJ(rMjf6Hr7{CjteEkU#-Z~Dm$%3)3w&pP5j0@ zwe-%Vm&MSujvm)zVO==r!b2A>x<-E3RmbGWQQh|N7ly~`VYT6fdRJh0uiBNj04rNR zBqhi{+RkB594_xhoD~&FKCljHnp5@oD`u?Wp*8h0Z&mLN}tfgi1))@~!K6}~v84s*l Owq@(a_0LWJ-v0pYn&W!_ literal 0 HcmV?d00001 diff --git a/dapp/src/fonts/Segment-SemiBold.otf b/dapp/src/fonts/Segment-SemiBold.otf new file mode 100644 index 0000000000000000000000000000000000000000..79a8e64891d30709c130f3c53e4245642ebf06fd GIT binary patch literal 33161 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 Zn(^p{S&LWAxc`yG8#k?8_3SOT{|~+D`fvaM literal 0 HcmV?d00001 diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index de73c60c8..fe038e9a8 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -2,7 +2,7 @@ import { StyleFunctionProps, Tooltip, extendTheme } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" import Button from "./Button" import Switch from "./Switch" -import { colors, fontSizes, fontWeights, lineHeights } from "./utils" +import { colors, fontSizes, fontWeights, fonts, lineHeights } from "./utils" // Currently, there is no possibility to set all tooltips with hasArrow by defaultProps. // Let's override the defaultProps as follows. @@ -10,6 +10,7 @@ Tooltip.defaultProps = { ...Tooltip.defaultProps, hasArrow: true } const defaultTheme = { colors, + fonts, fontSizes, fontWeights, lineHeights, diff --git a/dapp/src/theme/utils/fonts.ts b/dapp/src/theme/utils/fonts.ts index 18a482760..533af2b4e 100644 --- a/dapp/src/theme/utils/fonts.ts +++ b/dapp/src/theme/utils/fonts.ts @@ -7,6 +7,7 @@ export const fontSizes = { } export const fontWeights = { + normal: 400, medium: 500, semibold: 600, bold: 700, @@ -20,3 +21,8 @@ export const lineHeights = { lg: "1.75rem", xl: "1.875rem", } + +export const fonts = { + heading: "Segment, sans-serif", + body: "Segment, sans-serif", +} From ab39ddcde3b9a8fecccc60c5149ad53a23975dff Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 11:51:19 +0100 Subject: [PATCH 34/53] Add typography for headings --- .../components/shared/Typography/index.tsx | 72 +++++++++++++++++++ dapp/src/theme/utils/fonts.ts | 28 +++++--- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/dapp/src/components/shared/Typography/index.tsx b/dapp/src/components/shared/Typography/index.tsx index 1ffbd9ff1..5ef3cfb50 100644 --- a/dapp/src/components/shared/Typography/index.tsx +++ b/dapp/src/components/shared/Typography/index.tsx @@ -1,6 +1,78 @@ import React from "react" import { Text, TextProps } from "@chakra-ui/react" +export function H2Xl(props: TextProps) { + return ( + + ) +} + +export function HXl(props: TextProps) { + return ( + + ) +} + +export function HLg(props: TextProps) { + return ( + + ) +} + +export function HMd(props: TextProps) { + return ( + + ) +} + +export function HSm(props: TextProps) { + return ( + + ) +} + +export function HXs(props: TextProps) { + return ( + + ) +} + export function TextXl(props: TextProps) { return ( diff --git a/dapp/src/theme/utils/fonts.ts b/dapp/src/theme/utils/fonts.ts index 533af2b4e..efa338c48 100644 --- a/dapp/src/theme/utils/fonts.ts +++ b/dapp/src/theme/utils/fonts.ts @@ -1,9 +1,15 @@ export const fontSizes = { - xs: "0.75rem", - sm: "0.875rem", - md: "1rem", - lg: "1.125rem", + h2Xl: "4.5rem", + hXl: "3.75rem", + hLg: "3rem", + hMd: "2.25rem", + hSm: "1.875rem", + hXs: "1.5rem", xl: "1.25rem", + lg: "1.125rem", + md: "1rem", + sm: "0.875rem", + xs: "0.75rem", } export const fontWeights = { @@ -15,11 +21,17 @@ export const fontWeights = { } export const lineHeights = { - xs: "1.125rem", - sm: "1.25rem", - md: "1.5rem", - lg: "1.75rem", + h2Xl: "5.625rem", + hXl: "4.5rem", + hLg: "3.75rem", + hMd: "2.75rem", + hSm: "2.375rem", + hXs: "2rem", xl: "1.875rem", + lg: "1.75rem", + md: "1.5rem", + sm: "1.25rem", + xs: "1.125rem", } export const fonts = { From 94a544adc81c88130c22e074c826617cb508a405 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 12:21:52 +0100 Subject: [PATCH 35/53] Fix bug with incorrect colours --- dapp/src/components/Overview/PositionDetails.tsx | 2 +- dapp/src/components/Overview/index.tsx | 3 ++- dapp/src/theme/Button.ts | 2 +- dapp/src/theme/Switch.ts | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index e07b4a1c1..e4ebc90f0 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -22,7 +22,7 @@ export default function PositionDetails(props: CardProps) { Your positions {/* TODO: Add correct text for tooltip */} - + diff --git a/dapp/src/components/Overview/index.tsx b/dapp/src/components/Overview/index.tsx index d5fde912e..e60bab881 100644 --- a/dapp/src/components/Overview/index.tsx +++ b/dapp/src/components/Overview/index.tsx @@ -14,7 +14,8 @@ import { USD } from "../../constants" import { ChevronRight } from "../../static/icons" export default function Overview() { - const bg = useColorModeValue("white", "grey.400") + // TODO: Create a custom theme for card component + const bg = useColorModeValue("gold.100", "gold.100") return ( diff --git a/dapp/src/theme/Button.ts b/dapp/src/theme/Button.ts index 0273e2162..cb59ad1b8 100644 --- a/dapp/src/theme/Button.ts +++ b/dapp/src/theme/Button.ts @@ -18,7 +18,7 @@ const Button: ComponentSingleStyleConfig = { borderColor: mode("grey.700", "grey.700")(props), }), link: (props: StyleFunctionProps) => ({ - color: mode("black", "grey.50")(props), + color: mode("grey.700", "grey.700")(props), textDecoration: "underline", }), }, diff --git a/dapp/src/theme/Switch.ts b/dapp/src/theme/Switch.ts index 1529dda02..52ca179b0 100644 --- a/dapp/src/theme/Switch.ts +++ b/dapp/src/theme/Switch.ts @@ -5,10 +5,10 @@ const Switch: ComponentSingleStyleConfig = { track: { _checked: { _dark: { - bg: "purple", + bg: "grey.700", }, _light: { - bg: "grey.200", + bg: "grey.700", }, }, }, From 3a05a45e3ab62f29ad113330c3ee8b36bf827dd8 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 8 Dec 2023 13:32:35 +0100 Subject: [PATCH 36/53] 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 2c2a3afd7bb5df753c394638da0dd7c679341ca1 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 11 Dec 2023 10:18:04 +0100 Subject: [PATCH 37/53] Remove unnecessary use of `useColorModeValue` --- dapp/src/components/Overview/PositionDetails.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index e4ebc90f0..9b48a21bb 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -4,7 +4,6 @@ import { Button, Tooltip, Icon, - useColorModeValue, CardBody, Card, CardFooter, @@ -22,7 +21,7 @@ export default function PositionDetails(props: CardProps) { Your positions {/* TODO: Add correct text for tooltip */} - + From 21dc2ffd6d72dd881f79529a759d0a58175039d7 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 11 Dec 2023 10:30:49 +0100 Subject: [PATCH 38/53] Simplify the `Header` component --- dapp/src/components/Header/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dapp/src/components/Header/index.tsx b/dapp/src/components/Header/index.tsx index 56e427097..09d7fc1ed 100644 --- a/dapp/src/components/Header/index.tsx +++ b/dapp/src/components/Header/index.tsx @@ -1,15 +1,15 @@ import React from "react" -import { Box, Flex, Icon } from "@chakra-ui/react" +import { Flex, HStack, Icon } from "@chakra-ui/react" import ConnectWallet from "./ConnectWallet" import { AcreLogo } from "../../static/icons" export default function Header() { return ( - - - + + + - + ) } From aa133deee055d45fee2659f66052c50a19b83e5a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 13:31:09 +0100 Subject: [PATCH 39/53] 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 40/53] 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 41/53] 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 42/53] 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 01086fce2f4abda3c67f5a1d01502a5fc9ea7711 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 14:09:02 +0100 Subject: [PATCH 43/53] Approve->authorize for vaults and functions around it --- core/contracts/Dispatcher.sol | 22 ++++++------ core/test/Dispatcher.test.ts | 64 +++++++++++++++++------------------ 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/core/contracts/Dispatcher.sol b/core/contracts/Dispatcher.sol index fb9dfdca0..ed5a0b4d6 100644 --- a/core/contracts/Dispatcher.sol +++ b/core/contracts/Dispatcher.sol @@ -9,14 +9,14 @@ import "@openzeppelin/contracts/access/Ownable.sol"; /// generate yield for Bitcoin holders. contract Dispatcher is Ownable { struct VaultInfo { - bool approved; + bool authorized; } - /// @notice Approved Yield Vaults that implement ERC4626 standard. These + /// @notice Authorized Yield Vaults that implement ERC4626 standard. These /// vaults deposit assets to yield strategies, e.g. Uniswap V3 /// WBTC/TBTC pool. Vault can be a part of Acre ecosystem or can be /// implemented externally. As long as it complies with ERC4626 - /// standard and is approved by the owner it can be plugged into + /// standard and is authorized by the owner it can be plugged into /// Acre. address[] public vaults; mapping(address => VaultInfo) public vaultsInfo; @@ -26,23 +26,23 @@ contract Dispatcher is Ownable { constructor() Ownable(msg.sender) {} - /// @notice Adds a vault to the list of approved vaults. + /// @notice Adds a vault to the list of authorized vaults. /// @param vault Address of the vault to add. - function addVault(address vault) external onlyOwner { - require(!vaultsInfo[vault].approved, "Vault already approved"); + function authorizeVault(address vault) external onlyOwner { + require(!vaultsInfo[vault].authorized, "Vault already authorized"); vaults.push(vault); - vaultsInfo[vault].approved = true; + vaultsInfo[vault].authorized = true; emit VaultAdded(vault); } - /// @notice Removes a vault from the list of approved vaults. + /// @notice Removes a vault from the list of authorized vaults. /// @param vault Address of the vault to remove. - function removeVault(address vault) external onlyOwner { - require(vaultsInfo[vault].approved, "Not a vault"); + function deauthorizeVault(address vault) external onlyOwner { + require(vaultsInfo[vault].authorized, "Not a vault"); - vaultsInfo[vault].approved = false; + vaultsInfo[vault].authorized = false; for (uint256 i = 0; i < vaults.length; i++) { if (vaults[i] == vault) { diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts index c9037b850..5c29d54e8 100644 --- a/core/test/Dispatcher.test.ts +++ b/core/test/Dispatcher.test.ts @@ -39,11 +39,11 @@ describe("Dispatcher", () => { await snapshot.restore() }) - describe("addVault", () => { + describe("authorizeVault", () => { context("when caller is not a governance account", () => { it("should revert when adding a vault", async () => { await expect( - dispatcher.connect(thirdParty).addVault(vault1), + dispatcher.connect(thirdParty).authorizeVault(vault1), ).to.be.revertedWithCustomError( dispatcher, "OwnableUnauthorizedAccount", @@ -53,49 +53,49 @@ describe("Dispatcher", () => { context("when caller is a governance account", () => { it("should be able to add vaults", async () => { - await dispatcher.connect(governance).addVault(vault1) - await dispatcher.connect(governance).addVault(vault2) - await dispatcher.connect(governance).addVault(vault3) + await dispatcher.connect(governance).authorizeVault(vault1) + await dispatcher.connect(governance).authorizeVault(vault2) + await dispatcher.connect(governance).authorizeVault(vault3) expect(await dispatcher.vaults(0)).to.equal(vault1) - const isVault1Approved = await dispatcher.vaultsInfo(vault1) - expect(isVault1Approved).to.equal(true) + const isVault1Authorized = await dispatcher.vaultsInfo(vault1) + expect(isVault1Authorized).to.equal(true) expect(await dispatcher.vaults(1)).to.equal(vault2) - const isVault2Approved = await dispatcher.vaultsInfo(vault2) - expect(isVault2Approved).to.equal(true) + const isVault2Authorized = await dispatcher.vaultsInfo(vault2) + expect(isVault2Authorized).to.equal(true) expect(await dispatcher.vaults(2)).to.equal(vault3) - const isVault3Approved = await dispatcher.vaultsInfo(vault3) - expect(isVault3Approved).to.equal(true) + const isVault3Authorized = await dispatcher.vaultsInfo(vault3) + expect(isVault3Authorized).to.equal(true) }) it("should not be able to add the same vault twice", async () => { - await dispatcher.connect(governance).addVault(vault1) + await dispatcher.connect(governance).authorizeVault(vault1) await expect( - dispatcher.connect(governance).addVault(vault1), - ).to.be.revertedWith("Vault already approved") + dispatcher.connect(governance).authorizeVault(vault1), + ).to.be.revertedWith("Vault already authorized") }) it("should emit an event when adding a vault", async () => { - await expect(dispatcher.connect(governance).addVault(vault1)) + await expect(dispatcher.connect(governance).authorizeVault(vault1)) .to.emit(dispatcher, "VaultAdded") .withArgs(vault1) }) }) }) - describe("removeVault", () => { + describe("deauthorizeVault", () => { beforeEach(async () => { - await dispatcher.connect(governance).addVault(vault1) - await dispatcher.connect(governance).addVault(vault2) - await dispatcher.connect(governance).addVault(vault3) + await dispatcher.connect(governance).authorizeVault(vault1) + await dispatcher.connect(governance).authorizeVault(vault2) + await dispatcher.connect(governance).authorizeVault(vault3) }) context("when caller is not a governance account", () => { it("should revert when adding a vault", async () => { await expect( - dispatcher.connect(thirdParty).removeVault(vault1), + dispatcher.connect(thirdParty).deauthorizeVault(vault1), ).to.be.revertedWithCustomError( dispatcher, "OwnableUnauthorizedAccount", @@ -105,36 +105,36 @@ describe("Dispatcher", () => { context("when caller is a governance account", () => { it("should be able to remove vaults", async () => { - await dispatcher.connect(governance).removeVault(vault1) + await dispatcher.connect(governance).deauthorizeVault(vault1) // Last vault replaced the first vault in the 'vaults' array expect(await dispatcher.vaults(0)).to.equal(vault3) - const isVault1Approved = await dispatcher.vaultsInfo(vault1) - expect(isVault1Approved).to.equal(false) + const isVault1Authorized = await dispatcher.vaultsInfo(vault1) + expect(isVault1Authorized).to.equal(false) expect(await dispatcher.vaultsLength()).to.equal(2) - await dispatcher.connect(governance).removeVault(vault2) + await dispatcher.connect(governance).deauthorizeVault(vault2) // Last vault (vault2) was removed from the 'vaults' array 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) + const isVault2Authorized = await dispatcher.vaultsInfo(vault2) + expect(isVault2Authorized).to.equal(false) - await dispatcher.connect(governance).removeVault(vault3) + await dispatcher.connect(governance).deauthorizeVault(vault3) expect(await dispatcher.vaultsLength()).to.equal(0) - const isVault3Approved = await dispatcher.vaultsInfo(vault3) - expect(isVault3Approved).to.equal(false) + const isVault3Authorized = await dispatcher.vaultsInfo(vault3) + expect(isVault3Authorized).to.equal(false) }) - it("should not be able to remove a vault that is not approved", async () => { + it("should not be able to remove a vault that is not authorized", async () => { await expect( - dispatcher.connect(governance).removeVault(vault4), + dispatcher.connect(governance).deauthorizeVault(vault4), ).to.be.revertedWith("Not a vault") }) it("should emit an event when removing a vault", async () => { - await expect(dispatcher.connect(governance).removeVault(vault1)) + await expect(dispatcher.connect(governance).deauthorizeVault(vault1)) .to.emit(dispatcher, "VaultRemoved") .withArgs(vault1) }) From f477e4500a7fffdb1082f666b711be4c92de70c4 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 15:17:27 +0100 Subject: [PATCH 44/53] Replacing require err messages with custom errors --- core/contracts/Dispatcher.sol | 11 +++++++++-- core/test/Dispatcher.test.ts | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/contracts/Dispatcher.sol b/core/contracts/Dispatcher.sol index ed5a0b4d6..88e14f103 100644 --- a/core/contracts/Dispatcher.sol +++ b/core/contracts/Dispatcher.sol @@ -8,6 +8,9 @@ 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 { + error VaultAlreadyAuthorized(); + error VaultUnauthorized(); + struct VaultInfo { bool authorized; } @@ -29,7 +32,9 @@ contract Dispatcher is Ownable { /// @notice Adds a vault to the list of authorized vaults. /// @param vault Address of the vault to add. function authorizeVault(address vault) external onlyOwner { - require(!vaultsInfo[vault].authorized, "Vault already authorized"); + if (vaultsInfo[vault].authorized) { + revert VaultAlreadyAuthorized(); + } vaults.push(vault); vaultsInfo[vault].authorized = true; @@ -40,7 +45,9 @@ contract Dispatcher is Ownable { /// @notice Removes a vault from the list of authorized vaults. /// @param vault Address of the vault to remove. function deauthorizeVault(address vault) external onlyOwner { - require(vaultsInfo[vault].authorized, "Not a vault"); + if (!vaultsInfo[vault].authorized) { + revert VaultUnauthorized(); + } vaultsInfo[vault].authorized = false; diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts index 5c29d54e8..2305d34e3 100644 --- a/core/test/Dispatcher.test.ts +++ b/core/test/Dispatcher.test.ts @@ -74,7 +74,7 @@ describe("Dispatcher", () => { await dispatcher.connect(governance).authorizeVault(vault1) await expect( dispatcher.connect(governance).authorizeVault(vault1), - ).to.be.revertedWith("Vault already authorized") + ).to.be.revertedWithCustomError(dispatcher, "VaultAlreadyAuthorized") }) it("should emit an event when adding a vault", async () => { @@ -130,7 +130,7 @@ describe("Dispatcher", () => { it("should not be able to remove a vault that is not authorized", async () => { await expect( dispatcher.connect(governance).deauthorizeVault(vault4), - ).to.be.revertedWith("Not a vault") + ).to.be.revertedWithCustomError(dispatcher, "VaultUnauthorized") }) it("should emit an event when removing a vault", async () => { From d0bd5dd79dc4c8d7fcb1aad6121d0657fcf7ae13 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 15:28:49 +0100 Subject: [PATCH 45/53] Renaming events and returning the entire vaults array - Renamed VauldAdded->VaultAuthorized and VaultRemoved->VaultDeauthorized events - Returning vaults array instead of vaults array length --- core/contracts/Dispatcher.sol | 12 ++++++------ core/test/Dispatcher.test.ts | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/contracts/Dispatcher.sol b/core/contracts/Dispatcher.sol index 88e14f103..309da2ae8 100644 --- a/core/contracts/Dispatcher.sol +++ b/core/contracts/Dispatcher.sol @@ -24,8 +24,8 @@ contract Dispatcher is Ownable { address[] public vaults; mapping(address => VaultInfo) public vaultsInfo; - event VaultAdded(address indexed vault); - event VaultRemoved(address indexed vault); + event VaultAuthorized(address indexed vault); + event VaultDeauthorized(address indexed vault); constructor() Ownable(msg.sender) {} @@ -39,7 +39,7 @@ contract Dispatcher is Ownable { vaults.push(vault); vaultsInfo[vault].authorized = true; - emit VaultAdded(vault); + emit VaultAuthorized(vault); } /// @notice Removes a vault from the list of authorized vaults. @@ -60,10 +60,10 @@ contract Dispatcher is Ownable { } } - emit VaultRemoved(vault); + emit VaultDeauthorized(vault); } - function vaultsLength() external view returns (uint256) { - return vaults.length; + function getVaults() external view returns (address[] memory) { + return vaults; } } diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts index 2305d34e3..6aeea01fc 100644 --- a/core/test/Dispatcher.test.ts +++ b/core/test/Dispatcher.test.ts @@ -79,7 +79,7 @@ describe("Dispatcher", () => { it("should emit an event when adding a vault", async () => { await expect(dispatcher.connect(governance).authorizeVault(vault1)) - .to.emit(dispatcher, "VaultAdded") + .to.emit(dispatcher, "VaultAuthorized") .withArgs(vault1) }) }) @@ -111,18 +111,18 @@ describe("Dispatcher", () => { expect(await dispatcher.vaults(0)).to.equal(vault3) const isVault1Authorized = await dispatcher.vaultsInfo(vault1) expect(isVault1Authorized).to.equal(false) - expect(await dispatcher.vaultsLength()).to.equal(2) + expect((await dispatcher.getVaults()).length).to.equal(2) await dispatcher.connect(governance).deauthorizeVault(vault2) // Last vault (vault2) was removed from the 'vaults' array expect(await dispatcher.vaults(0)).to.equal(vault3) - expect(await dispatcher.vaultsLength()).to.equal(1) + expect((await dispatcher.getVaults()).length).to.equal(1) const isVault2Authorized = await dispatcher.vaultsInfo(vault2) expect(isVault2Authorized).to.equal(false) await dispatcher.connect(governance).deauthorizeVault(vault3) - expect(await dispatcher.vaultsLength()).to.equal(0) + expect((await dispatcher.getVaults()).length).to.equal(0) const isVault3Authorized = await dispatcher.vaultsInfo(vault3) expect(isVault3Authorized).to.equal(false) }) @@ -135,7 +135,7 @@ describe("Dispatcher", () => { it("should emit an event when removing a vault", async () => { await expect(dispatcher.connect(governance).deauthorizeVault(vault1)) - .to.emit(dispatcher, "VaultRemoved") + .to.emit(dispatcher, "VaultDeauthorized") .withArgs(vault1) }) }) From cf0d47d893f5795308b3f88337274fc35fead4e8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 15:31:57 +0100 Subject: [PATCH 46/53] Adding func.dependencies for Dispatcher ownership transfer --- core/deploy/22_transfer_ownership_acre_router.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/deploy/22_transfer_ownership_acre_router.ts b/core/deploy/22_transfer_ownership_acre_router.ts index 2d7748580..686d378c4 100644 --- a/core/deploy/22_transfer_ownership_acre_router.ts +++ b/core/deploy/22_transfer_ownership_acre_router.ts @@ -19,3 +19,4 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { export default func func.tags = ["TransferOwnershipAcreRouter"] +func.dependencies = ["Dispatcher"] From b131f8206a9a79535a10f423f6612fd41f3ce90a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 16:06:18 +0100 Subject: [PATCH 47/53] Refactoring Dispatcher tests: - Replaced Address type with a String type - Added test fixture just like we have in Acre.test.ts - Various renames --- core/test/Dispatcher.test.ts | 119 ++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 51 deletions(-) diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts index 6aeea01fc..5307dcc27 100644 --- a/core/test/Dispatcher.test.ts +++ b/core/test/Dispatcher.test.ts @@ -1,34 +1,41 @@ -import { getUnnamedAccounts } from "hardhat" +import { ethers } from "hardhat" import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" -import { Address } from "hardhat-deploy/types" import { expect } from "chai" import { SnapshotRestorer, takeSnapshot, + loadFixture, } from "@nomicfoundation/hardhat-toolbox/network-helpers" import type { Dispatcher } from "../typechain" import { deployment } from "./helpers/context" import { getNamedSigner, getUnnamedSigner } from "./helpers/signer" +async function fixture() { + const { dispatcher } = await deployment() + const { governance } = await getNamedSigner() + const [thirdParty] = await getUnnamedSigner() + + return { dispatcher, governance, thirdParty } +} + describe("Dispatcher", () => { let snapshot: SnapshotRestorer let dispatcher: Dispatcher let governance: HardhatEthersSigner let thirdParty: HardhatEthersSigner - let vault1: Address - let vault2: Address - let vault3: Address - let vault4: Address + let vaultAddress1: string + let vaultAddress2: string + let vaultAddress3: string + let vaultAddress4: string before(async () => { - const accounts = await getUnnamedAccounts() - ;[vault1, vault2, vault3, vault4] = accounts - - governance = (await getNamedSigner()).governance - ;[thirdParty] = await getUnnamedSigner() + ;({ dispatcher, governance, thirdParty } = await loadFixture(fixture)) - dispatcher = (await deployment()).dispatcher + vaultAddress1 = await ethers.Wallet.createRandom().getAddress() + vaultAddress2 = await ethers.Wallet.createRandom().getAddress() + vaultAddress3 = await ethers.Wallet.createRandom().getAddress() + vaultAddress4 = await ethers.Wallet.createRandom().getAddress() }) beforeEach(async () => { @@ -43,7 +50,7 @@ describe("Dispatcher", () => { context("when caller is not a governance account", () => { it("should revert when adding a vault", async () => { await expect( - dispatcher.connect(thirdParty).authorizeVault(vault1), + dispatcher.connect(thirdParty).authorizeVault(vaultAddress1), ).to.be.revertedWithCustomError( dispatcher, "OwnableUnauthorizedAccount", @@ -53,49 +60,54 @@ describe("Dispatcher", () => { context("when caller is a governance account", () => { it("should be able to add vaults", async () => { - await dispatcher.connect(governance).authorizeVault(vault1) - await dispatcher.connect(governance).authorizeVault(vault2) - await dispatcher.connect(governance).authorizeVault(vault3) - - expect(await dispatcher.vaults(0)).to.equal(vault1) - const isVault1Authorized = await dispatcher.vaultsInfo(vault1) - expect(isVault1Authorized).to.equal(true) - - expect(await dispatcher.vaults(1)).to.equal(vault2) - const isVault2Authorized = await dispatcher.vaultsInfo(vault2) - expect(isVault2Authorized).to.equal(true) - - expect(await dispatcher.vaults(2)).to.equal(vault3) - const isVault3Authorized = await dispatcher.vaultsInfo(vault3) - expect(isVault3Authorized).to.equal(true) + await dispatcher.connect(governance).authorizeVault(vaultAddress1) + await dispatcher.connect(governance).authorizeVault(vaultAddress2) + await dispatcher.connect(governance).authorizeVault(vaultAddress3) + + expect(await dispatcher.vaults(0)).to.equal(vaultAddress1) + const isVaultAddress1Authorized = + await dispatcher.vaultsInfo(vaultAddress1) + expect(isVaultAddress1Authorized).to.equal(true) + + expect(await dispatcher.vaults(1)).to.equal(vaultAddress2) + const isVaultAddress2Authorized = + await dispatcher.vaultsInfo(vaultAddress2) + expect(isVaultAddress2Authorized).to.equal(true) + + expect(await dispatcher.vaults(2)).to.equal(vaultAddress3) + const isVaultAddress3Authorized = + await dispatcher.vaultsInfo(vaultAddress3) + expect(isVaultAddress3Authorized).to.equal(true) }) it("should not be able to add the same vault twice", async () => { - await dispatcher.connect(governance).authorizeVault(vault1) + await dispatcher.connect(governance).authorizeVault(vaultAddress1) await expect( - dispatcher.connect(governance).authorizeVault(vault1), + dispatcher.connect(governance).authorizeVault(vaultAddress1), ).to.be.revertedWithCustomError(dispatcher, "VaultAlreadyAuthorized") }) it("should emit an event when adding a vault", async () => { - await expect(dispatcher.connect(governance).authorizeVault(vault1)) + await expect( + dispatcher.connect(governance).authorizeVault(vaultAddress1), + ) .to.emit(dispatcher, "VaultAuthorized") - .withArgs(vault1) + .withArgs(vaultAddress1) }) }) }) describe("deauthorizeVault", () => { beforeEach(async () => { - await dispatcher.connect(governance).authorizeVault(vault1) - await dispatcher.connect(governance).authorizeVault(vault2) - await dispatcher.connect(governance).authorizeVault(vault3) + 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(vault1), + dispatcher.connect(thirdParty).deauthorizeVault(vaultAddress1), ).to.be.revertedWithCustomError( dispatcher, "OwnableUnauthorizedAccount", @@ -105,38 +117,43 @@ describe("Dispatcher", () => { context("when caller is a governance account", () => { it("should be able to remove vaults", async () => { - await dispatcher.connect(governance).deauthorizeVault(vault1) + await dispatcher.connect(governance).deauthorizeVault(vaultAddress1) // Last vault replaced the first vault in the 'vaults' array - expect(await dispatcher.vaults(0)).to.equal(vault3) - const isVault1Authorized = await dispatcher.vaultsInfo(vault1) - expect(isVault1Authorized).to.equal(false) + expect(await dispatcher.vaults(0)).to.equal(vaultAddress3) + const isVaultAddress1Authorized = + await dispatcher.vaultsInfo(vaultAddress1) + expect(isVaultAddress1Authorized).to.equal(false) expect((await dispatcher.getVaults()).length).to.equal(2) - await dispatcher.connect(governance).deauthorizeVault(vault2) + await dispatcher.connect(governance).deauthorizeVault(vaultAddress2) - // Last vault (vault2) was removed from the 'vaults' array - expect(await dispatcher.vaults(0)).to.equal(vault3) + // 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) - const isVault2Authorized = await dispatcher.vaultsInfo(vault2) - expect(isVault2Authorized).to.equal(false) + const isVaultAddress2Authorized = + await dispatcher.vaultsInfo(vaultAddress2) + expect(isVaultAddress2Authorized).to.equal(false) - await dispatcher.connect(governance).deauthorizeVault(vault3) + await dispatcher.connect(governance).deauthorizeVault(vaultAddress3) expect((await dispatcher.getVaults()).length).to.equal(0) - const isVault3Authorized = await dispatcher.vaultsInfo(vault3) - expect(isVault3Authorized).to.equal(false) + const isVaultAddress3Authorized = + await dispatcher.vaultsInfo(vaultAddress3) + expect(isVaultAddress3Authorized).to.equal(false) }) it("should not be able to remove a vault that is not authorized", async () => { await expect( - dispatcher.connect(governance).deauthorizeVault(vault4), + 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(vault1)) + await expect( + dispatcher.connect(governance).deauthorizeVault(vaultAddress1), + ) .to.emit(dispatcher, "VaultDeauthorized") - .withArgs(vault1) + .withArgs(vaultAddress1) }) }) }) From e94c5fc0f4dd7f232b5c67261ebf84991ba9e0ee Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2023 16:32:21 +0100 Subject: [PATCH 48/53] Simplifying tests --- core/test/Dispatcher.test.ts | 40 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts index 5307dcc27..1fdecc5d9 100644 --- a/core/test/Dispatcher.test.ts +++ b/core/test/Dispatcher.test.ts @@ -59,28 +59,22 @@ describe("Dispatcher", () => { }) context("when caller is a governance account", () => { - it("should be able to add vaults", async () => { + it("should be able to authorize vaults", async () => { await dispatcher.connect(governance).authorizeVault(vaultAddress1) await dispatcher.connect(governance).authorizeVault(vaultAddress2) await dispatcher.connect(governance).authorizeVault(vaultAddress3) expect(await dispatcher.vaults(0)).to.equal(vaultAddress1) - const isVaultAddress1Authorized = - await dispatcher.vaultsInfo(vaultAddress1) - expect(isVaultAddress1Authorized).to.equal(true) + expect(await dispatcher.vaultsInfo(vaultAddress1)).to.be.equal(true) expect(await dispatcher.vaults(1)).to.equal(vaultAddress2) - const isVaultAddress2Authorized = - await dispatcher.vaultsInfo(vaultAddress2) - expect(isVaultAddress2Authorized).to.equal(true) + expect(await dispatcher.vaultsInfo(vaultAddress2)).to.be.equal(true) expect(await dispatcher.vaults(2)).to.equal(vaultAddress3) - const isVaultAddress3Authorized = - await dispatcher.vaultsInfo(vaultAddress3) - expect(isVaultAddress3Authorized).to.equal(true) + expect(await dispatcher.vaultsInfo(vaultAddress3)).to.be.equal(true) }) - it("should not be able to add the same vault twice", async () => { + it("should not be able to authorize the same vault twice", async () => { await dispatcher.connect(governance).authorizeVault(vaultAddress1) await expect( dispatcher.connect(governance).authorizeVault(vaultAddress1), @@ -116,14 +110,12 @@ describe("Dispatcher", () => { }) context("when caller is a governance account", () => { - it("should be able to remove vaults", async () => { + it("should be able to authorize 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) - const isVaultAddress1Authorized = - await dispatcher.vaultsInfo(vaultAddress1) - expect(isVaultAddress1Authorized).to.equal(false) + expect(await dispatcher.vaultsInfo(vaultAddress1)).to.be.equal(false) expect((await dispatcher.getVaults()).length).to.equal(2) await dispatcher.connect(governance).deauthorizeVault(vaultAddress2) @@ -131,18 +123,22 @@ describe("Dispatcher", () => { // 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) - const isVaultAddress2Authorized = - await dispatcher.vaultsInfo(vaultAddress2) - expect(isVaultAddress2Authorized).to.equal(false) + expect(await dispatcher.vaultsInfo(vaultAddress2)).to.be.equal(false) await dispatcher.connect(governance).deauthorizeVault(vaultAddress3) expect((await dispatcher.getVaults()).length).to.equal(0) - const isVaultAddress3Authorized = - await dispatcher.vaultsInfo(vaultAddress3) - expect(isVaultAddress3Authorized).to.equal(false) + expect(await dispatcher.vaultsInfo(vaultAddress3)).to.be.equal(false) }) - it("should not be able to remove a vault that is not authorized", async () => { + it("should be able to 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 be able to deauthorize a vault that is not authorized", async () => { await expect( dispatcher.connect(governance).deauthorizeVault(vaultAddress4), ).to.be.revertedWithCustomError(dispatcher, "VaultUnauthorized") From 5322d63723d6773772acda6f8d3662b157aafb49 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 12 Dec 2023 16:32:07 +0100 Subject: [PATCH 49/53] Use `format("opentype")` instead of `format("otf")` --- dapp/src/components/GlobalStyles/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dapp/src/components/GlobalStyles/index.tsx b/dapp/src/components/GlobalStyles/index.tsx index 355c6555d..80b36e6e8 100644 --- a/dapp/src/components/GlobalStyles/index.tsx +++ b/dapp/src/components/GlobalStyles/index.tsx @@ -13,31 +13,31 @@ export default function GlobalStyles() { styles={` @font-face { font-family: "Segment"; - src: url(${SegmentRegular}) format("otf"); + src: url(${SegmentRegular}) format("opentype"); font-weight: 400; font-style: normal; } @font-face { font-family: "Segment"; - src: url(${SegmentMedium}) format("otf"); + src: url(${SegmentMedium}) format("opentype"); font-weight: 500; font-style: normal; } @font-face { font-family: "Segment"; - src: url(${SegmentSemiBold}) format("otf"); + src: url(${SegmentSemiBold}) format("opentype"); font-weight: 600; font-style: normal; } @font-face { font-family: "Segment"; - src: url(${SegmentBold}) format("otf"); + src: url(${SegmentBold}) format("opentype"); font-weight: 700; font-style: normal; } @font-face { font-family: "Segment"; - src: url(${SegmentBlack}) format("otf"); + src: url(${SegmentBlack}) format("opentype"); font-weight: 900; font-style: normal; } From d9e7161c5a74bef2dc0c6b6aaa9f01ee40438f89 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 12 Dec 2023 18:10:54 +0100 Subject: [PATCH 50/53] Simplify styles for the headings --- .../components/shared/Typography/index.tsx | 74 ++++--------------- dapp/src/theme/Heading.ts | 32 ++++++++ dapp/src/theme/index.ts | 6 +- dapp/src/theme/utils/fonts.ts | 28 ------- 4 files changed, 48 insertions(+), 92 deletions(-) create mode 100644 dapp/src/theme/Heading.ts diff --git a/dapp/src/components/shared/Typography/index.tsx b/dapp/src/components/shared/Typography/index.tsx index 5ef3cfb50..b51cd2546 100644 --- a/dapp/src/components/shared/Typography/index.tsx +++ b/dapp/src/components/shared/Typography/index.tsx @@ -1,76 +1,28 @@ import React from "react" -import { Text, TextProps } from "@chakra-ui/react" +import { Heading, Text, TextProps } from "@chakra-ui/react" -export function H2Xl(props: TextProps) { - return ( - - ) +export function H1(props: TextProps) { + return } -export function HXl(props: TextProps) { - return ( - - ) +export function H2(props: TextProps) { + return } -export function HLg(props: TextProps) { - return ( - - ) +export function H3(props: TextProps) { + return } -export function HMd(props: TextProps) { - return ( - - ) +export function H4(props: TextProps) { + return } -export function HSm(props: TextProps) { - return ( - - ) +export function H5(props: TextProps) { + return } -export function HXs(props: TextProps) { - return ( - - ) +export function H6(props: TextProps) { + return } export function TextXl(props: TextProps) { diff --git a/dapp/src/theme/Heading.ts b/dapp/src/theme/Heading.ts new file mode 100644 index 000000000..fbdf450d1 --- /dev/null +++ b/dapp/src/theme/Heading.ts @@ -0,0 +1,32 @@ +import { ComponentSingleStyleConfig } from "@chakra-ui/react" + +const Heading: ComponentSingleStyleConfig = { + sizes: { + "7xl": { + fontSize: "4.5rem", + lineHeight: "5.625rem", + }, + "6xl": { + fontSize: "3.75rem", + lineHeight: "4.5rem", + }, + "5xl": { + fontSize: "3rem", + lineHeight: "3.75rem", + }, + "4xl": { + fontSize: "2.25rem", + lineHeight: "2.75rem", + }, + "3xl": { + fontSize: "1.875rem", + lineHeight: "2.375rem", + }, + "2xl": { + fontSize: "1.5rem", + lineHeight: "2rem", + }, + }, +} + +export default Heading diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index 7db9eae86..b90c220da 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -2,7 +2,8 @@ import { StyleFunctionProps, Tooltip, extendTheme } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" import Button from "./Button" import Switch from "./Switch" -import { colors, fontSizes, fontWeights, fonts, lineHeights } from "./utils" +import { colors, fonts, lineHeights } from "./utils" +import Heading from "./Heading" // Currently, there is no possibility to set all tooltips with hasArrow by defaultProps. // Let's override the defaultProps as follows. @@ -11,8 +12,6 @@ Tooltip.defaultProps = { ...Tooltip.defaultProps, hasArrow: true } const defaultTheme = { colors, fonts, - fontSizes, - fontWeights, lineHeights, styles: { global: (props: StyleFunctionProps) => ({ @@ -26,6 +25,7 @@ const defaultTheme = { components: { Button, Switch, + Heading, }, } diff --git a/dapp/src/theme/utils/fonts.ts b/dapp/src/theme/utils/fonts.ts index efa338c48..5fdcc15a6 100644 --- a/dapp/src/theme/utils/fonts.ts +++ b/dapp/src/theme/utils/fonts.ts @@ -1,32 +1,4 @@ -export const fontSizes = { - h2Xl: "4.5rem", - hXl: "3.75rem", - hLg: "3rem", - hMd: "2.25rem", - hSm: "1.875rem", - hXs: "1.5rem", - xl: "1.25rem", - lg: "1.125rem", - md: "1rem", - sm: "0.875rem", - xs: "0.75rem", -} - -export const fontWeights = { - normal: 400, - medium: 500, - semibold: 600, - bold: 700, - black: 900, -} - export const lineHeights = { - h2Xl: "5.625rem", - hXl: "4.5rem", - hLg: "3.75rem", - hMd: "2.75rem", - hSm: "2.375rem", - hXs: "2rem", xl: "1.875rem", lg: "1.75rem", md: "1.5rem", From 9cf53e7d29d7e7994060cbe6a39c43c049712126 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 12 Dec 2023 21:00:08 +0100 Subject: [PATCH 51/53] Fix props type for `Heading` --- dapp/src/components/shared/Typography/index.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dapp/src/components/shared/Typography/index.tsx b/dapp/src/components/shared/Typography/index.tsx index b51cd2546..1e2080c48 100644 --- a/dapp/src/components/shared/Typography/index.tsx +++ b/dapp/src/components/shared/Typography/index.tsx @@ -1,27 +1,27 @@ import React from "react" -import { Heading, Text, TextProps } from "@chakra-ui/react" +import { Heading, HeadingProps, Text, TextProps } from "@chakra-ui/react" -export function H1(props: TextProps) { +export function H1(props: HeadingProps) { return } -export function H2(props: TextProps) { +export function H2(props: HeadingProps) { return } -export function H3(props: TextProps) { +export function H3(props: HeadingProps) { return } -export function H4(props: TextProps) { +export function H4(props: HeadingProps) { return } -export function H5(props: TextProps) { +export function H5(props: HeadingProps) { return } -export function H6(props: TextProps) { +export function H6(props: HeadingProps) { return } From e21a50b8fe3268e34c5e5b9da22daecf930a048c Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 13 Dec 2023 08:45:28 +0100 Subject: [PATCH 52/53] Improve signers mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: RafaƂ Czajkowski <57687279+r-czajkowski@users.noreply.github.com> --- core/test/helpers/signer.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/core/test/helpers/signer.ts b/core/test/helpers/signer.ts index c6fea1c0e..22dcda331 100644 --- a/core/test/helpers/signer.ts +++ b/core/test/helpers/signer.ts @@ -25,13 +25,7 @@ export async function getNamedSigner(): Promise<{ * @returns Array of unnamed Hardhat Ethers Signers. */ export async function getUnnamedSigner(): Promise { - const unnamedSigners: HardhatEthersSigner[] = [] + const accounts = await getUnnamedAccounts() - await Promise.all( - (await getUnnamedAccounts()).map(async (address) => { - unnamedSigners.push(await ethers.getSigner(address)) - }), - ) - - return unnamedSigners + return await Promise.all(accounts.map(ethers.getSigner)) } From 2831ef6791b974690550a78e386e184dcd9f3190 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 13 Dec 2023 09:22:12 +0100 Subject: [PATCH 53/53] Don't return awaited promise in getUnnamedSigner Fix `@typescript-eslint/return-await` error returned by eslint. --- core/test/helpers/signer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/helpers/signer.ts b/core/test/helpers/signer.ts index 22dcda331..0ae57f35e 100644 --- a/core/test/helpers/signer.ts +++ b/core/test/helpers/signer.ts @@ -27,5 +27,5 @@ export async function getNamedSigner(): Promise<{ export async function getUnnamedSigner(): Promise { const accounts = await getUnnamedAccounts() - return await Promise.all(accounts.map(ethers.getSigner)) + return Promise.all(accounts.map(ethers.getSigner)) }