From 7ca8dbe2709f85b389ca2ff18baf40d254bce593 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 21 Nov 2023 12:46:06 +0100 Subject: [PATCH 01/89] 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/89] 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/89] 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 05030cfac884a1ea930971f5d582a5b52d09d26f Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Fri, 24 Nov 2023 15:14:43 +0100 Subject: [PATCH 05/89] Add OpenZeppelin contracts We want to implement the ERC4626 tokenized vault standard based on OpenZeppelin contracts. --- core/package.json | 3 +++ core/yarn.lock | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/core/package.json b/core/package.json index 570eebfbf..a454a7064 100644 --- a/core/package.json +++ b/core/package.json @@ -55,5 +55,8 @@ "ts-node": "^10.9.1", "typechain": "^8.3.2", "typescript": "^5.2.2" + }, + "dependencies": { + "@openzeppelin/contracts": "^5.0.0" } } diff --git a/core/yarn.lock b/core/yarn.lock index cf702692a..43b0c719a 100644 --- a/core/yarn.lock +++ b/core/yarn.lock @@ -874,6 +874,11 @@ table "^6.8.0" undici "^5.14.0" +"@openzeppelin/contracts@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.0.tgz#ee0e4b4564f101a5c4ee398cd4d73c0bd92b289c" + integrity sha512-bv2sdS6LKqVVMLI5+zqnNrNU/CA+6z6CmwFXm/MzmOPBRSO5reEJN7z0Gbzvs0/bv/MZZXNklubpwy3v2+azsw== + "@openzeppelin/defender-admin-client@^1.48.0": version "1.49.0" resolved "https://registry.yarnpkg.com/@openzeppelin/defender-admin-client/-/defender-admin-client-1.49.0.tgz#ed07318ccba10ac8a8a33cf594fc18b7ab5889f9" From c80b77cc14312e521a8cab56455e09a868dff24c Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Fri, 24 Nov 2023 15:23:02 +0100 Subject: [PATCH 06/89] Update solhint rules Based on [the solhint docs](https://github.com/protofire/solhint/blob/develop/docs/rules/security/func-visibility.md) This rule is required to be true for Solidity `>=0.7.0`. --- core/.solhint.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/.solhint.json b/core/.solhint.json index 7afe6405c..b53dba35b 100644 --- a/core/.solhint.json +++ b/core/.solhint.json @@ -1,5 +1,7 @@ { "extends": "thesis", "plugins": [], - "rules": {} + "rules": { + "func-visibility": ["off", { "ignoreConstructors": true }] + } } From 3ebb88b5567e8019c0079ca1a1fd7e2e3f7b17da Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Fri, 24 Nov 2023 15:25:51 +0100 Subject: [PATCH 07/89] Initial implementation of the main Acre contract The Acre contract is ERC4626 tokenized vault standard. Here we inherit from abstract contract `ERC4626` from OpenZeppelin lib. We are going to implement some custom features in follow-up work but the core is based on the ERC4626 standard. --- core/contracts/Acre.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index cb6ed5f33..7f82199a0 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -// Uncomment this line to use console.log -// import "hardhat/console.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; -contract Acre { - // TODO: add your implementation +contract Acre is ERC4626 { + constructor(IERC20 _token) ERC4626(_token) ERC20("Staking BTC", "stBTC") {} } From c81cfd570008b17a75476b07496b85bd3125b4d7 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Fri, 24 Nov 2023 16:17:17 +0100 Subject: [PATCH 08/89] Add initial implementation of the stake function. Stakes a given amount of underlying token and mints shares to a receiver. This function calls `deposit` function from `ERC4626` contract. The function should accept `referrer` input parameter that will be later used for accounting (out of scope of this issue). --- core/contracts/Acre.sol | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 7f82199a0..0d4819ba7 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -5,4 +5,21 @@ import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; contract Acre is ERC4626 { constructor(IERC20 _token) ERC4626(_token) ERC20("Staking BTC", "stBTC") {} + + /// @notice Stakes a given amount of underlying token and mints shares to a + /// receiver. + /// @dev This function calls `deposit` function from `ERC4626` contract. + /// @param assets Approved amount for the transfer and stake. + /// @param receiver The address to which the shares will be minted. + /// @param referrer Data used for refferal program. + /// @return shares Minted shares. + function stake( + uint256 assets, + address receiver, + bytes32 referrer + ) external returns (uint256 shares) { + require(referrer != bytes32(0), "Referrer can not be empty"); + + return super.deposit(assets, receiver); + } } From e754acab851b72d1b0f91acac208a32697c7221a Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 27 Nov 2023 11:29:15 +0100 Subject: [PATCH 09/89] Add `@thesis/solidity-contracts` dependency We want to support `receiveApproval`/`approveAndCall` pattern in the Acre contract. This package provides required interfaces to support this pattern. --- core/package.json | 3 ++- core/yarn.lock | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/core/package.json b/core/package.json index a454a7064..89acd2dd1 100644 --- a/core/package.json +++ b/core/package.json @@ -57,6 +57,7 @@ "typescript": "^5.2.2" }, "dependencies": { - "@openzeppelin/contracts": "^5.0.0" + "@openzeppelin/contracts": "^5.0.0", + "@thesis/solidity-contracts": "github:thesis/solidity-contracts#c315b9d" } } diff --git a/core/yarn.lock b/core/yarn.lock index 43b0c719a..7359ea7cf 100644 --- a/core/yarn.lock +++ b/core/yarn.lock @@ -874,6 +874,11 @@ table "^6.8.0" undici "^5.14.0" +"@openzeppelin/contracts@^4.1.0": + version "4.9.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.3.tgz#00d7a8cf35a475b160b3f0293a6403c511099364" + integrity sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg== + "@openzeppelin/contracts@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.0.tgz#ee0e4b4564f101a5c4ee398cd4d73c0bd92b289c" @@ -1112,6 +1117,12 @@ version "0.0.2" resolved "https://codeload.github.com/thesis/prettier-config/tar.gz/daeaac564056a7885e4366ce12bfde6fd823fc90" +"@thesis/solidity-contracts@github:thesis/solidity-contracts#c315b9d": + version "0.0.1-pre" + resolved "https://codeload.github.com/thesis/solidity-contracts/tar.gz/c315b9d5f0c39253428898474e97552f5e5b9046" + dependencies: + "@openzeppelin/contracts" "^4.1.0" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" From f36c65cd38ff7ba6a2e04944671f4d4523c294aa Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 27 Nov 2023 11:35:38 +0100 Subject: [PATCH 10/89] Add support for receive approval pattern to Acre The tBTC token contract that will be a staking token supports this pattern. To be able to stake in one transaction (instead of 2: approve + stake) we must implement the `RecieveApproval` interface. The token staking contract receives approval to spend tokens and create a stake for a given account. --- core/contracts/Acre.sol | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 0d4819ba7..0c19cac2e 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -2,10 +2,34 @@ pragma solidity ^0.8.21; import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import "@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol"; -contract Acre is ERC4626 { +contract Acre is ERC4626, IReceiveApproval { constructor(IERC20 _token) ERC4626(_token) ERC20("Staking BTC", "stBTC") {} + /// @notice Receives approval of token transfer and stakes the approved + /// amount. + /// @dev Requires that the provided token contract be the same one linked to + /// this contract. + /// @param from The owner of the tokens who approved them to transfer. + /// @param amount Approved amount for the transfer and stake. + /// @param _token Token contract address. + /// @param extraData Extra data for stake. This byte array must have the + /// following values concatenated: + /// - referrer ID (32 bytes) + function receiveApproval( + address from, + uint256 amount, + address _token, + bytes calldata extraData + ) external override { + require(_token == super.asset(), "Unrecognized token"); + // TODO: Decide on the format of the `extradata` variable. + require(extraData.length >= 32, "Corrupted stake data"); + + this.stake(amount, from, bytes32(extraData)); + } + /// @notice Stakes a given amount of underlying token and mints shares to a /// receiver. /// @dev This function calls `deposit` function from `ERC4626` contract. From 14c14e146ab8e882c66036b0a65a20f45ce2e8da Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 27 Nov 2023 11:57:44 +0100 Subject: [PATCH 11/89] Add test token contract Use this contract in unit tests as tBTC token. --- core/contracts/test/TestToken.sol | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 core/contracts/test/TestToken.sol diff --git a/core/contracts/test/TestToken.sol b/core/contracts/test/TestToken.sol new file mode 100644 index 000000000..5a83eabd8 --- /dev/null +++ b/core/contracts/test/TestToken.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@thesis/solidity-contracts/contracts/token/IApproveAndCall.sol"; +import "@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol"; + +contract TestToken is ERC20, IApproveAndCall { + constructor() ERC20("Test Token", "TEST") {} + + function mint(address account, uint256 value) external { + _mint(account, value); + } + + function approveAndCall( + address spender, + uint256 amount, + bytes memory extraData + ) external returns (bool) { + if (approve(spender, amount)) { + IReceiveApproval(spender).receiveApproval( + msg.sender, + amount, + address(this), + extraData + ); + return true; + } + return false; + } +} From 568bc1d71a6e96c35f66601d3dfc63f4d5469bec Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 27 Nov 2023 14:32:52 +0100 Subject: [PATCH 12/89] 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 02c2b3b4495340dffa0867f13538d0dd2bfeb606 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 27 Nov 2023 16:54:03 +0100 Subject: [PATCH 13/89] Add basic unit tests for staking --- core/contracts/Acre.sol | 2 +- core/test/Acre.test.ts | 117 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 3 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 0c19cac2e..104e1a586 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -25,7 +25,7 @@ contract Acre is ERC4626, IReceiveApproval { ) external override { require(_token == super.asset(), "Unrecognized token"); // TODO: Decide on the format of the `extradata` variable. - require(extraData.length >= 32, "Corrupted stake data"); + require(extraData.length == 32, "Corrupted stake data"); this.stake(amount, from, bytes32(extraData)); } diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 97775ba4b..b4e301df0 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -1,5 +1,118 @@ +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" +import { ethers } from "hardhat" +import { expect } from "chai" +import { WeiPerEther } from "ethers" +import type { TestToken, Acre } from "../typechain" + +async function acreFixture() { + const [_, tokenHolder] = await ethers.getSigners() + const Token = await ethers.getContractFactory("TestToken") + const token = await Token.deploy() + + const amountToMint = WeiPerEther * 10000n + + token.mint(tokenHolder, amountToMint) + + const Acre = await ethers.getContractFactory("Acre") + const acre = await Acre.deploy(await token.getAddress()) + + return { acre, token, tokenHolder } +} + describe("Acre", () => { - describe("Add your tests", () => { - it("Should validate something", async () => {}) + let acre: Acre + let token: TestToken + let tokenHolder: HardhatEthersSigner + + beforeEach(async () => { + ;({ acre, token, tokenHolder } = await loadFixture(acreFixture)) + }) + + describe("Staking", () => { + const referrer = ethers.encodeBytes32String("referrer") + + context("when staking via Acre contract", () => { + beforeEach(async () => { + // Infinite approval for staking contract. + await token + .connect(tokenHolder) + .approve(await acre.getAddress(), ethers.MaxUint256) + }) + + it("should stake tokens and receive shares", async () => { + const tokenHolderAddress = tokenHolder.address + const amountToStake = await token.balanceOf(tokenHolderAddress) + + await expect( + acre + .connect(tokenHolder) + .stake(amountToStake, tokenHolderAddress, referrer), + ) + .to.emit(acre, "Deposit") + .withArgs( + // Caller. + tokenHolderAddress, + // Receiver. + tokenHolderAddress, + // Staked tokens. + amountToStake, + // Received shares. In this test case there is only one staker and + // the token vault has not earned anythig yet so received shares are + // equal to staked tokens amount. + amountToStake, + ) + expect(await acre.balanceOf(tokenHolderAddress)).to.be.eq(amountToStake) + expect(await token.balanceOf(tokenHolderAddress)).to.be.eq(0) + }) + + it("should revert if the referrer is zero valu", async () => { + const emptyReferrer = ethers.encodeBytes32String("") + + await expect( + acre + .connect(tokenHolder) + .stake(1, tokenHolder.address, emptyReferrer), + ).to.be.revertedWith("Referrer can not be empty") + }) + }) + + context( + "when staking via staking token using approve and call pattern", + () => { + it("should stake tokens", () => { + // TODO: The `ERC4626.deposit` sets the owner as `msg.sender` under + // the hood. Using the approve and call pattern the `msg.sender` is + // token address, so we are actually trying to send tokens from the + // token contract to the receiver, which is impossible. We need to + // decide if we want to override the `deposit` function to allow + // staking via approve and call pattern. + }) + + it("should revert when called not for linked token", async () => { + const FakeToken = await ethers.getContractFactory("TestToken") + const fakeToken = await FakeToken.deploy() + const acreAddress = await acre.getAddress() + + await expect( + fakeToken.connect(tokenHolder).approveAndCall(acreAddress, 1, "0x"), + ).to.be.revertedWith("Unrecognized token") + }) + + it("should revert when extra data is invalid", async () => { + const acreAddress = await acre.getAddress() + const invalidExtraData = ethers.AbiCoder.defaultAbiCoder().encode( + ["bytes32", "address"], + [referrer, tokenHolder.address], + ) + + await expect( + token + .connect(tokenHolder) + .approveAndCall(acreAddress, 1, invalidExtraData), + ).to.be.revertedWith("Corrupted stake data") + }) + }, + ) }) }) From a9b6c0c3ec5107cbbe83ac46f6049eff9dd77977 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 28 Nov 2023 13:47:13 +0100 Subject: [PATCH 14/89] Remove unnecessary `super` keyword There is no need to add `super` keyword unless you want to override to function and call the original implementation. --- core/contracts/Acre.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 104e1a586..913b10eb1 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -23,7 +23,7 @@ contract Acre is ERC4626, IReceiveApproval { address _token, bytes calldata extraData ) external override { - require(_token == super.asset(), "Unrecognized token"); + require(_token == asset(), "Unrecognized token"); // TODO: Decide on the format of the `extradata` variable. require(extraData.length == 32, "Corrupted stake data"); @@ -44,6 +44,6 @@ contract Acre is ERC4626, IReceiveApproval { ) external returns (uint256 shares) { require(referrer != bytes32(0), "Referrer can not be empty"); - return super.deposit(assets, receiver); + return deposit(assets, receiver); } } From a4f9f8725b76f72c5eedaefa9f9ff0cc60ccb7e2 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 28 Nov 2023 13:50:48 +0100 Subject: [PATCH 15/89] Update the name of the ERC4626 token We decided to use `Acre Staked Bitcoin` as a full name of the ERC4626 token. --- core/contracts/Acre.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 913b10eb1..8006b1684 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -5,7 +5,9 @@ import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; import "@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol"; contract Acre is ERC4626, IReceiveApproval { - constructor(IERC20 _token) ERC4626(_token) ERC20("Staking BTC", "stBTC") {} + constructor( + IERC20 _token + ) ERC4626(_token) ERC20("Acre Staked Bitcoin", "stBTC") {} /// @notice Receives approval of token transfer and stakes the approved /// amount. From 028319fce92aca1023ec770de54cde2e4fc8d8e3 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 28 Nov 2023 14:02:11 +0100 Subject: [PATCH 16/89] Remove unnecessary undersocre for `_token` Use `token` as it's how it is defined in the `IReceiveApproval` interface. It also doesn't collide with another property so the underscore is unnecessary. --- core/contracts/Acre.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 8006b1684..cf890c22e 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -15,17 +15,17 @@ contract Acre is ERC4626, IReceiveApproval { /// this contract. /// @param from The owner of the tokens who approved them to transfer. /// @param amount Approved amount for the transfer and stake. - /// @param _token Token contract address. + /// @param token Token contract address. /// @param extraData Extra data for stake. This byte array must have the /// following values concatenated: /// - referrer ID (32 bytes) function receiveApproval( address from, uint256 amount, - address _token, + address token, bytes calldata extraData ) external override { - require(_token == asset(), "Unrecognized token"); + require(token == asset(), "Unrecognized token"); // TODO: Decide on the format of the `extradata` variable. require(extraData.length == 32, "Corrupted stake data"); From f1a126b0d97eab4b3af115bef81ff16ad4004355 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 28 Nov 2023 14:11:35 +0100 Subject: [PATCH 17/89] Update the `stake` function This function is called by `receiveApproval` internally so we should use `public` instead of `external`. --- core/contracts/Acre.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index cf890c22e..91c2fc714 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -29,7 +29,7 @@ contract Acre is ERC4626, IReceiveApproval { // TODO: Decide on the format of the `extradata` variable. require(extraData.length == 32, "Corrupted stake data"); - this.stake(amount, from, bytes32(extraData)); + stake(amount, from, bytes32(extraData)); } /// @notice Stakes a given amount of underlying token and mints shares to a @@ -43,7 +43,7 @@ contract Acre is ERC4626, IReceiveApproval { uint256 assets, address receiver, bytes32 referrer - ) external returns (uint256 shares) { + ) public returns (uint256 shares) { require(referrer != bytes32(0), "Referrer can not be empty"); return deposit(assets, receiver); From 684d667cc517a9b50d4942d0d36c3a6ed03f4dca Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 28 Nov 2023 14:26:08 +0100 Subject: [PATCH 18/89] Update the Acre unit tests Rename `token` -> `tbtc`. --- core/test/Acre.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index b4e301df0..c82ef81eb 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -22,11 +22,11 @@ async function acreFixture() { describe("Acre", () => { let acre: Acre - let token: TestToken + let tbtc: TestToken let tokenHolder: HardhatEthersSigner beforeEach(async () => { - ;({ acre, token, tokenHolder } = await loadFixture(acreFixture)) + ;({ acre, token: tbtc, tokenHolder } = await loadFixture(acreFixture)) }) describe("Staking", () => { @@ -35,14 +35,14 @@ describe("Acre", () => { context("when staking via Acre contract", () => { beforeEach(async () => { // Infinite approval for staking contract. - await token + await tbtc .connect(tokenHolder) .approve(await acre.getAddress(), ethers.MaxUint256) }) it("should stake tokens and receive shares", async () => { const tokenHolderAddress = tokenHolder.address - const amountToStake = await token.balanceOf(tokenHolderAddress) + const amountToStake = await tbtc.balanceOf(tokenHolderAddress) await expect( acre @@ -63,7 +63,7 @@ describe("Acre", () => { amountToStake, ) expect(await acre.balanceOf(tokenHolderAddress)).to.be.eq(amountToStake) - expect(await token.balanceOf(tokenHolderAddress)).to.be.eq(0) + expect(await tbtc.balanceOf(tokenHolderAddress)).to.be.eq(0) }) it("should revert if the referrer is zero valu", async () => { @@ -107,7 +107,7 @@ describe("Acre", () => { ) await expect( - token + tbtc .connect(tokenHolder) .approveAndCall(acreAddress, 1, invalidExtraData), ).to.be.revertedWith("Corrupted stake data") From 829c3ed5d76ba032699497ddf8348eb7598fe058 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 28 Nov 2023 14:34:55 +0100 Subject: [PATCH 19/89] Rename param in `stake` function `referrer` -> `referral` --- core/contracts/Acre.sol | 8 ++++---- core/test/Acre.test.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 91c2fc714..a25e2252a 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -18,7 +18,7 @@ contract Acre is ERC4626, IReceiveApproval { /// @param token Token contract address. /// @param extraData Extra data for stake. This byte array must have the /// following values concatenated: - /// - referrer ID (32 bytes) + /// - referral (32 bytes) function receiveApproval( address from, uint256 amount, @@ -37,14 +37,14 @@ contract Acre is ERC4626, IReceiveApproval { /// @dev This function calls `deposit` function from `ERC4626` contract. /// @param assets Approved amount for the transfer and stake. /// @param receiver The address to which the shares will be minted. - /// @param referrer Data used for refferal program. + /// @param referral Data used for referral program. /// @return shares Minted shares. function stake( uint256 assets, address receiver, - bytes32 referrer + bytes32 referral ) public returns (uint256 shares) { - require(referrer != bytes32(0), "Referrer can not be empty"); + require(referral != bytes32(0), "Referral can not be empty"); return deposit(assets, receiver); } diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index c82ef81eb..03f40393f 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -30,7 +30,7 @@ describe("Acre", () => { }) describe("Staking", () => { - const referrer = ethers.encodeBytes32String("referrer") + const referral = ethers.encodeBytes32String("referral") context("when staking via Acre contract", () => { beforeEach(async () => { @@ -47,7 +47,7 @@ describe("Acre", () => { await expect( acre .connect(tokenHolder) - .stake(amountToStake, tokenHolderAddress, referrer), + .stake(amountToStake, tokenHolderAddress, referral), ) .to.emit(acre, "Deposit") .withArgs( @@ -103,7 +103,7 @@ describe("Acre", () => { const acreAddress = await acre.getAddress() const invalidExtraData = ethers.AbiCoder.defaultAbiCoder().encode( ["bytes32", "address"], - [referrer, tokenHolder.address], + [referral, tokenHolder.address], ) await expect( From ffaaba693b2a902112625230ff6373254d1e8b7b Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 28 Nov 2023 14:43:34 +0100 Subject: [PATCH 20/89] Update `Acre` unit tests We've updated the revert message in `stake` function when validating the `referral` param. Here we adjust the revert message in unit tests to the current version of contract. --- core/test/Acre.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 03f40393f..0f0d228dc 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -73,7 +73,7 @@ describe("Acre", () => { acre .connect(tokenHolder) .stake(1, tokenHolder.address, emptyReferrer), - ).to.be.revertedWith("Referrer can not be empty") + ).to.be.revertedWith("Referral can not be empty") }) }) From 0abcd5d631c2eaf07535845c1d77a703876d2b94 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 28 Nov 2023 16:47:18 +0100 Subject: [PATCH 21/89] 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 22/89] 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 b8a5b963602d8bb34bf9bcb3c43dc78f9fdeac2a Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 09:10:36 +0100 Subject: [PATCH 23/89] Fix typo `valu` -> `value` --- core/test/Acre.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 0f0d228dc..2425fc671 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -66,7 +66,7 @@ describe("Acre", () => { expect(await tbtc.balanceOf(tokenHolderAddress)).to.be.eq(0) }) - it("should revert if the referrer is zero valu", async () => { + it("should revert if the referrer is zero value", async () => { const emptyReferrer = ethers.encodeBytes32String("") await expect( From 6132a7bb36405402273683f2f9cb6a91ed707a43 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 10:34:20 +0100 Subject: [PATCH 24/89] Add more unit tests for staking flow Unit test stakig flow where there are more than one staker and the vault earns yield from strategies. --- core/test/Acre.test.ts | 189 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 2 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 2425fc671..893a7b71a 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -1,4 +1,8 @@ -import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { + SnapshotRestorer, + loadFixture, + takeSnapshot, +} from "@nomicfoundation/hardhat-toolbox/network-helpers" import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import { ethers } from "hardhat" import { expect } from "chai" @@ -25,21 +29,27 @@ describe("Acre", () => { let tbtc: TestToken let tokenHolder: HardhatEthersSigner - beforeEach(async () => { + before(async () => { ;({ acre, token: tbtc, tokenHolder } = await loadFixture(acreFixture)) }) describe("Staking", () => { const referral = ethers.encodeBytes32String("referral") + let snapshot: SnapshotRestorer context("when staking via Acre contract", () => { beforeEach(async () => { + snapshot = await takeSnapshot() // Infinite approval for staking contract. await tbtc .connect(tokenHolder) .approve(await acre.getAddress(), ethers.MaxUint256) }) + afterEach(async () => { + await snapshot.restore() + }) + it("should stake tokens and receive shares", async () => { const tokenHolderAddress = tokenHolder.address const amountToStake = await tbtc.balanceOf(tokenHolderAddress) @@ -114,5 +124,180 @@ describe("Acre", () => { }) }, ) + + context("when there are two stakers, A and B ", () => { + type Staker = { + signer: HardhatEthersSigner + address: string + amountToStake: bigint + } + let stakerA: Staker + let stakerB: Staker + let afterStakesSnapshot: SnapshotRestorer + let afterSimulatingYieldSnapshot: SnapshotRestorer + + before(async () => { + const [staker1, staker2] = await ethers.getSigners() + const staker1AmountToStake = WeiPerEther * 10000n + const staker2AmountToStake = WeiPerEther * 5000n + // Infinite approval for staking contract. + await tbtc + .connect(staker1) + .approve(await acre.getAddress(), ethers.MaxUint256) + await tbtc + .connect(staker2) + .approve(await acre.getAddress(), ethers.MaxUint256) + + // Mint tokens. + await tbtc.connect(staker1).mint(staker1.address, staker1AmountToStake) + await tbtc.connect(staker2).mint(staker2.address, staker2AmountToStake) + + stakerA = { + signer: staker1, + address: staker1.address, + amountToStake: staker1AmountToStake, + } + stakerB = { + signer: staker2, + address: staker2.address, + amountToStake: staker2AmountToStake, + } + }) + + context( + "when the vault is empty and has not yet earned yield from strategies", + () => { + after(async () => { + afterStakesSnapshot = await takeSnapshot() + }) + + context("when staker A stakes tokens", () => { + it("should stake tokens", async () => { + await expect( + acre + .connect(stakerA.signer) + .stake(stakerA.amountToStake, stakerA.address, referral), + ).to.be.not.reverted + }) + + it("should receive shares equal to a staked amount", async () => { + const shares = await acre.balanceOf(stakerA.address) + + expect(shares).to.eq(stakerA.amountToStake) + }) + }) + + context("when staker B stakes tokens", () => { + it("should stake tokens correctly", async () => { + await expect( + acre + .connect(stakerB.signer) + .stake(stakerB.amountToStake, stakerB.address, referral), + ).to.be.not.reverted + }) + + it("should receive shares equal to a staked amount", async () => { + const shares = await acre.balanceOf(stakerB.address) + + expect(shares).to.eq(stakerB.amountToStake) + }) + }) + }, + ) + + context("when the vault has stakes", () => { + before(async () => { + await afterStakesSnapshot.restore() + }) + + it("the total assets amount should be equal to all staked tokens", async () => { + const totalAssets = await acre.totalAssets() + + expect(totalAssets).to.eq( + stakerA.amountToStake + stakerB.amountToStake, + ) + }) + }) + + context("when vault earns yield", () => { + let stakerASharesBefore: bigint + let stakerBSharesBefore: bigint + let vaultYield: bigint + + before(async () => { + await afterStakesSnapshot.restore() + + stakerASharesBefore = await acre.balanceOf(stakerA.address) + stakerBSharesBefore = await acre.balanceOf(stakerB.address) + vaultYield = WeiPerEther * 10000n + + // Simulating yield returned from strategies. The vault now contains + // more tokens than deposited which causes the exchange rate to + // change. + await tbtc.mint(await acre.getAddress(), vaultYield) + }) + + after(async () => { + afterSimulatingYieldSnapshot = await takeSnapshot() + }) + + it("the vault should hold more asstes", async () => { + expect(await acre.totalAssets()).to.be.eq( + stakerA.amountToStake + stakerB.amountToStake + vaultYield, + ) + }) + + it("the staker's shares should be the same", async () => { + expect(await acre.balanceOf(stakerA.address)).to.be.eq( + stakerASharesBefore, + ) + expect(await acre.balanceOf(stakerB.address)).to.be.eq( + stakerBSharesBefore, + ) + }) + + it("the staker A should be able to redeem more tokens than before", async () => { + const shares = await acre.balanceOf(stakerA.address) + const availableAssetsToRedeem = await acre.previewRedeem(shares) + + expect(availableAssetsToRedeem).to.be.greaterThan( + stakerA.amountToStake, + ) + }) + + it("the staker B should be able to redeem more tokens than before", async () => { + const shares = await acre.balanceOf(stakerB.address) + const availableAssetsToRedeem = await acre.previewRedeem(shares) + + expect(availableAssetsToRedeem).to.be.greaterThan( + stakerB.amountToStake, + ) + }) + }) + + context("when staker A stakes more tokens", () => { + let sharesBefore: bigint + let availableToRedeemBefore: bigint + + before(async () => { + await afterSimulatingYieldSnapshot.restore() + + sharesBefore = await acre.balanceOf(stakerA.address) + availableToRedeemBefore = await acre.previewRedeem(sharesBefore) + + tbtc.mint(stakerA.address, stakerA.amountToStake) + + await acre.stake(stakerA.amountToStake, stakerA.address, referral) + }) + + it("should receive more shares", async () => { + const shares = await acre.balanceOf(stakerA.address) + const availableToRedeem = await acre.previewRedeem(shares) + + expect(shares).to.be.greaterThan(sharesBefore) + expect(availableToRedeem).to.be.greaterThan(availableToRedeemBefore) + }) + }) + }) }) }) From 3a3cb35ab5c7fe8ff0bedec3d16a78bdfb480b95 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 10:44:12 +0100 Subject: [PATCH 25/89] Add `Staked` event Instead of this require we want to emit an event that will include the referral. The off-chain accounting will need it to calculate the fee shares. To not duplicate the `Deposit` event from `ERC4626` the `Staked` event has only context for referral program. We can get other values from the `Deposit` event since it will be emitted in the same transaction. --- core/contracts/Acre.sol | 8 +++++-- core/test/Acre.test.ts | 46 ++++++++++++++++++++++++----------------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index a25e2252a..a7eadb38f 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -5,6 +5,8 @@ import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; import "@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol"; contract Acre is ERC4626, IReceiveApproval { + event Staked(bytes32 indexed referral, uint256 assets, uint256 shares); + constructor( IERC20 _token ) ERC4626(_token) ERC20("Acre Staked Bitcoin", "stBTC") {} @@ -44,8 +46,10 @@ contract Acre is ERC4626, IReceiveApproval { address receiver, bytes32 referral ) public returns (uint256 shares) { - require(referral != bytes32(0), "Referral can not be empty"); + shares = deposit(assets, receiver); + + emit Staked(referral, assets, shares); - return deposit(assets, receiver); + return shares; } } diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 893a7b71a..c85cb713e 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -54,36 +54,44 @@ describe("Acre", () => { const tokenHolderAddress = tokenHolder.address const amountToStake = await tbtc.balanceOf(tokenHolderAddress) - await expect( - acre - .connect(tokenHolder) - .stake(amountToStake, tokenHolderAddress, referral), + const tx = await acre + .connect(tokenHolder) + .stake(amountToStake, tokenHolderAddress, referral) + + const stakedTokens = amountToStake + + // In this test case there is only one staker and + // the token vault has not earned anythig yet so received shares are + // equal to staked tokens amount. + const receivedShares = amountToStake + + expect(tx).to.emit(acre, "Deposit").withArgs( + // Caller. + tokenHolderAddress, + // Receiver. + tokenHolderAddress, + // Staked tokens. + stakedTokens, + // Received shares. + receivedShares, ) - .to.emit(acre, "Deposit") - .withArgs( - // Caller. - tokenHolderAddress, - // Receiver. - tokenHolderAddress, - // Staked tokens. - amountToStake, - // Received shares. In this test case there is only one staker and - // the token vault has not earned anythig yet so received shares are - // equal to staked tokens amount. - amountToStake, - ) + + expect(tx) + .to.emit(acre, "Staked") + .withArgs(referral, stakedTokens, receivedShares) + expect(await acre.balanceOf(tokenHolderAddress)).to.be.eq(amountToStake) expect(await tbtc.balanceOf(tokenHolderAddress)).to.be.eq(0) }) - it("should revert if the referrer is zero value", async () => { + it("should not revert if the referral is zero value", async () => { const emptyReferrer = ethers.encodeBytes32String("") await expect( acre .connect(tokenHolder) .stake(1, tokenHolder.address, emptyReferrer), - ).to.be.revertedWith("Referral can not be empty") + ).to.be.not.reverted }) }) From 6837e74915b3647bbe37758a1159fcd4b1a5e07e Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 12:23:17 +0100 Subject: [PATCH 26/89] Revert "Add support for receive approval pattern to Acre" This reverts commit f36c65cd38ff7ba6a2e04944671f4d4523c294aa. To support receive approval pattern we need to override the `ERC4626.deposit` function to allow pass an `owner`. The OZ implementation sets the owner as `msg.sender` by default under the hood see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol#L178 and we can't change the owner. W/o this update using approve and call pattern to stake tokens, the `msg.sender` is tBTC token address, so we are actually trying to send tokens from the token contract to the receiver, which is impossible. We are going to address it in a separate PR. --- core/contracts/Acre.sol | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index a7eadb38f..fda7ab237 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -2,38 +2,14 @@ pragma solidity ^0.8.21; import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; -import "@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol"; -contract Acre is ERC4626, IReceiveApproval { +contract Acre is ERC4626 { event Staked(bytes32 indexed referral, uint256 assets, uint256 shares); constructor( IERC20 _token ) ERC4626(_token) ERC20("Acre Staked Bitcoin", "stBTC") {} - /// @notice Receives approval of token transfer and stakes the approved - /// amount. - /// @dev Requires that the provided token contract be the same one linked to - /// this contract. - /// @param from The owner of the tokens who approved them to transfer. - /// @param amount Approved amount for the transfer and stake. - /// @param token Token contract address. - /// @param extraData Extra data for stake. This byte array must have the - /// following values concatenated: - /// - referral (32 bytes) - function receiveApproval( - address from, - uint256 amount, - address token, - bytes calldata extraData - ) external override { - require(token == asset(), "Unrecognized token"); - // TODO: Decide on the format of the `extradata` variable. - require(extraData.length == 32, "Corrupted stake data"); - - stake(amount, from, bytes32(extraData)); - } - /// @notice Stakes a given amount of underlying token and mints shares to a /// receiver. /// @dev This function calls `deposit` function from `ERC4626` contract. From c960102f5650f9d3165297ac8c18814d7f1bc759 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 12:29:15 +0100 Subject: [PATCH 27/89] Remove unit tests for receive approval pattern --- core/test/Acre.test.ts | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index c85cb713e..3ba383b1a 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -95,44 +95,6 @@ describe("Acre", () => { }) }) - context( - "when staking via staking token using approve and call pattern", - () => { - it("should stake tokens", () => { - // TODO: The `ERC4626.deposit` sets the owner as `msg.sender` under - // the hood. Using the approve and call pattern the `msg.sender` is - // token address, so we are actually trying to send tokens from the - // token contract to the receiver, which is impossible. We need to - // decide if we want to override the `deposit` function to allow - // staking via approve and call pattern. - }) - - it("should revert when called not for linked token", async () => { - const FakeToken = await ethers.getContractFactory("TestToken") - const fakeToken = await FakeToken.deploy() - const acreAddress = await acre.getAddress() - - await expect( - fakeToken.connect(tokenHolder).approveAndCall(acreAddress, 1, "0x"), - ).to.be.revertedWith("Unrecognized token") - }) - - it("should revert when extra data is invalid", async () => { - const acreAddress = await acre.getAddress() - const invalidExtraData = ethers.AbiCoder.defaultAbiCoder().encode( - ["bytes32", "address"], - [referral, tokenHolder.address], - ) - - await expect( - tbtc - .connect(tokenHolder) - .approveAndCall(acreAddress, 1, invalidExtraData), - ).to.be.revertedWith("Corrupted stake data") - }) - }, - ) - context("when there are two stakers, A and B ", () => { type Staker = { signer: HardhatEthersSigner From 58d2ddd55c7098626e7f9e8205a392f4394a37a5 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 14:41:44 +0100 Subject: [PATCH 28/89] Rename variable in fixture Rename `token` -> `tbtc`. Acre works only with tBTC token - to simplify let's use `tbtc`. --- core/test/Acre.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 3ba383b1a..c7fdf4a02 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -12,16 +12,16 @@ import type { TestToken, Acre } from "../typechain" async function acreFixture() { const [_, tokenHolder] = await ethers.getSigners() const Token = await ethers.getContractFactory("TestToken") - const token = await Token.deploy() + const tbtc = await Token.deploy() const amountToMint = WeiPerEther * 10000n - token.mint(tokenHolder, amountToMint) + tbtc.mint(tokenHolder, amountToMint) const Acre = await ethers.getContractFactory("Acre") - const acre = await Acre.deploy(await token.getAddress()) + const acre = await Acre.deploy(await tbtc.getAddress()) - return { acre, token, tokenHolder } + return { acre, tbtc, tokenHolder } } describe("Acre", () => { @@ -30,7 +30,7 @@ describe("Acre", () => { let tokenHolder: HardhatEthersSigner before(async () => { - ;({ acre, token: tbtc, tokenHolder } = await loadFixture(acreFixture)) + ;({ acre, tbtc, tokenHolder } = await loadFixture(acreFixture)) }) describe("Staking", () => { From 05b20912ef0e9a2cb69e48395c5833aacf61db8b Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 15:05:41 +0100 Subject: [PATCH 29/89] Update unit test for staking To be closer to a real world scenarios we get rid of infinite approval dor staking contract and approve an amount that will be staked - just to make sure Acre can't spend more than it was approved for. Here we also add additional test case where staker tries to stake more than approved. --- core/test/Acre.test.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index c7fdf4a02..79f4b5ede 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -14,7 +14,7 @@ async function acreFixture() { const Token = await ethers.getContractFactory("TestToken") const tbtc = await Token.deploy() - const amountToMint = WeiPerEther * 10000n + const amountToMint = WeiPerEther * 100000n tbtc.mint(tokenHolder, amountToMint) @@ -38,12 +38,14 @@ describe("Acre", () => { let snapshot: SnapshotRestorer context("when staking via Acre contract", () => { + const amountToStake = 1000n * WeiPerEther + beforeEach(async () => { snapshot = await takeSnapshot() - // Infinite approval for staking contract. + await tbtc .connect(tokenHolder) - .approve(await acre.getAddress(), ethers.MaxUint256) + .approve(await acre.getAddress(), amountToStake) }) afterEach(async () => { @@ -52,7 +54,7 @@ describe("Acre", () => { it("should stake tokens and receive shares", async () => { const tokenHolderAddress = tokenHolder.address - const amountToStake = await tbtc.balanceOf(tokenHolderAddress) + const balanceOfBeforeStake = await tbtc.balanceOf(tokenHolderAddress) const tx = await acre .connect(tokenHolder) @@ -81,7 +83,9 @@ describe("Acre", () => { .withArgs(referral, stakedTokens, receivedShares) expect(await acre.balanceOf(tokenHolderAddress)).to.be.eq(amountToStake) - expect(await tbtc.balanceOf(tokenHolderAddress)).to.be.eq(0) + expect(await tbtc.balanceOf(tokenHolderAddress)).to.be.eq( + balanceOfBeforeStake - amountToStake, + ) }) it("should not revert if the referral is zero value", async () => { @@ -90,9 +94,17 @@ describe("Acre", () => { await expect( acre .connect(tokenHolder) - .stake(1, tokenHolder.address, emptyReferrer), + .stake(amountToStake, tokenHolder.address, emptyReferrer), ).to.be.not.reverted }) + + it("should revert if a staker wants to stake more tokens than approved", async () => { + await expect( + acre + .connect(tokenHolder) + .stake(amountToStake + 1n, tokenHolder.address, referral), + ).to.be.reverted + }) }) context("when there are two stakers, A and B ", () => { From a77b1a460be3e1a8e319282ac34306dc0a15ee6a Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 15:15:07 +0100 Subject: [PATCH 30/89] Rename variable `emptyReferrer` -> `emptyReferral` --- core/test/Acre.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 79f4b5ede..6648c81bf 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -89,12 +89,12 @@ describe("Acre", () => { }) it("should not revert if the referral is zero value", async () => { - const emptyReferrer = ethers.encodeBytes32String("") + const emptyReferral = ethers.encodeBytes32String("") await expect( acre .connect(tokenHolder) - .stake(amountToStake, tokenHolder.address, emptyReferrer), + .stake(amountToStake, tokenHolder.address, emptyReferral), ).to.be.not.reverted }) From 4c9eeecd03a46cfef34ea4b0ef7ece6d87dc6222 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 15:18:37 +0100 Subject: [PATCH 31/89] Fix typo --- core/test/Acre.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 6648c81bf..247942fa1 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -223,7 +223,7 @@ describe("Acre", () => { afterSimulatingYieldSnapshot = await takeSnapshot() }) - it("the vault should hold more asstes", async () => { + it("the vault should hold more assets", async () => { expect(await acre.totalAssets()).to.be.eq( stakerA.amountToStake + stakerB.amountToStake + vaultYield, ) From 249fb11d40359f07cc7514acee872f5c603a00dd Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 15:27:27 +0100 Subject: [PATCH 32/89] Rename variable in tests `tokenHolder` -> `staker` --- core/test/Acre.test.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 247942fa1..1a74af049 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -10,27 +10,27 @@ import { WeiPerEther } from "ethers" import type { TestToken, Acre } from "../typechain" async function acreFixture() { - const [_, tokenHolder] = await ethers.getSigners() + const [_, staker] = await ethers.getSigners() const Token = await ethers.getContractFactory("TestToken") const tbtc = await Token.deploy() const amountToMint = WeiPerEther * 100000n - tbtc.mint(tokenHolder, amountToMint) + tbtc.mint(staker, amountToMint) const Acre = await ethers.getContractFactory("Acre") const acre = await Acre.deploy(await tbtc.getAddress()) - return { acre, tbtc, tokenHolder } + return { acre, tbtc, staker } } describe("Acre", () => { let acre: Acre let tbtc: TestToken - let tokenHolder: HardhatEthersSigner + let staker: HardhatEthersSigner before(async () => { - ;({ acre, tbtc, tokenHolder } = await loadFixture(acreFixture)) + ;({ acre, tbtc, staker } = await loadFixture(acreFixture)) }) describe("Staking", () => { @@ -44,7 +44,7 @@ describe("Acre", () => { snapshot = await takeSnapshot() await tbtc - .connect(tokenHolder) + .connect(staker) .approve(await acre.getAddress(), amountToStake) }) @@ -53,12 +53,12 @@ describe("Acre", () => { }) it("should stake tokens and receive shares", async () => { - const tokenHolderAddress = tokenHolder.address - const balanceOfBeforeStake = await tbtc.balanceOf(tokenHolderAddress) + const stakerAddress = staker.address + const balanceOfBeforeStake = await tbtc.balanceOf(stakerAddress) const tx = await acre - .connect(tokenHolder) - .stake(amountToStake, tokenHolderAddress, referral) + .connect(staker) + .stake(amountToStake, stakerAddress, referral) const stakedTokens = amountToStake @@ -69,9 +69,9 @@ describe("Acre", () => { expect(tx).to.emit(acre, "Deposit").withArgs( // Caller. - tokenHolderAddress, + stakerAddress, // Receiver. - tokenHolderAddress, + stakerAddress, // Staked tokens. stakedTokens, // Received shares. @@ -82,8 +82,8 @@ describe("Acre", () => { .to.emit(acre, "Staked") .withArgs(referral, stakedTokens, receivedShares) - expect(await acre.balanceOf(tokenHolderAddress)).to.be.eq(amountToStake) - expect(await tbtc.balanceOf(tokenHolderAddress)).to.be.eq( + expect(await acre.balanceOf(stakerAddress)).to.be.eq(amountToStake) + expect(await tbtc.balanceOf(stakerAddress)).to.be.eq( balanceOfBeforeStake - amountToStake, ) }) @@ -93,16 +93,16 @@ describe("Acre", () => { await expect( acre - .connect(tokenHolder) - .stake(amountToStake, tokenHolder.address, emptyReferral), + .connect(staker) + .stake(amountToStake, staker.address, emptyReferral), ).to.be.not.reverted }) it("should revert if a staker wants to stake more tokens than approved", async () => { await expect( acre - .connect(tokenHolder) - .stake(amountToStake + 1n, tokenHolder.address, referral), + .connect(staker) + .stake(amountToStake + 1n, staker.address, referral), ).to.be.reverted }) }) From 20086c68caf70b0f3ff161392476d69c0daf4819 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 30 Nov 2023 17:07:13 +0100 Subject: [PATCH 33/89] Update staking flow unit tests Be more precise when comparing available amount of assets to redeem. The OZ implementation supports rounding so the available amount to redeem is slightly lower than expected see: 1. Staker A stakes 75 tBTC - receives 75 shares. 2. Staker B stakes 25 tBTC - receives 25 shares. 3. Vault earns 100 tBTC - total assets: 75 + 25 + 100 = 200 tBTC. 4. Available amount to redeem for staker is: - A - 75 * 200 / 100 = 150 -> 149 w/ rounding, - B - 25 * 200 / 100 = 50 -> 49 w/ rounding. --- core/test/Acre.test.ts | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 1a74af049..a3b714169 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -120,8 +120,8 @@ describe("Acre", () => { before(async () => { const [staker1, staker2] = await ethers.getSigners() - const staker1AmountToStake = WeiPerEther * 10000n - const staker2AmountToStake = WeiPerEther * 5000n + const staker1AmountToStake = WeiPerEther * 75n + const staker2AmountToStake = WeiPerEther * 25n // Infinite approval for staking contract. await tbtc .connect(staker1) @@ -154,7 +154,7 @@ describe("Acre", () => { }) context("when staker A stakes tokens", () => { - it("should stake tokens", async () => { + it("should stake tokens correctly", async () => { await expect( acre .connect(stakerA.signer) @@ -205,13 +205,22 @@ describe("Acre", () => { let stakerASharesBefore: bigint let stakerBSharesBefore: bigint let vaultYield: bigint + let expectedTotalAssets: bigint + let expectedTotalSupply: bigint before(async () => { await afterStakesSnapshot.restore() stakerASharesBefore = await acre.balanceOf(stakerA.address) stakerBSharesBefore = await acre.balanceOf(stakerB.address) - vaultYield = WeiPerEther * 10000n + vaultYield = WeiPerEther * 100n + // Staker A shares = 75 + // Staker B shares = 25 + // Total assets = 75(staker A) + 25(staker) + 100(yield) + expectedTotalAssets = + stakerA.amountToStake + stakerB.amountToStake + vaultYield + // Total shares = 75 + 25 = 100 + expectedTotalSupply = stakerA.amountToStake + stakerB.amountToStake // Simulating yield returned from strategies. The vault now contains // more tokens than deposited which causes the exchange rate to @@ -242,18 +251,32 @@ describe("Acre", () => { const shares = await acre.balanceOf(stakerA.address) const availableAssetsToRedeem = await acre.previewRedeem(shares) + // Expected amount w/o rounding: 75 * 200 / 100 = 150 + // Expected amount w/ support for rounding: 149999999999999999999 in + // tBTC token precision. + const expectedAssetsToRedeem = + (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n) + expect(availableAssetsToRedeem).to.be.greaterThan( stakerA.amountToStake, ) + expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem) }) it("the staker B should be able to redeem more tokens than before", async () => { const shares = await acre.balanceOf(stakerB.address) const availableAssetsToRedeem = await acre.previewRedeem(shares) + // Expected amount w/o rounding: 25 * 200 / 100 = 50 + // Expected amount w/ support for rounding: 49999999999999999999 in + // tBTC token precision. + const expectedAssetsToRedeem = + (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n) + expect(availableAssetsToRedeem).to.be.greaterThan( stakerB.amountToStake, ) + expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem) }) }) From bf0da457fbfcebdc3dd6ffe391358af8e72fcb1c Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 1 Dec 2023 15:33:09 +0100 Subject: [PATCH 34/89] Add deploymet script for Acre including tBTC reference Acre contract requires tBTC address as argument. We add `00_resolve_tbtc.ts` script that will ensure tBTC deployment artifact reference is available based on the network. For networks marked with `allowStubs` tag (hardhat local network for running tests) a stub ERC20 contract will be deployed. --- core/.eslintrc | 9 ++++++++- core/deploy/00_resolve_tbtc.ts | 30 ++++++++++++++++++++++++++++++ core/deploy/01_deploy_acre.ts | 19 +++++++++++++++---- core/hardhat.config.ts | 3 +++ core/helpers/address.ts | 6 ++++++ core/tsconfig.json | 2 +- 6 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 core/deploy/00_resolve_tbtc.ts create mode 100644 core/helpers/address.ts diff --git a/core/.eslintrc b/core/.eslintrc index 727b7ad1e..39419600b 100644 --- a/core/.eslintrc +++ b/core/.eslintrc @@ -4,7 +4,14 @@ "rules": { "import/no-extraneous-dependencies": [ "error", - { "devDependencies": ["hardhat.config.ts", "test/**"] } + { + "devDependencies": [ + "hardhat.config.ts", + "deploy/**", + "helpers/**", + "test/**" + ] + } ] } } diff --git a/core/deploy/00_resolve_tbtc.ts b/core/deploy/00_resolve_tbtc.ts new file mode 100644 index 000000000..83f796ceb --- /dev/null +++ b/core/deploy/00_resolve_tbtc.ts @@ -0,0 +1,30 @@ +import type { HardhatRuntimeEnvironment } from "hardhat/types" +import type { DeployFunction } from "hardhat-deploy/types" +import { isNonZeroAddress } from "../helpers/address" + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { getNamedAccounts, deployments } = hre + const { log } = deployments + const { deployer } = await getNamedAccounts() + + const tbtc = await deployments.getOrNull("TBTC") + + if (tbtc && isNonZeroAddress(tbtc.address)) { + log(`using TBTC contract at ${tbtc.address}`) + } else if (!hre.network.tags.allowStubs) { + throw new Error("deployed TBTC contract not found") + } else { + log("deploying TBTC contract stub") + + await deployments.deploy("TBTC", { + contract: "TestToken", // TODO: Rename to TestERC20 + from: deployer, + log: true, + waitConfirmations: 1, + }) + } +} + +export default func + +func.tags = ["TBTC"] diff --git a/core/deploy/01_deploy_acre.ts b/core/deploy/01_deploy_acre.ts index 3266a1986..531fc6c12 100644 --- a/core/deploy/01_deploy_acre.ts +++ b/core/deploy/01_deploy_acre.ts @@ -1,13 +1,24 @@ import type { HardhatRuntimeEnvironment } from "hardhat/types" import type { DeployFunction } from "hardhat-deploy/types" -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { deployments } = hre - const { log } = deployments +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { getNamedAccounts, deployments } = hre + const { deployer } = await getNamedAccounts() - log("Deploying Acre contract") + const tbtc = await deployments.get("TBTC") + + await deployments.deploy("Acre", { + from: deployer, + args: [tbtc.address], + log: true, + waitConfirmations: 1, + }) + + // TODO: Add Etherscan verification + // TODO: Add Tenderly verification } export default func func.tags = ["Acre"] +func.dependencies = ["TBTC"] diff --git a/core/hardhat.config.ts b/core/hardhat.config.ts index 64e14bce6..2390d0d19 100644 --- a/core/hardhat.config.ts +++ b/core/hardhat.config.ts @@ -23,6 +23,9 @@ const config: HardhatUserConfig = { }, networks: { + hardhat: { + tags: ["allowStubs"], + }, sepolia: { url: process.env.CHAIN_API_URL || "", chainId: 11155111, diff --git a/core/helpers/address.ts b/core/helpers/address.ts new file mode 100644 index 000000000..5d5105819 --- /dev/null +++ b/core/helpers/address.ts @@ -0,0 +1,6 @@ +import { ethers } from "ethers" + +// eslint-disable-next-line import/prefer-default-export +export function isNonZeroAddress(address: string): boolean { + return ethers.getAddress(address) !== ethers.ZeroAddress +} diff --git a/core/tsconfig.json b/core/tsconfig.json index 81b5d7d52..3f8c9f270 100644 --- a/core/tsconfig.json +++ b/core/tsconfig.json @@ -9,5 +9,5 @@ "resolveJsonModule": true }, "files": ["./hardhat.config.ts"], - "include": ["./deploy", "./test", "./typechain"] + "include": ["./deploy", "./test", "./typechain", "./helpers/"] } From 3e6f18c4c39ec255ee74e809d73a7d6f8cb536f8 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 1 Dec 2023 15:51:27 +0100 Subject: [PATCH 35/89] Fetch TBTC contract deployment artifact for Sepolia and Mainnet Added a script `./scripts/fetch_external_artifacts.sh` that downloads TBTC token deployment artifact from NPM packages for sepolia and mainnet network and places them under `./external` directory where they will be used for contracts deployment. --- .gitignore | 3 + core/.eslintignore | 1 + core/.prettierignore | 1 + core/external/mainnet/TBTC.json | 762 ++++++++++++++++++++++ core/external/sepolia/TBTC.json | 763 +++++++++++++++++++++++ core/hardhat.config.ts | 9 +- core/scripts/fetch_external_artifacts.sh | 48 ++ 7 files changed, 1586 insertions(+), 1 deletion(-) create mode 100644 core/external/mainnet/TBTC.json create mode 100644 core/external/sepolia/TBTC.json create mode 100755 core/scripts/fetch_external_artifacts.sh diff --git a/.gitignore b/.gitignore index 5eef559d6..668256290 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ pnpm-debug.log* !.vscode/extensions.json .idea .DS_Store + +# Temporary directory +tmp/ diff --git a/core/.eslintignore b/core/.eslintignore index 0f586e83a..567a7eaf1 100644 --- a/core/.eslintignore +++ b/core/.eslintignore @@ -3,4 +3,5 @@ cache/ deployments/ export.json export/ +external/ typechain/ diff --git a/core/.prettierignore b/core/.prettierignore index 0f586e83a..567a7eaf1 100644 --- a/core/.prettierignore +++ b/core/.prettierignore @@ -3,4 +3,5 @@ cache/ deployments/ export.json export/ +external/ typechain/ diff --git a/core/external/mainnet/TBTC.json b/core/external/mainnet/TBTC.json new file mode 100644 index 000000000..dbb8de123 --- /dev/null +++ b/core/external/mainnet/TBTC.json @@ -0,0 +1,762 @@ +{ + "address": "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "approveAndCall", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cachedChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cachedDomainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC721", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "recoverERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0x52538ac60ce0a5672aa77991e9f030ca6eed76db0bf027821ed5170b29971ba2", + "receipt": { + "to": null, + "from": "0x123694886DBf5Ac94DDA07135349534536D14cAf", + "contractAddress": "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "transactionIndex": 44, + "gasUsed": "2909503", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000040000001000000000000000000020000000000000000020000000000000000000800000000000000000000000000000000400000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3586b492c74f630a840cbf65b80646da78860364cbdc7976521aee86449a6759", + "transactionHash": "0x52538ac60ce0a5672aa77991e9f030ca6eed76db0bf027821ed5170b29971ba2", + "logs": [ + { + "transactionIndex": 44, + "blockNumber": 13042356, + "transactionHash": "0x52538ac60ce0a5672aa77991e9f030ca6eed76db0bf027821ed5170b29971ba2", + "address": "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000123694886dbf5ac94dda07135349534536d14caf" + ], + "data": "0x", + "logIndex": 54, + "blockHash": "0x3586b492c74f630a840cbf65b80646da78860364cbdc7976521aee86449a6759" + } + ], + "blockNumber": 13042356, + "cumulativeGasUsed": "5443048", + "status": 1, + "byzantium": true + }, + "args": [], + "solcInputHash": "7cc3eda3cb3ff2522d18b5e7b31ea228", + "metadata": "{\"compiler\":{\"version\":\"0.8.4+commit.c7e474f2\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"name\":\"approveAndCall\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burnFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cachedChainId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cachedDomainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"permit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC721\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"recoverERC721\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"approve(address,uint256)\":{\"details\":\"If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance. Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\",\"returns\":{\"_0\":\"True if the operation succeeded.\"}},\"approveAndCall(address,uint256,bytes)\":{\"details\":\"If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.\",\"returns\":{\"_0\":\"True if both approval and `receiveApproval` calls succeeded.\"}},\"burn(uint256)\":{\"details\":\"Requirements: - the caller must have a balance of at least `amount`.\"},\"burnFrom(address,uint256)\":{\"details\":\"Requirements: - `account` must have a balance of at least `amount`, - the caller must have allowance for `account`'s tokens of at least `amount`.\"},\"mint(address,uint256)\":{\"details\":\"Requirements: - `recipient` cannot be the zero address.\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"details\":\"The deadline argument can be set to `type(uint256).max to create permits that effectively never expire. If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.\"},\"transfer(address,uint256)\":{\"details\":\"Requirements: - `recipient` cannot be the zero address, - the caller must have a balance of at least `amount`.\",\"returns\":{\"_0\":\"True if the operation succeeded, reverts otherwise.\"}},\"transferFrom(address,address,uint256)\":{\"details\":\"Requirements: - `sender` and `recipient` cannot be the zero address, - `sender` must have a balance of at least `amount`, - the caller must have allowance for `sender`'s tokens of at least `amount`.\",\"returns\":{\"_0\":\"True if the operation succeeded, reverts otherwise.\"}},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"DOMAIN_SEPARATOR()\":{\"notice\":\"Returns hash of EIP712 Domain struct with the token name as a signing domain and token contract as a verifying contract. Used to construct EIP2612 signature provided to `permit` function.\"},\"PERMIT_TYPEHASH()\":{\"notice\":\"Returns EIP2612 Permit message hash. Used to construct EIP2612 signature provided to `permit` function.\"},\"allowance(address,address)\":{\"notice\":\"The remaining number of tokens that spender will be allowed to spend on behalf of owner through `transferFrom` and `burnFrom`. This is zero by default.\"},\"approve(address,uint256)\":{\"notice\":\"Sets `amount` as the allowance of `spender` over the caller's tokens.\"},\"approveAndCall(address,uint256,bytes)\":{\"notice\":\"Calls `receiveApproval` function on spender previously approving the spender to withdraw from the caller multiple times, up to the `amount` amount. If this function is called again, it overwrites the current allowance with `amount`. Reverts if the approval reverted or if `receiveApproval` call on the spender reverted.\"},\"balanceOf(address)\":{\"notice\":\"The amount of tokens owned by the given account.\"},\"burn(uint256)\":{\"notice\":\"Destroys `amount` tokens from the caller.\"},\"burnFrom(address,uint256)\":{\"notice\":\"Destroys `amount` of tokens from `account` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`.\"},\"decimals()\":{\"notice\":\"The decimals places of the token.\"},\"mint(address,uint256)\":{\"notice\":\"Creates `amount` tokens and assigns them to `account`, increasing the total supply.\"},\"name()\":{\"notice\":\"The name of the token.\"},\"nonces(address)\":{\"notice\":\"Returns the current nonce for EIP2612 permission for the provided token owner for a replay protection. Used to construct EIP2612 signature provided to `permit` function.\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"notice\":\"EIP2612 approval made with secp256k1 signature. Users can authorize a transfer of their tokens with a signature conforming EIP712 standard, rather than an on-chain transaction from their address. Anyone can submit this signature on the user's behalf by calling the permit function, paying gas fees, and possibly performing other actions in the same transaction.\"},\"symbol()\":{\"notice\":\"The symbol of the token.\"},\"totalSupply()\":{\"notice\":\"The amount of tokens in existence.\"},\"transfer(address,uint256)\":{\"notice\":\"Moves `amount` tokens from the caller's account to `recipient`.\"},\"transferFrom(address,address,uint256)\":{\"notice\":\"Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/token/TBTC.sol\":\"TBTC\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor() {\\n _setOwner(_msgSender());\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n _;\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions anymore. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby removing any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _setOwner(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _setOwner(newOwner);\\n }\\n\\n function _setOwner(address newOwner) private {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0x6bb804a310218875e89d12c053e94a13a4607cdf7cc2052f3e52bd32a0dc50a1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0x027b891937d20ccf213fdb9c31531574256de774bda99d3a70ecef6e1913ed2a\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x83fe24f5c04a56091e50f4a345ff504c8bff658a76d4c43b16878c8f940c53b2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0x02348b2e4b9f3200c7e3907c5c2661643a6d8520e9f79939fbb9b4005a54894d\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/IERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../../utils/introspection/IERC165.sol\\\";\\n\\n/**\\n * @dev Required interface of an ERC721 compliant contract.\\n */\\ninterface IERC721 is IERC165 {\\n /**\\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\\n */\\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\\n */\\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\\n\\n /**\\n * @dev Returns the number of tokens in ``owner``'s account.\\n */\\n function balanceOf(address owner) external view returns (uint256 balance);\\n\\n /**\\n * @dev Returns the owner of the `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function ownerOf(uint256 tokenId) external view returns (address owner);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Transfers `tokenId` token from `from` to `to`.\\n *\\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\\n * The approval is cleared when the token is transferred.\\n *\\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\\n *\\n * Requirements:\\n *\\n * - The caller must own the token or be an approved operator.\\n * - `tokenId` must exist.\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Returns the account approved for `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function getApproved(uint256 tokenId) external view returns (address operator);\\n\\n /**\\n * @dev Approve or remove `operator` as an operator for the caller.\\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\\n *\\n * Requirements:\\n *\\n * - The `operator` cannot be the caller.\\n *\\n * Emits an {ApprovalForAll} event.\\n */\\n function setApprovalForAll(address operator, bool _approved) external;\\n\\n /**\\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\\n *\\n * See {setApprovalForAll}\\n */\\n function isApprovedForAll(address owner, address operator) external view returns (bool);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes calldata data\\n ) external;\\n}\\n\",\"keccak256\":\"0xf101e8720213560fab41104d53b3cc7ba0456ef3a98455aa7f022391783144a0\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize, which returns 0 for contracts in\\n // construction, since the code is only stored at the end of the\\n // constructor execution.\\n\\n uint256 size;\\n assembly {\\n size := extcodesize(account)\\n }\\n return size > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return _verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return _verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return _verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n function _verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) private pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3b4820cac4f127869f6eb496c1d74fa6ac86ed24071e0f94742e6aef20e7252c\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\n/*\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0x95098bd1d9c8dec4d80d3dedb88a0d949fa0d740ee99f2aa466bc308216ca6d5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xa28007762d9da9db878dd421960c8cb9a10471f47ab5c1b3309bfe48e9e79ff4\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IERC20WithPermit.sol\\\";\\nimport \\\"./IReceiveApproval.sol\\\";\\n\\n/// @title ERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ncontract ERC20WithPermit is IERC20WithPermit, Ownable {\\n /// @notice The amount of tokens owned by the given account.\\n mapping(address => uint256) public override balanceOf;\\n\\n /// @notice The remaining number of tokens that spender will be\\n /// allowed to spend on behalf of owner through `transferFrom` and\\n /// `burnFrom`. This is zero by default.\\n mapping(address => mapping(address => uint256)) public override allowance;\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n mapping(address => uint256) public override nonces;\\n\\n uint256 public immutable cachedChainId;\\n bytes32 public immutable cachedDomainSeparator;\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n bytes32 public constant override PERMIT_TYPEHASH =\\n keccak256(\\n \\\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\\\"\\n );\\n\\n /// @notice The amount of tokens in existence.\\n uint256 public override totalSupply;\\n\\n /// @notice The name of the token.\\n string public override name;\\n\\n /// @notice The symbol of the token.\\n string public override symbol;\\n\\n /// @notice The decimals places of the token.\\n uint8 public constant override decimals = 18;\\n\\n constructor(string memory _name, string memory _symbol) {\\n name = _name;\\n symbol = _symbol;\\n\\n cachedChainId = block.chainid;\\n cachedDomainSeparator = buildDomainSeparator();\\n }\\n\\n /// @notice Moves `amount` tokens from the caller's account to `recipient`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - the caller must have a balance of at least `amount`.\\n function transfer(address recipient, uint256 amount)\\n external\\n override\\n returns (bool)\\n {\\n _transfer(msg.sender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice Moves `amount` tokens from `sender` to `recipient` using the\\n /// allowance mechanism. `amount` is then deducted from the caller's\\n /// allowance unless the allowance was made for `type(uint256).max`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `sender` and `recipient` cannot be the zero address,\\n /// - `sender` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `sender`'s tokens of at least\\n /// `amount`.\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external override returns (bool) {\\n uint256 currentAllowance = allowance[sender][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Transfer amount exceeds allowance\\\"\\n );\\n _approve(sender, msg.sender, currentAllowance - amount);\\n }\\n _transfer(sender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire. If the `amount` is set\\n /// to `type(uint256).max` then `transferFrom` and `burnFrom` will\\n /// not reduce an allowance.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external override {\\n /* solhint-disable-next-line not-rely-on-time */\\n require(deadline >= block.timestamp, \\\"Permission expired\\\");\\n\\n // Validate `s` and `v` values for a malleability concern described in EIP2.\\n // Only signatures with `s` value in the lower half of the secp256k1\\n // curve's order and `v` value of 27 or 28 are considered valid.\\n require(\\n uint256(s) <=\\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\\n \\\"Invalid signature 's' value\\\"\\n );\\n require(v == 27 || v == 28, \\\"Invalid signature 'v' value\\\");\\n\\n bytes32 digest = keccak256(\\n abi.encodePacked(\\n \\\"\\\\x19\\\\x01\\\",\\n DOMAIN_SEPARATOR(),\\n keccak256(\\n abi.encode(\\n PERMIT_TYPEHASH,\\n owner,\\n spender,\\n amount,\\n nonces[owner]++,\\n deadline\\n )\\n )\\n )\\n );\\n address recoveredAddress = ecrecover(digest, v, r, s);\\n require(\\n recoveredAddress != address(0) && recoveredAddress == owner,\\n \\\"Invalid signature\\\"\\n );\\n _approve(owner, spender, amount);\\n }\\n\\n /// @notice Creates `amount` tokens and assigns them to `account`,\\n /// increasing the total supply.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address.\\n function mint(address recipient, uint256 amount) external onlyOwner {\\n require(recipient != address(0), \\\"Mint to the zero address\\\");\\n\\n beforeTokenTransfer(address(0), recipient, amount);\\n\\n totalSupply += amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(address(0), recipient, amount);\\n }\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n /// @dev Requirements:\\n /// - the caller must have a balance of at least `amount`.\\n function burn(uint256 amount) external override {\\n _burn(msg.sender, amount);\\n }\\n\\n /// @notice Destroys `amount` of tokens from `account` using the allowance\\n /// mechanism. `amount` is then deducted from the caller's allowance\\n /// unless the allowance was made for `type(uint256).max`.\\n /// @dev Requirements:\\n /// - `account` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `account`'s tokens of at least\\n /// `amount`.\\n function burnFrom(address account, uint256 amount) external override {\\n uint256 currentAllowance = allowance[account][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Burn amount exceeds allowance\\\"\\n );\\n _approve(account, msg.sender, currentAllowance - amount);\\n }\\n _burn(account, amount);\\n }\\n\\n /// @notice Calls `receiveApproval` function on spender previously approving\\n /// the spender to withdraw from the caller multiple times, up to\\n /// the `amount` amount. If this function is called again, it\\n /// overwrites the current allowance with `amount`. Reverts if the\\n /// approval reverted or if `receiveApproval` call on the spender\\n /// reverted.\\n /// @return True if both approval and `receiveApproval` calls succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external override returns (bool) {\\n if (approve(spender, amount)) {\\n IReceiveApproval(spender).receiveApproval(\\n msg.sender,\\n amount,\\n address(this),\\n extraData\\n );\\n return true;\\n }\\n return false;\\n }\\n\\n /// @notice Sets `amount` as the allowance of `spender` over the caller's\\n /// tokens.\\n /// @return True if the operation succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n /// Beware that changing an allowance with this method brings the risk\\n /// that someone may use both the old and the new allowance by\\n /// unfortunate transaction ordering. One possible solution to mitigate\\n /// this race condition is to first reduce the spender's allowance to 0\\n /// and set the desired value afterwards:\\n /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n function approve(address spender, uint256 amount)\\n public\\n override\\n returns (bool)\\n {\\n _approve(msg.sender, spender, amount);\\n return true;\\n }\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() public view override returns (bytes32) {\\n // As explained in EIP-2612, if the DOMAIN_SEPARATOR contains the\\n // chainId and is defined at contract deployment instead of\\n // reconstructed for every signature, there is a risk of possible replay\\n // attacks between chains in the event of a future chain split.\\n // To address this issue, we check the cached chain ID against the\\n // current one and in case they are different, we build domain separator\\n // from scratch.\\n if (block.chainid == cachedChainId) {\\n return cachedDomainSeparator;\\n } else {\\n return buildDomainSeparator();\\n }\\n }\\n\\n /// @dev Hook that is called before any transfer of tokens. This includes\\n /// minting and burning.\\n ///\\n /// Calling conditions:\\n /// - when `from` and `to` are both non-zero, `amount` of `from`'s tokens\\n /// will be to transferred to `to`.\\n /// - when `from` is zero, `amount` tokens will be minted for `to`.\\n /// - when `to` is zero, `amount` of ``from``'s tokens will be burned.\\n /// - `from` and `to` are never both zero.\\n // slither-disable-next-line dead-code\\n function beforeTokenTransfer(\\n address from,\\n address to,\\n uint256 amount\\n ) internal virtual {}\\n\\n function _burn(address account, uint256 amount) internal {\\n uint256 currentBalance = balanceOf[account];\\n require(currentBalance >= amount, \\\"Burn amount exceeds balance\\\");\\n\\n beforeTokenTransfer(account, address(0), amount);\\n\\n balanceOf[account] = currentBalance - amount;\\n totalSupply -= amount;\\n emit Transfer(account, address(0), amount);\\n }\\n\\n function _transfer(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) private {\\n require(sender != address(0), \\\"Transfer from the zero address\\\");\\n require(recipient != address(0), \\\"Transfer to the zero address\\\");\\n require(recipient != address(this), \\\"Transfer to the token address\\\");\\n\\n beforeTokenTransfer(sender, recipient, amount);\\n\\n uint256 senderBalance = balanceOf[sender];\\n require(senderBalance >= amount, \\\"Transfer amount exceeds balance\\\");\\n balanceOf[sender] = senderBalance - amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(sender, recipient, amount);\\n }\\n\\n function _approve(\\n address owner,\\n address spender,\\n uint256 amount\\n ) private {\\n require(owner != address(0), \\\"Approve from the zero address\\\");\\n require(spender != address(0), \\\"Approve to the zero address\\\");\\n allowance[owner][spender] = amount;\\n emit Approval(owner, spender, amount);\\n }\\n\\n function buildDomainSeparator() private view returns (bytes32) {\\n return\\n keccak256(\\n abi.encode(\\n keccak256(\\n \\\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\\\"\\n ),\\n keccak256(bytes(name)),\\n keccak256(bytes(\\\"1\\\")),\\n block.chainid,\\n address(this)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x03d73b1cb4915aee716e6245c0bb79335bbe0510cf16a08b2228151eb35d3183\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IApproveAndCall.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by tokens supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IApproveAndCall {\\n /// @notice Executes `receiveApproval` function on spender as specified in\\n /// `IReceiveApproval` interface. Approves spender to withdraw from\\n /// the caller multiple times, up to the `amount`. If this\\n /// function is called again, it overwrites the current allowance\\n /// with `amount`. Reverts if the approval reverted or if\\n /// `receiveApproval` call on the spender reverted.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x393d18ef81a57dcc96fff4c340cc2945deaebb37b9796c322cf2bc96872c3df8\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\n\\nimport \\\"./IApproveAndCall.sol\\\";\\n\\n/// @title IERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ninterface IERC20WithPermit is IERC20, IERC20Metadata, IApproveAndCall {\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n function burn(uint256 amount) external;\\n\\n /// @notice Destroys `amount` of tokens from `account`, deducting the amount\\n /// from caller's allowance.\\n function burnFrom(address account, uint256 amount) external;\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n function nonces(address owner) external view returns (uint256);\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function PERMIT_TYPEHASH() external pure returns (bytes32);\\n}\\n\",\"keccak256\":\"0xe6ecbd8d29688969b11bedb4ce1d076be09a69c97d31ae89c913734ece5d61fc\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by contracts supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IReceiveApproval {\\n /// @notice Receives approval to spend tokens. Called as a result of\\n /// `approveAndCall` call on the token.\\n function receiveApproval(\\n address from,\\n uint256 amount,\\n address token,\\n bytes calldata extraData\\n ) external;\\n}\\n\",\"keccak256\":\"0x6a30d83ad230548b1e7839737affc8489a035314209de14b89dbef7fb0f66395\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC721/IERC721.sol\\\";\\n\\n/// @title MisfundRecovery\\n/// @notice Allows the owner of the token contract extending MisfundRecovery\\n/// to recover any ERC20 and ERC721 sent mistakenly to the token\\n/// contract address.\\ncontract MisfundRecovery is Ownable {\\n using SafeERC20 for IERC20;\\n\\n function recoverERC20(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n token.safeTransfer(recipient, amount);\\n }\\n\\n function recoverERC721(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n token.safeTransferFrom(address(this), recipient, tokenId, data);\\n }\\n}\\n\",\"keccak256\":\"0xbbfea02bf20e2a6df5a497bbc05c7540a3b7c7dfb8b1feeaffef7f6b8ba65d65\",\"license\":\"MIT\"},\"contracts/token/TBTC.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.8.4;\\n\\nimport \\\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\\\";\\nimport \\\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\\\";\\n\\ncontract TBTC is ERC20WithPermit, MisfundRecovery {\\n constructor() ERC20WithPermit(\\\"tBTC v2\\\", \\\"tBTC\\\") {}\\n}\\n\",\"keccak256\":\"0xc139767d54b9d525f99cee906d2f6ee5b0c049fa97606e39ed88ccc4858c1ad3\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60c06040523480156200001157600080fd5b506040518060400160405280600781526020017f74425443207632000000000000000000000000000000000000000000000000008152506040518060400160405280600481526020017f74425443000000000000000000000000000000000000000000000000000000008152506200009e62000092620000f760201b60201c565b620000ff60201b60201c565b8160059080519060200190620000b69291906200026f565b508060069080519060200190620000cf9291906200026f565b504660808181525050620000e8620001c360201b60201c565b60a08181525050505062000520565b600033905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6005604051620001f79190620003dd565b60405180910390206040518060400160405280600181526020017f310000000000000000000000000000000000000000000000000000000000000081525080519060200120463060405160200162000254959493929190620003f6565b60405160208183030381529060405280519060200120905090565b8280546200027d90620004bb565b90600052602060002090601f016020900481019282620002a15760008555620002ed565b82601f10620002bc57805160ff1916838001178555620002ed565b82800160010185558215620002ed579182015b82811115620002ec578251825591602001919060010190620002cf565b5b509050620002fc919062000300565b5090565b5b808211156200031b57600081600090555060010162000301565b5090565b6200032a8162000473565b82525050565b6200033b8162000487565b82525050565b600081546200035081620004bb565b6200035c818662000468565b945060018216600081146200037a57600181146200038c57620003c3565b60ff19831686528186019350620003c3565b620003978562000453565b60005b83811015620003bb578154818901526001820191506020810190506200039a565b838801955050505b50505092915050565b620003d781620004b1565b82525050565b6000620003eb828462000341565b915081905092915050565b600060a0820190506200040d600083018862000330565b6200041c602083018762000330565b6200042b604083018662000330565b6200043a6060830185620003cc565b6200044960808301846200031f565b9695505050505050565b60008190508160005260206000209050919050565b600081905092915050565b6000620004808262000491565b9050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006002820490506001821680620004d457607f821691505b60208210811415620004eb57620004ea620004f1565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60805160a05161324d620005546000396000818161074f0152610c1e0152600081816107270152610a03015261324d6000f3fe608060405234801561001057600080fd5b50600436106101585760003560e01c8063771da5c5116100c3578063b4f94b2e1161007c578063b4f94b2e146103b7578063cae9ca51146103d5578063d505accf14610405578063dd62ed3e14610421578063f2fde38b14610451578063fc4e51f61461046d57610158565b8063771da5c5146102e157806379cc6790146102ff5780637ecebe001461031b5780638da5cb5b1461034b57806395d89b4114610369578063a9059cbb1461038757610158565b8063313ce56711610115578063313ce567146102335780633644e5151461025157806340c10f191461026f57806342966c681461028b57806370a08231146102a7578063715018a6146102d757610158565b806306fdde031461015d578063095ea7b31461017b5780631171bda9146101ab57806318160ddd146101c757806323b872dd146101e557806330adf81f14610215575b600080fd5b610165610489565b6040516101729190612796565b60405180910390f35b61019560048036038101906101909190611ef3565b610517565b6040516101a29190612667565b60405180910390f35b6101c560048036038101906101c09190611fbf565b61052e565b005b6101cf6105da565b6040516101dc9190612a18565b60405180910390f35b6101ff60048036038101906101fa9190611e06565b6105e0565b60405161020c9190612667565b60405180910390f35b61021d6106fa565b60405161022a9190612682565b60405180910390f35b61023b61071e565b6040516102489190612a33565b60405180910390f35b610259610723565b6040516102669190612682565b60405180910390f35b61028960048036038101906102849190611ef3565b610783565b005b6102a560048036038101906102a0919061208e565b610954565b005b6102c160048036038101906102bc9190611da1565b610961565b6040516102ce9190612a18565b60405180910390f35b6102df610979565b005b6102e9610a01565b6040516102f69190612a18565b60405180910390f35b61031960048036038101906103149190611ef3565b610a25565b005b61033560048036038101906103309190611da1565b610b36565b6040516103429190612a18565b60405180910390f35b610353610b4e565b6040516103609190612589565b60405180910390f35b610371610b77565b60405161037e9190612796565b60405180910390f35b6103a1600480360381019061039c9190611ef3565b610c05565b6040516103ae9190612667565b60405180910390f35b6103bf610c1c565b6040516103cc9190612682565b60405180910390f35b6103ef60048036038101906103ea9190611f2f565b610c40565b6040516103fc9190612667565b60405180910390f35b61041f600480360381019061041a9190611e55565b610cd7565b005b61043b60048036038101906104369190611dca565b610fbb565b6040516104489190612a18565b60405180910390f35b61046b60048036038101906104669190611da1565b610fe0565b005b6104876004803603810190610482919061200e565b6110d8565b005b6005805461049690612c56565b80601f01602080910402602001604051908101604052809291908181526020018280546104c290612c56565b801561050f5780601f106104e45761010080835404028352916020019161050f565b820191906000526020600020905b8154815290600101906020018083116104f257829003601f168201915b505050505081565b60006105243384846111ce565b6001905092915050565b610536611399565b73ffffffffffffffffffffffffffffffffffffffff16610554610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146105aa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105a190612918565b60405180910390fd5b6105d582828573ffffffffffffffffffffffffffffffffffffffff166113a19092919063ffffffff16565b505050565b60045481565b600080600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146106e357828110156106cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c3906127f8565b60405180910390fd5b6106e2853385846106dd9190612b5d565b6111ce565b5b6106ee858585611427565b60019150509392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60007f0000000000000000000000000000000000000000000000000000000000000000461415610775577f00000000000000000000000000000000000000000000000000000000000000009050610780565b61077d611718565b90505b90565b61078b611399565b73ffffffffffffffffffffffffffffffffffffffff166107a9610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146107ff576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107f690612918565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561086f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161086690612998565b60405180910390fd5b61087b600083836117c0565b806004600082825461088d9190612b07565b9250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546108e39190612b07565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516109489190612a18565b60405180910390a35050565b61095e33826117c5565b50565b60016020528060005260406000206000915090505481565b610981611399565b73ffffffffffffffffffffffffffffffffffffffff1661099f610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146109f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109ec90612918565b60405180910390fd5b6109ff600061192b565b565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610b275781811015610b10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b07906128b8565b60405180910390fd5b610b2683338484610b219190612b5d565b6111ce565b5b610b3183836117c5565b505050565b60036020528060005260406000206000915090505481565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60068054610b8490612c56565b80601f0160208091040260200160405190810160405280929190818152602001828054610bb090612c56565b8015610bfd5780601f10610bd257610100808354040283529160200191610bfd565b820191906000526020600020905b815481529060010190602001808311610be057829003601f168201915b505050505081565b6000610c12338484611427565b6001905092915050565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000610c4c8484610517565b15610ccb578373ffffffffffffffffffffffffffffffffffffffff16638f4ffcb1338530866040518563ffffffff1660e01b8152600401610c90949392919061261b565b600060405180830381600087803b158015610caa57600080fd5b505af1158015610cbe573d6000803e3d6000fd5b5050505060019050610cd0565b600090505b9392505050565b42841015610d1a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d1190612938565b60405180910390fd5b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08160001c1115610d80576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d7790612978565b60405180910390fd5b601b8360ff161480610d955750601c8360ff16145b610dd4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dcb90612818565b60405180910390fd5b6000610dde610723565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9898989600360008e73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000815480929190610e5290612cb9565b919050558a604051602001610e6c9695949392919061269d565b60405160208183030381529060405280519060200120604051602001610e93929190612552565b604051602081830303815290604052805190602001209050600060018286868660405160008152602001604052604051610ed09493929190612751565b6020604051602081039080840390855afa158015610ef2573d6000803e3d6000fd5b505050602060405103519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015610f6657508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b610fa5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f9c90612858565b60405180910390fd5b610fb08989896111ce565b505050505050505050565b6002602052816000526040600020602052806000526040600020600091509150505481565b610fe8611399565b73ffffffffffffffffffffffffffffffffffffffff16611006610b4e565b73ffffffffffffffffffffffffffffffffffffffff161461105c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161105390612918565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156110cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110c3906127d8565b60405180910390fd5b6110d58161192b565b50565b6110e0611399565b73ffffffffffffffffffffffffffffffffffffffff166110fe610b4e565b73ffffffffffffffffffffffffffffffffffffffff1614611154576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161114b90612918565b60405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff1663b88d4fde30868686866040518663ffffffff1660e01b81526004016111959594939291906125a4565b600060405180830381600087803b1580156111af57600080fd5b505af11580156111c3573d6000803e3d6000fd5b505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561123e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161123590612898565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156112ae576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112a5906127b8565b60405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161138c9190612a18565b60405180910390a3505050565b600033905090565b6114228363a9059cbb60e01b84846040516024016113c09291906125f2565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506119ef565b505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611497576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161148e906129f8565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611507576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114fe906129d8565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611576576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161156d90612838565b60405180910390fd5b6115818383836117c0565b6000600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611608576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115ff906128d8565b60405180910390fd5b81816116149190612b5d565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546116a69190612b07565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161170a9190612a18565b60405180910390a350505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f600560405161174a919061253b565b60405180910390206040518060400160405280600181526020017f31000000000000000000000000000000000000000000000000000000000000008152508051906020012046306040516020016117a59594939291906126fe565b60405160208183030381529060405280519060200120905090565b505050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561184c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611843906128f8565b60405180910390fd5b611858836000846117c0565b81816118649190612b5d565b600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600460008282546118b99190612b5d565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161191e9190612a18565b60405180910390a3505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b6000611a51826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff16611ab69092919063ffffffff16565b9050600081511115611ab15780806020019051810190611a719190611f96565b611ab0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611aa7906129b8565b60405180910390fd5b5b505050565b6060611ac58484600085611ace565b90509392505050565b606082471015611b13576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b0a90612878565b60405180910390fd5b611b1c85611be2565b611b5b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5290612958565b60405180910390fd5b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051611b849190612524565b60006040518083038185875af1925050503d8060008114611bc1576040519150601f19603f3d011682016040523d82523d6000602084013e611bc6565b606091505b5091509150611bd6828286611bf5565b92505050949350505050565b600080823b905060008111915050919050565b60608315611c0557829050611c55565b600083511115611c185782518084602001fd5b816040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c4c9190612796565b60405180910390fd5b9392505050565b6000611c6f611c6a84612a73565b612a4e565b905082815260208101848484011115611c8757600080fd5b611c92848285612c14565b509392505050565b600081359050611ca981613176565b92915050565b600081519050611cbe8161318d565b92915050565b600081359050611cd3816131a4565b92915050565b60008083601f840112611ceb57600080fd5b8235905067ffffffffffffffff811115611d0457600080fd5b602083019150836001820283011115611d1c57600080fd5b9250929050565b600082601f830112611d3457600080fd5b8135611d44848260208601611c5c565b91505092915050565b600081359050611d5c816131bb565b92915050565b600081359050611d71816131d2565b92915050565b600081359050611d86816131e9565b92915050565b600081359050611d9b81613200565b92915050565b600060208284031215611db357600080fd5b6000611dc184828501611c9a565b91505092915050565b60008060408385031215611ddd57600080fd5b6000611deb85828601611c9a565b9250506020611dfc85828601611c9a565b9150509250929050565b600080600060608486031215611e1b57600080fd5b6000611e2986828701611c9a565b9350506020611e3a86828701611c9a565b9250506040611e4b86828701611d77565b9150509250925092565b600080600080600080600060e0888a031215611e7057600080fd5b6000611e7e8a828b01611c9a565b9750506020611e8f8a828b01611c9a565b9650506040611ea08a828b01611d77565b9550506060611eb18a828b01611d77565b9450506080611ec28a828b01611d8c565b93505060a0611ed38a828b01611cc4565b92505060c0611ee48a828b01611cc4565b91505092959891949750929550565b60008060408385031215611f0657600080fd5b6000611f1485828601611c9a565b9250506020611f2585828601611d77565b9150509250929050565b600080600060608486031215611f4457600080fd5b6000611f5286828701611c9a565b9350506020611f6386828701611d77565b925050604084013567ffffffffffffffff811115611f8057600080fd5b611f8c86828701611d23565b9150509250925092565b600060208284031215611fa857600080fd5b6000611fb684828501611caf565b91505092915050565b600080600060608486031215611fd457600080fd5b6000611fe286828701611d4d565b9350506020611ff386828701611c9a565b925050604061200486828701611d77565b9150509250925092565b60008060008060006080868803121561202657600080fd5b600061203488828901611d62565b955050602061204588828901611c9a565b945050604061205688828901611d77565b935050606086013567ffffffffffffffff81111561207357600080fd5b61207f88828901611cd9565b92509250509295509295909350565b6000602082840312156120a057600080fd5b60006120ae84828501611d77565b91505092915050565b6120c081612b91565b82525050565b6120cf81612ba3565b82525050565b6120de81612baf565b82525050565b6120f56120f082612baf565b612d02565b82525050565b60006121078385612acf565b9350612114838584612c14565b61211d83612d99565b840190509392505050565b600061213382612ab9565b61213d8185612acf565b935061214d818560208601612c23565b61215681612d99565b840191505092915050565b600061216c82612ab9565b6121768185612ae0565b9350612186818560208601612c23565b80840191505092915050565b6000815461219f81612c56565b6121a98186612ae0565b945060018216600081146121c457600181146121d557612208565b60ff19831686528186019350612208565b6121de85612aa4565b60005b83811015612200578154818901526001820191506020810190506121e1565b838801955050505b50505092915050565b600061221c82612ac4565b6122268185612aeb565b9350612236818560208601612c23565b61223f81612d99565b840191505092915050565b6000612257601b83612aeb565b915061226282612daa565b602082019050919050565b600061227a602683612aeb565b915061228582612dd3565b604082019050919050565b600061229d602183612aeb565b91506122a882612e22565b604082019050919050565b60006122c0600283612afc565b91506122cb82612e71565b600282019050919050565b60006122e3601b83612aeb565b91506122ee82612e9a565b602082019050919050565b6000612306601d83612aeb565b915061231182612ec3565b602082019050919050565b6000612329601183612aeb565b915061233482612eec565b602082019050919050565b600061234c602683612aeb565b915061235782612f15565b604082019050919050565b600061236f601d83612aeb565b915061237a82612f64565b602082019050919050565b6000612392601d83612aeb565b915061239d82612f8d565b602082019050919050565b60006123b5601f83612aeb565b91506123c082612fb6565b602082019050919050565b60006123d8601b83612aeb565b91506123e382612fdf565b602082019050919050565b60006123fb602083612aeb565b915061240682613008565b602082019050919050565b600061241e601283612aeb565b915061242982613031565b602082019050919050565b6000612441601d83612aeb565b915061244c8261305a565b602082019050919050565b6000612464601b83612aeb565b915061246f82613083565b602082019050919050565b6000612487601883612aeb565b9150612492826130ac565b602082019050919050565b60006124aa602a83612aeb565b91506124b5826130d5565b604082019050919050565b60006124cd601c83612aeb565b91506124d882613124565b602082019050919050565b60006124f0601e83612aeb565b91506124fb8261314d565b602082019050919050565b61250f81612bfd565b82525050565b61251e81612c07565b82525050565b60006125308284612161565b915081905092915050565b60006125478284612192565b915081905092915050565b600061255d826122b3565b915061256982856120e4565b60208201915061257982846120e4565b6020820191508190509392505050565b600060208201905061259e60008301846120b7565b92915050565b60006080820190506125b960008301886120b7565b6125c660208301876120b7565b6125d36040830186612506565b81810360608301526125e68184866120fb565b90509695505050505050565b600060408201905061260760008301856120b7565b6126146020830184612506565b9392505050565b600060808201905061263060008301876120b7565b61263d6020830186612506565b61264a60408301856120b7565b818103606083015261265c8184612128565b905095945050505050565b600060208201905061267c60008301846120c6565b92915050565b600060208201905061269760008301846120d5565b92915050565b600060c0820190506126b260008301896120d5565b6126bf60208301886120b7565b6126cc60408301876120b7565b6126d96060830186612506565b6126e66080830185612506565b6126f360a0830184612506565b979650505050505050565b600060a08201905061271360008301886120d5565b61272060208301876120d5565b61272d60408301866120d5565b61273a6060830185612506565b61274760808301846120b7565b9695505050505050565b600060808201905061276660008301876120d5565b6127736020830186612515565b61278060408301856120d5565b61278d60608301846120d5565b95945050505050565b600060208201905081810360008301526127b08184612211565b905092915050565b600060208201905081810360008301526127d18161224a565b9050919050565b600060208201905081810360008301526127f18161226d565b9050919050565b6000602082019050818103600083015261281181612290565b9050919050565b60006020820190508181036000830152612831816122d6565b9050919050565b60006020820190508181036000830152612851816122f9565b9050919050565b600060208201905081810360008301526128718161231c565b9050919050565b600060208201905081810360008301526128918161233f565b9050919050565b600060208201905081810360008301526128b181612362565b9050919050565b600060208201905081810360008301526128d181612385565b9050919050565b600060208201905081810360008301526128f1816123a8565b9050919050565b60006020820190508181036000830152612911816123cb565b9050919050565b60006020820190508181036000830152612931816123ee565b9050919050565b6000602082019050818103600083015261295181612411565b9050919050565b6000602082019050818103600083015261297181612434565b9050919050565b6000602082019050818103600083015261299181612457565b9050919050565b600060208201905081810360008301526129b18161247a565b9050919050565b600060208201905081810360008301526129d18161249d565b9050919050565b600060208201905081810360008301526129f1816124c0565b9050919050565b60006020820190508181036000830152612a11816124e3565b9050919050565b6000602082019050612a2d6000830184612506565b92915050565b6000602082019050612a486000830184612515565b92915050565b6000612a58612a69565b9050612a648282612c88565b919050565b6000604051905090565b600067ffffffffffffffff821115612a8e57612a8d612d6a565b5b612a9782612d99565b9050602081019050919050565b60008190508160005260206000209050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600081905092915050565b600082825260208201905092915050565b600081905092915050565b6000612b1282612bfd565b9150612b1d83612bfd565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115612b5257612b51612d0c565b5b828201905092915050565b6000612b6882612bfd565b9150612b7383612bfd565b925082821015612b8657612b85612d0c565b5b828203905092915050565b6000612b9c82612bdd565b9050919050565b60008115159050919050565b6000819050919050565b6000612bc482612b91565b9050919050565b6000612bd682612b91565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b83811015612c41578082015181840152602081019050612c26565b83811115612c50576000848401525b50505050565b60006002820490506001821680612c6e57607f821691505b60208210811415612c8257612c81612d3b565b5b50919050565b612c9182612d99565b810181811067ffffffffffffffff82111715612cb057612caf612d6a565b5b80604052505050565b6000612cc482612bfd565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415612cf757612cf6612d0c565b5b600182019050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b7f417070726f766520746f20746865207a65726f20616464726573730000000000600082015250565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b7f5472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6360008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b7f1901000000000000000000000000000000000000000000000000000000000000600082015250565b7f496e76616c6964207369676e6174757265202776272076616c75650000000000600082015250565b7f5472616e7366657220746f2074686520746f6b656e2061646472657373000000600082015250565b7f496e76616c6964207369676e6174757265000000000000000000000000000000600082015250565b7f416464726573733a20696e73756666696369656e742062616c616e636520666f60008201527f722063616c6c0000000000000000000000000000000000000000000000000000602082015250565b7f417070726f76652066726f6d20746865207a65726f2061646472657373000000600082015250565b7f4275726e20616d6f756e74206578636565647320616c6c6f77616e6365000000600082015250565b7f5472616e7366657220616d6f756e7420657863656564732062616c616e636500600082015250565b7f4275726e20616d6f756e7420657863656564732062616c616e63650000000000600082015250565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b7f5065726d697373696f6e20657870697265640000000000000000000000000000600082015250565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b7f496e76616c6964207369676e6174757265202773272076616c75650000000000600082015250565b7f4d696e7420746f20746865207a65726f20616464726573730000000000000000600082015250565b7f5361666545524332303a204552433230206f7065726174696f6e20646964206e60008201527f6f74207375636365656400000000000000000000000000000000000000000000602082015250565b7f5472616e7366657220746f20746865207a65726f206164647265737300000000600082015250565b7f5472616e736665722066726f6d20746865207a65726f20616464726573730000600082015250565b61317f81612b91565b811461318a57600080fd5b50565b61319681612ba3565b81146131a157600080fd5b50565b6131ad81612baf565b81146131b857600080fd5b50565b6131c481612bb9565b81146131cf57600080fd5b50565b6131db81612bcb565b81146131e657600080fd5b50565b6131f281612bfd565b81146131fd57600080fd5b50565b61320981612c07565b811461321457600080fd5b5056fea2646970667358221220b66e3ef21ffde9263f6eb3247e39c2adf91afb2daa59dd552c43bf8eace893db64736f6c63430008040033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101585760003560e01c8063771da5c5116100c3578063b4f94b2e1161007c578063b4f94b2e146103b7578063cae9ca51146103d5578063d505accf14610405578063dd62ed3e14610421578063f2fde38b14610451578063fc4e51f61461046d57610158565b8063771da5c5146102e157806379cc6790146102ff5780637ecebe001461031b5780638da5cb5b1461034b57806395d89b4114610369578063a9059cbb1461038757610158565b8063313ce56711610115578063313ce567146102335780633644e5151461025157806340c10f191461026f57806342966c681461028b57806370a08231146102a7578063715018a6146102d757610158565b806306fdde031461015d578063095ea7b31461017b5780631171bda9146101ab57806318160ddd146101c757806323b872dd146101e557806330adf81f14610215575b600080fd5b610165610489565b6040516101729190612796565b60405180910390f35b61019560048036038101906101909190611ef3565b610517565b6040516101a29190612667565b60405180910390f35b6101c560048036038101906101c09190611fbf565b61052e565b005b6101cf6105da565b6040516101dc9190612a18565b60405180910390f35b6101ff60048036038101906101fa9190611e06565b6105e0565b60405161020c9190612667565b60405180910390f35b61021d6106fa565b60405161022a9190612682565b60405180910390f35b61023b61071e565b6040516102489190612a33565b60405180910390f35b610259610723565b6040516102669190612682565b60405180910390f35b61028960048036038101906102849190611ef3565b610783565b005b6102a560048036038101906102a0919061208e565b610954565b005b6102c160048036038101906102bc9190611da1565b610961565b6040516102ce9190612a18565b60405180910390f35b6102df610979565b005b6102e9610a01565b6040516102f69190612a18565b60405180910390f35b61031960048036038101906103149190611ef3565b610a25565b005b61033560048036038101906103309190611da1565b610b36565b6040516103429190612a18565b60405180910390f35b610353610b4e565b6040516103609190612589565b60405180910390f35b610371610b77565b60405161037e9190612796565b60405180910390f35b6103a1600480360381019061039c9190611ef3565b610c05565b6040516103ae9190612667565b60405180910390f35b6103bf610c1c565b6040516103cc9190612682565b60405180910390f35b6103ef60048036038101906103ea9190611f2f565b610c40565b6040516103fc9190612667565b60405180910390f35b61041f600480360381019061041a9190611e55565b610cd7565b005b61043b60048036038101906104369190611dca565b610fbb565b6040516104489190612a18565b60405180910390f35b61046b60048036038101906104669190611da1565b610fe0565b005b6104876004803603810190610482919061200e565b6110d8565b005b6005805461049690612c56565b80601f01602080910402602001604051908101604052809291908181526020018280546104c290612c56565b801561050f5780601f106104e45761010080835404028352916020019161050f565b820191906000526020600020905b8154815290600101906020018083116104f257829003601f168201915b505050505081565b60006105243384846111ce565b6001905092915050565b610536611399565b73ffffffffffffffffffffffffffffffffffffffff16610554610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146105aa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105a190612918565b60405180910390fd5b6105d582828573ffffffffffffffffffffffffffffffffffffffff166113a19092919063ffffffff16565b505050565b60045481565b600080600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146106e357828110156106cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c3906127f8565b60405180910390fd5b6106e2853385846106dd9190612b5d565b6111ce565b5b6106ee858585611427565b60019150509392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60007f0000000000000000000000000000000000000000000000000000000000000000461415610775577f00000000000000000000000000000000000000000000000000000000000000009050610780565b61077d611718565b90505b90565b61078b611399565b73ffffffffffffffffffffffffffffffffffffffff166107a9610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146107ff576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107f690612918565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561086f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161086690612998565b60405180910390fd5b61087b600083836117c0565b806004600082825461088d9190612b07565b9250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546108e39190612b07565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516109489190612a18565b60405180910390a35050565b61095e33826117c5565b50565b60016020528060005260406000206000915090505481565b610981611399565b73ffffffffffffffffffffffffffffffffffffffff1661099f610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146109f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109ec90612918565b60405180910390fd5b6109ff600061192b565b565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610b275781811015610b10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b07906128b8565b60405180910390fd5b610b2683338484610b219190612b5d565b6111ce565b5b610b3183836117c5565b505050565b60036020528060005260406000206000915090505481565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60068054610b8490612c56565b80601f0160208091040260200160405190810160405280929190818152602001828054610bb090612c56565b8015610bfd5780601f10610bd257610100808354040283529160200191610bfd565b820191906000526020600020905b815481529060010190602001808311610be057829003601f168201915b505050505081565b6000610c12338484611427565b6001905092915050565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000610c4c8484610517565b15610ccb578373ffffffffffffffffffffffffffffffffffffffff16638f4ffcb1338530866040518563ffffffff1660e01b8152600401610c90949392919061261b565b600060405180830381600087803b158015610caa57600080fd5b505af1158015610cbe573d6000803e3d6000fd5b5050505060019050610cd0565b600090505b9392505050565b42841015610d1a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d1190612938565b60405180910390fd5b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08160001c1115610d80576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d7790612978565b60405180910390fd5b601b8360ff161480610d955750601c8360ff16145b610dd4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dcb90612818565b60405180910390fd5b6000610dde610723565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9898989600360008e73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000815480929190610e5290612cb9565b919050558a604051602001610e6c9695949392919061269d565b60405160208183030381529060405280519060200120604051602001610e93929190612552565b604051602081830303815290604052805190602001209050600060018286868660405160008152602001604052604051610ed09493929190612751565b6020604051602081039080840390855afa158015610ef2573d6000803e3d6000fd5b505050602060405103519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015610f6657508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b610fa5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f9c90612858565b60405180910390fd5b610fb08989896111ce565b505050505050505050565b6002602052816000526040600020602052806000526040600020600091509150505481565b610fe8611399565b73ffffffffffffffffffffffffffffffffffffffff16611006610b4e565b73ffffffffffffffffffffffffffffffffffffffff161461105c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161105390612918565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156110cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110c3906127d8565b60405180910390fd5b6110d58161192b565b50565b6110e0611399565b73ffffffffffffffffffffffffffffffffffffffff166110fe610b4e565b73ffffffffffffffffffffffffffffffffffffffff1614611154576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161114b90612918565b60405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff1663b88d4fde30868686866040518663ffffffff1660e01b81526004016111959594939291906125a4565b600060405180830381600087803b1580156111af57600080fd5b505af11580156111c3573d6000803e3d6000fd5b505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561123e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161123590612898565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156112ae576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112a5906127b8565b60405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161138c9190612a18565b60405180910390a3505050565b600033905090565b6114228363a9059cbb60e01b84846040516024016113c09291906125f2565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506119ef565b505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611497576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161148e906129f8565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611507576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114fe906129d8565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611576576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161156d90612838565b60405180910390fd5b6115818383836117c0565b6000600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611608576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115ff906128d8565b60405180910390fd5b81816116149190612b5d565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546116a69190612b07565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161170a9190612a18565b60405180910390a350505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f600560405161174a919061253b565b60405180910390206040518060400160405280600181526020017f31000000000000000000000000000000000000000000000000000000000000008152508051906020012046306040516020016117a59594939291906126fe565b60405160208183030381529060405280519060200120905090565b505050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561184c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611843906128f8565b60405180910390fd5b611858836000846117c0565b81816118649190612b5d565b600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600460008282546118b99190612b5d565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161191e9190612a18565b60405180910390a3505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b6000611a51826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff16611ab69092919063ffffffff16565b9050600081511115611ab15780806020019051810190611a719190611f96565b611ab0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611aa7906129b8565b60405180910390fd5b5b505050565b6060611ac58484600085611ace565b90509392505050565b606082471015611b13576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b0a90612878565b60405180910390fd5b611b1c85611be2565b611b5b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5290612958565b60405180910390fd5b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051611b849190612524565b60006040518083038185875af1925050503d8060008114611bc1576040519150601f19603f3d011682016040523d82523d6000602084013e611bc6565b606091505b5091509150611bd6828286611bf5565b92505050949350505050565b600080823b905060008111915050919050565b60608315611c0557829050611c55565b600083511115611c185782518084602001fd5b816040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c4c9190612796565b60405180910390fd5b9392505050565b6000611c6f611c6a84612a73565b612a4e565b905082815260208101848484011115611c8757600080fd5b611c92848285612c14565b509392505050565b600081359050611ca981613176565b92915050565b600081519050611cbe8161318d565b92915050565b600081359050611cd3816131a4565b92915050565b60008083601f840112611ceb57600080fd5b8235905067ffffffffffffffff811115611d0457600080fd5b602083019150836001820283011115611d1c57600080fd5b9250929050565b600082601f830112611d3457600080fd5b8135611d44848260208601611c5c565b91505092915050565b600081359050611d5c816131bb565b92915050565b600081359050611d71816131d2565b92915050565b600081359050611d86816131e9565b92915050565b600081359050611d9b81613200565b92915050565b600060208284031215611db357600080fd5b6000611dc184828501611c9a565b91505092915050565b60008060408385031215611ddd57600080fd5b6000611deb85828601611c9a565b9250506020611dfc85828601611c9a565b9150509250929050565b600080600060608486031215611e1b57600080fd5b6000611e2986828701611c9a565b9350506020611e3a86828701611c9a565b9250506040611e4b86828701611d77565b9150509250925092565b600080600080600080600060e0888a031215611e7057600080fd5b6000611e7e8a828b01611c9a565b9750506020611e8f8a828b01611c9a565b9650506040611ea08a828b01611d77565b9550506060611eb18a828b01611d77565b9450506080611ec28a828b01611d8c565b93505060a0611ed38a828b01611cc4565b92505060c0611ee48a828b01611cc4565b91505092959891949750929550565b60008060408385031215611f0657600080fd5b6000611f1485828601611c9a565b9250506020611f2585828601611d77565b9150509250929050565b600080600060608486031215611f4457600080fd5b6000611f5286828701611c9a565b9350506020611f6386828701611d77565b925050604084013567ffffffffffffffff811115611f8057600080fd5b611f8c86828701611d23565b9150509250925092565b600060208284031215611fa857600080fd5b6000611fb684828501611caf565b91505092915050565b600080600060608486031215611fd457600080fd5b6000611fe286828701611d4d565b9350506020611ff386828701611c9a565b925050604061200486828701611d77565b9150509250925092565b60008060008060006080868803121561202657600080fd5b600061203488828901611d62565b955050602061204588828901611c9a565b945050604061205688828901611d77565b935050606086013567ffffffffffffffff81111561207357600080fd5b61207f88828901611cd9565b92509250509295509295909350565b6000602082840312156120a057600080fd5b60006120ae84828501611d77565b91505092915050565b6120c081612b91565b82525050565b6120cf81612ba3565b82525050565b6120de81612baf565b82525050565b6120f56120f082612baf565b612d02565b82525050565b60006121078385612acf565b9350612114838584612c14565b61211d83612d99565b840190509392505050565b600061213382612ab9565b61213d8185612acf565b935061214d818560208601612c23565b61215681612d99565b840191505092915050565b600061216c82612ab9565b6121768185612ae0565b9350612186818560208601612c23565b80840191505092915050565b6000815461219f81612c56565b6121a98186612ae0565b945060018216600081146121c457600181146121d557612208565b60ff19831686528186019350612208565b6121de85612aa4565b60005b83811015612200578154818901526001820191506020810190506121e1565b838801955050505b50505092915050565b600061221c82612ac4565b6122268185612aeb565b9350612236818560208601612c23565b61223f81612d99565b840191505092915050565b6000612257601b83612aeb565b915061226282612daa565b602082019050919050565b600061227a602683612aeb565b915061228582612dd3565b604082019050919050565b600061229d602183612aeb565b91506122a882612e22565b604082019050919050565b60006122c0600283612afc565b91506122cb82612e71565b600282019050919050565b60006122e3601b83612aeb565b91506122ee82612e9a565b602082019050919050565b6000612306601d83612aeb565b915061231182612ec3565b602082019050919050565b6000612329601183612aeb565b915061233482612eec565b602082019050919050565b600061234c602683612aeb565b915061235782612f15565b604082019050919050565b600061236f601d83612aeb565b915061237a82612f64565b602082019050919050565b6000612392601d83612aeb565b915061239d82612f8d565b602082019050919050565b60006123b5601f83612aeb565b91506123c082612fb6565b602082019050919050565b60006123d8601b83612aeb565b91506123e382612fdf565b602082019050919050565b60006123fb602083612aeb565b915061240682613008565b602082019050919050565b600061241e601283612aeb565b915061242982613031565b602082019050919050565b6000612441601d83612aeb565b915061244c8261305a565b602082019050919050565b6000612464601b83612aeb565b915061246f82613083565b602082019050919050565b6000612487601883612aeb565b9150612492826130ac565b602082019050919050565b60006124aa602a83612aeb565b91506124b5826130d5565b604082019050919050565b60006124cd601c83612aeb565b91506124d882613124565b602082019050919050565b60006124f0601e83612aeb565b91506124fb8261314d565b602082019050919050565b61250f81612bfd565b82525050565b61251e81612c07565b82525050565b60006125308284612161565b915081905092915050565b60006125478284612192565b915081905092915050565b600061255d826122b3565b915061256982856120e4565b60208201915061257982846120e4565b6020820191508190509392505050565b600060208201905061259e60008301846120b7565b92915050565b60006080820190506125b960008301886120b7565b6125c660208301876120b7565b6125d36040830186612506565b81810360608301526125e68184866120fb565b90509695505050505050565b600060408201905061260760008301856120b7565b6126146020830184612506565b9392505050565b600060808201905061263060008301876120b7565b61263d6020830186612506565b61264a60408301856120b7565b818103606083015261265c8184612128565b905095945050505050565b600060208201905061267c60008301846120c6565b92915050565b600060208201905061269760008301846120d5565b92915050565b600060c0820190506126b260008301896120d5565b6126bf60208301886120b7565b6126cc60408301876120b7565b6126d96060830186612506565b6126e66080830185612506565b6126f360a0830184612506565b979650505050505050565b600060a08201905061271360008301886120d5565b61272060208301876120d5565b61272d60408301866120d5565b61273a6060830185612506565b61274760808301846120b7565b9695505050505050565b600060808201905061276660008301876120d5565b6127736020830186612515565b61278060408301856120d5565b61278d60608301846120d5565b95945050505050565b600060208201905081810360008301526127b08184612211565b905092915050565b600060208201905081810360008301526127d18161224a565b9050919050565b600060208201905081810360008301526127f18161226d565b9050919050565b6000602082019050818103600083015261281181612290565b9050919050565b60006020820190508181036000830152612831816122d6565b9050919050565b60006020820190508181036000830152612851816122f9565b9050919050565b600060208201905081810360008301526128718161231c565b9050919050565b600060208201905081810360008301526128918161233f565b9050919050565b600060208201905081810360008301526128b181612362565b9050919050565b600060208201905081810360008301526128d181612385565b9050919050565b600060208201905081810360008301526128f1816123a8565b9050919050565b60006020820190508181036000830152612911816123cb565b9050919050565b60006020820190508181036000830152612931816123ee565b9050919050565b6000602082019050818103600083015261295181612411565b9050919050565b6000602082019050818103600083015261297181612434565b9050919050565b6000602082019050818103600083015261299181612457565b9050919050565b600060208201905081810360008301526129b18161247a565b9050919050565b600060208201905081810360008301526129d18161249d565b9050919050565b600060208201905081810360008301526129f1816124c0565b9050919050565b60006020820190508181036000830152612a11816124e3565b9050919050565b6000602082019050612a2d6000830184612506565b92915050565b6000602082019050612a486000830184612515565b92915050565b6000612a58612a69565b9050612a648282612c88565b919050565b6000604051905090565b600067ffffffffffffffff821115612a8e57612a8d612d6a565b5b612a9782612d99565b9050602081019050919050565b60008190508160005260206000209050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600081905092915050565b600082825260208201905092915050565b600081905092915050565b6000612b1282612bfd565b9150612b1d83612bfd565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115612b5257612b51612d0c565b5b828201905092915050565b6000612b6882612bfd565b9150612b7383612bfd565b925082821015612b8657612b85612d0c565b5b828203905092915050565b6000612b9c82612bdd565b9050919050565b60008115159050919050565b6000819050919050565b6000612bc482612b91565b9050919050565b6000612bd682612b91565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b83811015612c41578082015181840152602081019050612c26565b83811115612c50576000848401525b50505050565b60006002820490506001821680612c6e57607f821691505b60208210811415612c8257612c81612d3b565b5b50919050565b612c9182612d99565b810181811067ffffffffffffffff82111715612cb057612caf612d6a565b5b80604052505050565b6000612cc482612bfd565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415612cf757612cf6612d0c565b5b600182019050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b7f417070726f766520746f20746865207a65726f20616464726573730000000000600082015250565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b7f5472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6360008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b7f1901000000000000000000000000000000000000000000000000000000000000600082015250565b7f496e76616c6964207369676e6174757265202776272076616c75650000000000600082015250565b7f5472616e7366657220746f2074686520746f6b656e2061646472657373000000600082015250565b7f496e76616c6964207369676e6174757265000000000000000000000000000000600082015250565b7f416464726573733a20696e73756666696369656e742062616c616e636520666f60008201527f722063616c6c0000000000000000000000000000000000000000000000000000602082015250565b7f417070726f76652066726f6d20746865207a65726f2061646472657373000000600082015250565b7f4275726e20616d6f756e74206578636565647320616c6c6f77616e6365000000600082015250565b7f5472616e7366657220616d6f756e7420657863656564732062616c616e636500600082015250565b7f4275726e20616d6f756e7420657863656564732062616c616e63650000000000600082015250565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b7f5065726d697373696f6e20657870697265640000000000000000000000000000600082015250565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b7f496e76616c6964207369676e6174757265202773272076616c75650000000000600082015250565b7f4d696e7420746f20746865207a65726f20616464726573730000000000000000600082015250565b7f5361666545524332303a204552433230206f7065726174696f6e20646964206e60008201527f6f74207375636365656400000000000000000000000000000000000000000000602082015250565b7f5472616e7366657220746f20746865207a65726f206164647265737300000000600082015250565b7f5472616e736665722066726f6d20746865207a65726f20616464726573730000600082015250565b61317f81612b91565b811461318a57600080fd5b50565b61319681612ba3565b81146131a157600080fd5b50565b6131ad81612baf565b81146131b857600080fd5b50565b6131c481612bb9565b81146131cf57600080fd5b50565b6131db81612bcb565b81146131e657600080fd5b50565b6131f281612bfd565b81146131fd57600080fd5b50565b61320981612c07565b811461321457600080fd5b5056fea2646970667358221220b66e3ef21ffde9263f6eb3247e39c2adf91afb2daa59dd552c43bf8eace893db64736f6c63430008040033", + "devdoc": { + "kind": "dev", + "methods": { + "approve(address,uint256)": { + "details": "If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance. Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729", + "returns": { + "_0": "True if the operation succeeded." + } + }, + "approveAndCall(address,uint256,bytes)": { + "details": "If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.", + "returns": { + "_0": "True if both approval and `receiveApproval` calls succeeded." + } + }, + "burn(uint256)": { + "details": "Requirements: - the caller must have a balance of at least `amount`." + }, + "burnFrom(address,uint256)": { + "details": "Requirements: - `account` must have a balance of at least `amount`, - the caller must have allowance for `account`'s tokens of at least `amount`." + }, + "mint(address,uint256)": { + "details": "Requirements: - `recipient` cannot be the zero address." + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { + "details": "The deadline argument can be set to `type(uint256).max to create permits that effectively never expire. If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance." + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner." + }, + "transfer(address,uint256)": { + "details": "Requirements: - `recipient` cannot be the zero address, - the caller must have a balance of at least `amount`.", + "returns": { + "_0": "True if the operation succeeded, reverts otherwise." + } + }, + "transferFrom(address,address,uint256)": { + "details": "Requirements: - `sender` and `recipient` cannot be the zero address, - `sender` must have a balance of at least `amount`, - the caller must have allowance for `sender`'s tokens of at least `amount`.", + "returns": { + "_0": "True if the operation succeeded, reverts otherwise." + } + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "DOMAIN_SEPARATOR()": { + "notice": "Returns hash of EIP712 Domain struct with the token name as a signing domain and token contract as a verifying contract. Used to construct EIP2612 signature provided to `permit` function." + }, + "PERMIT_TYPEHASH()": { + "notice": "Returns EIP2612 Permit message hash. Used to construct EIP2612 signature provided to `permit` function." + }, + "allowance(address,address)": { + "notice": "The remaining number of tokens that spender will be allowed to spend on behalf of owner through `transferFrom` and `burnFrom`. This is zero by default." + }, + "approve(address,uint256)": { + "notice": "Sets `amount` as the allowance of `spender` over the caller's tokens." + }, + "approveAndCall(address,uint256,bytes)": { + "notice": "Calls `receiveApproval` function on spender previously approving the spender to withdraw from the caller multiple times, up to the `amount` amount. If this function is called again, it overwrites the current allowance with `amount`. Reverts if the approval reverted or if `receiveApproval` call on the spender reverted." + }, + "balanceOf(address)": { + "notice": "The amount of tokens owned by the given account." + }, + "burn(uint256)": { + "notice": "Destroys `amount` tokens from the caller." + }, + "burnFrom(address,uint256)": { + "notice": "Destroys `amount` of tokens from `account` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`." + }, + "decimals()": { + "notice": "The decimals places of the token." + }, + "mint(address,uint256)": { + "notice": "Creates `amount` tokens and assigns them to `account`, increasing the total supply." + }, + "name()": { + "notice": "The name of the token." + }, + "nonces(address)": { + "notice": "Returns the current nonce for EIP2612 permission for the provided token owner for a replay protection. Used to construct EIP2612 signature provided to `permit` function." + }, + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { + "notice": "EIP2612 approval made with secp256k1 signature. Users can authorize a transfer of their tokens with a signature conforming EIP712 standard, rather than an on-chain transaction from their address. Anyone can submit this signature on the user's behalf by calling the permit function, paying gas fees, and possibly performing other actions in the same transaction." + }, + "symbol()": { + "notice": "The symbol of the token." + }, + "totalSupply()": { + "notice": "The amount of tokens in existence." + }, + "transfer(address,uint256)": { + "notice": "Moves `amount` tokens from the caller's account to `recipient`." + }, + "transferFrom(address,address,uint256)": { + "notice": "Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`." + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 7, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 1981, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "balanceOf", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 1989, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "allowance", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 1995, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "nonces", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 2010, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "totalSupply", + "offset": 0, + "slot": "4", + "type": "t_uint256" + }, + { + "astId": 2014, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "name", + "offset": 0, + "slot": "5", + "type": "t_string_storage" + }, + { + "astId": 2018, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "symbol", + "offset": 0, + "slot": "6", + "type": "t_string_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } +} diff --git a/core/external/sepolia/TBTC.json b/core/external/sepolia/TBTC.json new file mode 100644 index 000000000..d12d88f04 --- /dev/null +++ b/core/external/sepolia/TBTC.json @@ -0,0 +1,763 @@ +{ + "address": "0x517f2982701695D4E52f1ECFBEf3ba31Df470161", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "approveAndCall", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cachedChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cachedDomainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC721", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "recoverERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0x8f26955ab7df8e1c4a4d1b739a5b21e95c2a4233d9fda88b39ed9508a3f61205", + "receipt": { + "to": null, + "from": "0x68ad60CC5e8f3B7cC53beaB321cf0e6036962dBc", + "contractAddress": "0x517f2982701695D4E52f1ECFBEf3ba31Df470161", + "transactionIndex": 23, + "gasUsed": "1590745", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000001000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000400000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000001020000000000000000000000000000000000000000000000000000800000000000002", + "blockHash": "0x6d9f0e441a93aae381458f444c20091301cd4f968b9ceb3fdec6a3ce63efa69d", + "transactionHash": "0x8f26955ab7df8e1c4a4d1b739a5b21e95c2a4233d9fda88b39ed9508a3f61205", + "logs": [ + { + "transactionIndex": 23, + "blockNumber": 4553001, + "transactionHash": "0x8f26955ab7df8e1c4a4d1b739a5b21e95c2a4233d9fda88b39ed9508a3f61205", + "address": "0x517f2982701695D4E52f1ECFBEf3ba31Df470161", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000068ad60cc5e8f3b7cc53beab321cf0e6036962dbc" + ], + "data": "0x", + "logIndex": 142, + "blockHash": "0x6d9f0e441a93aae381458f444c20091301cd4f968b9ceb3fdec6a3ce63efa69d" + } + ], + "blockNumber": 4553001, + "cumulativeGasUsed": "7290511", + "status": 1, + "byzantium": true + }, + "args": [], + "numDeployments": 1, + "solcInputHash": "c44da59b12f2cc32a8b991146f34536b", + "metadata": "{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"name\":\"approveAndCall\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burnFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cachedChainId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cachedDomainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"nonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"permit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC721\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"recoverERC721\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"approve(address,uint256)\":{\"details\":\"If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance. Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\",\"returns\":{\"_0\":\"True if the operation succeeded.\"}},\"approveAndCall(address,uint256,bytes)\":{\"details\":\"If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.\",\"returns\":{\"_0\":\"True if both approval and `receiveApproval` calls succeeded.\"}},\"burn(uint256)\":{\"details\":\"Requirements: - the caller must have a balance of at least `amount`.\"},\"burnFrom(address,uint256)\":{\"details\":\"Requirements: - `account` must have a balance of at least `amount`, - the caller must have allowance for `account`'s tokens of at least `amount`.\"},\"mint(address,uint256)\":{\"details\":\"Requirements: - `recipient` cannot be the zero address.\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"details\":\"The deadline argument can be set to `type(uint256).max to create permits that effectively never expire. If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.\"},\"transfer(address,uint256)\":{\"details\":\"Requirements: - `recipient` cannot be the zero address, - the caller must have a balance of at least `amount`.\",\"returns\":{\"_0\":\"True if the operation succeeded, reverts otherwise.\"}},\"transferFrom(address,address,uint256)\":{\"details\":\"Requirements: - `spender` and `recipient` cannot be the zero address, - `spender` must have a balance of at least `amount`, - the caller must have allowance for `spender`'s tokens of at least `amount`.\",\"returns\":{\"_0\":\"True if the operation succeeded, reverts otherwise.\"}},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"DOMAIN_SEPARATOR()\":{\"notice\":\"Returns hash of EIP712 Domain struct with the token name as a signing domain and token contract as a verifying contract. Used to construct EIP2612 signature provided to `permit` function.\"},\"PERMIT_TYPEHASH()\":{\"notice\":\"Returns EIP2612 Permit message hash. Used to construct EIP2612 signature provided to `permit` function.\"},\"allowance(address,address)\":{\"notice\":\"The remaining number of tokens that spender will be allowed to spend on behalf of owner through `transferFrom` and `burnFrom`. This is zero by default.\"},\"approve(address,uint256)\":{\"notice\":\"Sets `amount` as the allowance of `spender` over the caller's tokens.\"},\"approveAndCall(address,uint256,bytes)\":{\"notice\":\"Calls `receiveApproval` function on spender previously approving the spender to withdraw from the caller multiple times, up to the `amount` amount. If this function is called again, it overwrites the current allowance with `amount`. Reverts if the approval reverted or if `receiveApproval` call on the spender reverted.\"},\"balanceOf(address)\":{\"notice\":\"The amount of tokens owned by the given account.\"},\"burn(uint256)\":{\"notice\":\"Destroys `amount` tokens from the caller.\"},\"burnFrom(address,uint256)\":{\"notice\":\"Destroys `amount` of tokens from `account` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`.\"},\"decimals()\":{\"notice\":\"The decimals places of the token.\"},\"mint(address,uint256)\":{\"notice\":\"Creates `amount` tokens and assigns them to `account`, increasing the total supply.\"},\"name()\":{\"notice\":\"The name of the token.\"},\"nonce(address)\":{\"notice\":\"Returns the current nonce for EIP2612 permission for the provided token owner for a replay protection. Used to construct EIP2612 signature provided to `permit` function.\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"notice\":\"EIP2612 approval made with secp256k1 signature. Users can authorize a transfer of their tokens with a signature conforming EIP712 standard, rather than an on-chain transaction from their address. Anyone can submit this signature on the user's behalf by calling the permit function, paying gas fees, and possibly performing other actions in the same transaction.\"},\"symbol()\":{\"notice\":\"The symbol of the token.\"},\"totalSupply()\":{\"notice\":\"The amount of tokens in existence.\"},\"transfer(address,uint256)\":{\"notice\":\"Moves `amount` tokens from the caller's account to `recipient`.\"},\"transferFrom(address,address,uint256)\":{\"notice\":\"Moves `amount` tokens from `spender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/token/TBTC.sol\":\"TBTC\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":1000},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor() {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions anymore. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby removing any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xa94b34880e3c1b0b931662cb1c09e5dfa6662f31cba80e07c5ee71cd135c9673\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `from` to `to` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 amount\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x9750c6b834f7b43000631af5cc30001c5f547b3ceb3635488f140f60e897ea6b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\\n *\\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\\n * need to send a transaction, and thus is not required to hold Ether at all.\\n */\\ninterface IERC20Permit {\\n /**\\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\\n * given ``owner``'s signed approval.\\n *\\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\\n * ordering also apply here.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `spender` cannot be the zero address.\\n * - `deadline` must be a timestamp in the future.\\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\\n * over the EIP712-formatted function arguments.\\n * - the signature must use ``owner``'s current nonce (see {nonces}).\\n *\\n * For more information on the signature format, see the\\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\\n * section].\\n */\\n function permit(\\n address owner,\\n address spender,\\n uint256 value,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /**\\n * @dev Returns the current nonce for `owner`. This value must be\\n * included whenever a signature is generated for {permit}.\\n *\\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\\n * prevents a signature from being used multiple times.\\n */\\n function nonces(address owner) external view returns (uint256);\\n\\n /**\\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\\n */\\n // solhint-disable-next-line func-name-mixedcase\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0xf41ca991f30855bf80ffd11e9347856a517b977f0a6c2d52e6421a99b7840329\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../extensions/draft-IERC20Permit.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n function safePermit(\\n IERC20Permit token,\\n address owner,\\n address spender,\\n uint256 value,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal {\\n uint256 nonceBefore = token.nonces(owner);\\n token.permit(owner, spender, value, deadline, v, r, s);\\n uint256 nonceAfter = token.nonces(owner);\\n require(nonceAfter == nonceBefore + 1, \\\"SafeERC20: permit did not succeed\\\");\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0x9b72f93be69ca894d8492c244259615c4a742afc8d63720dbc8bb81087d9b238\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/IERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../../utils/introspection/IERC165.sol\\\";\\n\\n/**\\n * @dev Required interface of an ERC721 compliant contract.\\n */\\ninterface IERC721 is IERC165 {\\n /**\\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\\n */\\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\\n */\\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\\n\\n /**\\n * @dev Returns the number of tokens in ``owner``'s account.\\n */\\n function balanceOf(address owner) external view returns (uint256 balance);\\n\\n /**\\n * @dev Returns the owner of the `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function ownerOf(uint256 tokenId) external view returns (address owner);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes calldata data\\n ) external;\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Transfers `tokenId` token from `from` to `to`.\\n *\\n * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721\\n * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must\\n * understand this adds an external call which potentially creates a reentrancy vulnerability.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\\n * The approval is cleared when the token is transferred.\\n *\\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\\n *\\n * Requirements:\\n *\\n * - The caller must own the token or be an approved operator.\\n * - `tokenId` must exist.\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Approve or remove `operator` as an operator for the caller.\\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\\n *\\n * Requirements:\\n *\\n * - The `operator` cannot be the caller.\\n *\\n * Emits an {ApprovalForAll} event.\\n */\\n function setApprovalForAll(address operator, bool _approved) external;\\n\\n /**\\n * @dev Returns the account approved for `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function getApproved(uint256 tokenId) external view returns (address operator);\\n\\n /**\\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\\n *\\n * See {setApprovalForAll}\\n */\\n function isApprovedForAll(address owner, address operator) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xab28a56179c1db258c9bf5235b382698cb650debecb51b23d12be9e241374b68\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\\n *\\n * _Available since v4.8._\\n */\\n function verifyCallResultFromTarget(\\n address target,\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n if (success) {\\n if (returndata.length == 0) {\\n // only check isContract if the call was successful and the return data is empty\\n // otherwise we already know that it was a contract\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n }\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason or using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n /// @solidity memory-safe-assembly\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n}\\n\",\"keccak256\":\"0xf96f969e24029d43d0df89e59d365f277021dac62b48e1c1e3ebe0acdd7f1ca1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IERC20WithPermit.sol\\\";\\nimport \\\"./IReceiveApproval.sol\\\";\\n\\n/// @title ERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ncontract ERC20WithPermit is IERC20WithPermit, Ownable {\\n /// @notice The amount of tokens owned by the given account.\\n mapping(address => uint256) public override balanceOf;\\n\\n /// @notice The remaining number of tokens that spender will be\\n /// allowed to spend on behalf of owner through `transferFrom` and\\n /// `burnFrom`. This is zero by default.\\n mapping(address => mapping(address => uint256)) public override allowance;\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n mapping(address => uint256) public override nonce;\\n\\n uint256 public immutable cachedChainId;\\n bytes32 public immutable cachedDomainSeparator;\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n bytes32 public constant override PERMIT_TYPEHASH =\\n keccak256(\\n \\\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\\\"\\n );\\n\\n /// @notice The amount of tokens in existence.\\n uint256 public override totalSupply;\\n\\n /// @notice The name of the token.\\n string public override name;\\n\\n /// @notice The symbol of the token.\\n string public override symbol;\\n\\n /// @notice The decimals places of the token.\\n uint8 public constant override decimals = 18;\\n\\n constructor(string memory _name, string memory _symbol) {\\n name = _name;\\n symbol = _symbol;\\n\\n cachedChainId = block.chainid;\\n cachedDomainSeparator = buildDomainSeparator();\\n }\\n\\n /// @notice Moves `amount` tokens from the caller's account to `recipient`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - the caller must have a balance of at least `amount`.\\n function transfer(address recipient, uint256 amount)\\n external\\n override\\n returns (bool)\\n {\\n _transfer(msg.sender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice Moves `amount` tokens from `spender` to `recipient` using the\\n /// allowance mechanism. `amount` is then deducted from the caller's\\n /// allowance unless the allowance was made for `type(uint256).max`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `spender` and `recipient` cannot be the zero address,\\n /// - `spender` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `spender`'s tokens of at least\\n /// `amount`.\\n function transferFrom(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) external override returns (bool) {\\n uint256 currentAllowance = allowance[spender][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Transfer amount exceeds allowance\\\"\\n );\\n _approve(spender, msg.sender, currentAllowance - amount);\\n }\\n _transfer(spender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire. If the `amount` is set\\n /// to `type(uint256).max` then `transferFrom` and `burnFrom` will\\n /// not reduce an allowance.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external override {\\n /* solhint-disable-next-line not-rely-on-time */\\n require(deadline >= block.timestamp, \\\"Permission expired\\\");\\n\\n // Validate `s` and `v` values for a malleability concern described in EIP2.\\n // Only signatures with `s` value in the lower half of the secp256k1\\n // curve's order and `v` value of 27 or 28 are considered valid.\\n require(\\n uint256(s) <=\\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\\n \\\"Invalid signature 's' value\\\"\\n );\\n require(v == 27 || v == 28, \\\"Invalid signature 'v' value\\\");\\n\\n bytes32 digest = keccak256(\\n abi.encodePacked(\\n \\\"\\\\x19\\\\x01\\\",\\n DOMAIN_SEPARATOR(),\\n keccak256(\\n abi.encode(\\n PERMIT_TYPEHASH,\\n owner,\\n spender,\\n amount,\\n nonce[owner]++,\\n deadline\\n )\\n )\\n )\\n );\\n address recoveredAddress = ecrecover(digest, v, r, s);\\n require(\\n recoveredAddress != address(0) && recoveredAddress == owner,\\n \\\"Invalid signature\\\"\\n );\\n _approve(owner, spender, amount);\\n }\\n\\n /// @notice Creates `amount` tokens and assigns them to `account`,\\n /// increasing the total supply.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address.\\n function mint(address recipient, uint256 amount) external onlyOwner {\\n require(recipient != address(0), \\\"Mint to the zero address\\\");\\n\\n beforeTokenTransfer(address(0), recipient, amount);\\n\\n totalSupply += amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(address(0), recipient, amount);\\n }\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n /// @dev Requirements:\\n /// - the caller must have a balance of at least `amount`.\\n function burn(uint256 amount) external override {\\n _burn(msg.sender, amount);\\n }\\n\\n /// @notice Destroys `amount` of tokens from `account` using the allowance\\n /// mechanism. `amount` is then deducted from the caller's allowance\\n /// unless the allowance was made for `type(uint256).max`.\\n /// @dev Requirements:\\n /// - `account` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `account`'s tokens of at least\\n /// `amount`.\\n function burnFrom(address account, uint256 amount) external override {\\n uint256 currentAllowance = allowance[account][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Burn amount exceeds allowance\\\"\\n );\\n _approve(account, msg.sender, currentAllowance - amount);\\n }\\n _burn(account, amount);\\n }\\n\\n /// @notice Calls `receiveApproval` function on spender previously approving\\n /// the spender to withdraw from the caller multiple times, up to\\n /// the `amount` amount. If this function is called again, it\\n /// overwrites the current allowance with `amount`. Reverts if the\\n /// approval reverted or if `receiveApproval` call on the spender\\n /// reverted.\\n /// @return True if both approval and `receiveApproval` calls succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external override returns (bool) {\\n if (approve(spender, amount)) {\\n IReceiveApproval(spender).receiveApproval(\\n msg.sender,\\n amount,\\n address(this),\\n extraData\\n );\\n return true;\\n }\\n return false;\\n }\\n\\n /// @notice Sets `amount` as the allowance of `spender` over the caller's\\n /// tokens.\\n /// @return True if the operation succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n /// Beware that changing an allowance with this method brings the risk\\n /// that someone may use both the old and the new allowance by\\n /// unfortunate transaction ordering. One possible solution to mitigate\\n /// this race condition is to first reduce the spender's allowance to 0\\n /// and set the desired value afterwards:\\n /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n function approve(address spender, uint256 amount)\\n public\\n override\\n returns (bool)\\n {\\n _approve(msg.sender, spender, amount);\\n return true;\\n }\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() public view override returns (bytes32) {\\n // As explained in EIP-2612, if the DOMAIN_SEPARATOR contains the\\n // chainId and is defined at contract deployment instead of\\n // reconstructed for every signature, there is a risk of possible replay\\n // attacks between chains in the event of a future chain split.\\n // To address this issue, we check the cached chain ID against the\\n // current one and in case they are different, we build domain separator\\n // from scratch.\\n if (block.chainid == cachedChainId) {\\n return cachedDomainSeparator;\\n } else {\\n return buildDomainSeparator();\\n }\\n }\\n\\n /// @dev Hook that is called before any transfer of tokens. This includes\\n /// minting and burning.\\n ///\\n /// Calling conditions:\\n /// - when `from` and `to` are both non-zero, `amount` of `from`'s tokens\\n /// will be to transferred to `to`.\\n /// - when `from` is zero, `amount` tokens will be minted for `to`.\\n /// - when `to` is zero, `amount` of ``from``'s tokens will be burned.\\n /// - `from` and `to` are never both zero.\\n // slither-disable-next-line dead-code\\n function beforeTokenTransfer(\\n address from,\\n address to,\\n uint256 amount\\n ) internal virtual {}\\n\\n function _burn(address account, uint256 amount) internal {\\n uint256 currentBalance = balanceOf[account];\\n require(currentBalance >= amount, \\\"Burn amount exceeds balance\\\");\\n\\n beforeTokenTransfer(account, address(0), amount);\\n\\n balanceOf[account] = currentBalance - amount;\\n totalSupply -= amount;\\n emit Transfer(account, address(0), amount);\\n }\\n\\n function _transfer(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) private {\\n require(spender != address(0), \\\"Transfer from the zero address\\\");\\n require(recipient != address(0), \\\"Transfer to the zero address\\\");\\n require(recipient != address(this), \\\"Transfer to the token address\\\");\\n\\n beforeTokenTransfer(spender, recipient, amount);\\n\\n uint256 spenderBalance = balanceOf[spender];\\n require(spenderBalance >= amount, \\\"Transfer amount exceeds balance\\\");\\n balanceOf[spender] = spenderBalance - amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(spender, recipient, amount);\\n }\\n\\n function _approve(\\n address owner,\\n address spender,\\n uint256 amount\\n ) private {\\n require(owner != address(0), \\\"Approve from the zero address\\\");\\n require(spender != address(0), \\\"Approve to the zero address\\\");\\n allowance[owner][spender] = amount;\\n emit Approval(owner, spender, amount);\\n }\\n\\n function buildDomainSeparator() private view returns (bytes32) {\\n return\\n keccak256(\\n abi.encode(\\n keccak256(\\n \\\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\\\"\\n ),\\n keccak256(bytes(name)),\\n keccak256(bytes(\\\"1\\\")),\\n block.chainid,\\n address(this)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x1e1bf4ec5c9d6fe70f6f834316482aeff3f122ff4ffaa7178099e7ae71a0b16d\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IApproveAndCall.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by tokens supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IApproveAndCall {\\n /// @notice Executes `receiveApproval` function on spender as specified in\\n /// `IReceiveApproval` interface. Approves spender to withdraw from\\n /// the caller multiple times, up to the `amount`. If this\\n /// function is called again, it overwrites the current allowance\\n /// with `amount`. Reverts if the approval reverted or if\\n /// `receiveApproval` call on the spender reverted.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x393d18ef81a57dcc96fff4c340cc2945deaebb37b9796c322cf2bc96872c3df8\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\n\\nimport \\\"./IApproveAndCall.sol\\\";\\n\\n/// @title IERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ninterface IERC20WithPermit is IERC20, IERC20Metadata, IApproveAndCall {\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n function burn(uint256 amount) external;\\n\\n /// @notice Destroys `amount` of tokens from `account`, deducting the amount\\n /// from caller's allowance.\\n function burnFrom(address account, uint256 amount) external;\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n function nonce(address owner) external view returns (uint256);\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function PERMIT_TYPEHASH() external pure returns (bytes32);\\n}\\n\",\"keccak256\":\"0xdac9a5086c19a7128b505a7be1ab0ac1aa314f6989cb88d2417e9d7383f89fa9\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by contracts supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IReceiveApproval {\\n /// @notice Receives approval to spend tokens. Called as a result of\\n /// `approveAndCall` call on the token.\\n function receiveApproval(\\n address from,\\n uint256 amount,\\n address token,\\n bytes calldata extraData\\n ) external;\\n}\\n\",\"keccak256\":\"0x6a30d83ad230548b1e7839737affc8489a035314209de14b89dbef7fb0f66395\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC721/IERC721.sol\\\";\\n\\n/// @title MisfundRecovery\\n/// @notice Allows the owner of the token contract extending MisfundRecovery\\n/// to recover any ERC20 and ERC721 sent mistakenly to the token\\n/// contract address.\\ncontract MisfundRecovery is Ownable {\\n using SafeERC20 for IERC20;\\n\\n function recoverERC20(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n token.safeTransfer(recipient, amount);\\n }\\n\\n function recoverERC721(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n token.safeTransferFrom(address(this), recipient, tokenId, data);\\n }\\n}\\n\",\"keccak256\":\"0xbbfea02bf20e2a6df5a497bbc05c7540a3b7c7dfb8b1feeaffef7f6b8ba65d65\",\"license\":\"MIT\"},\"contracts/token/TBTC.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\\\";\\nimport \\\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\\\";\\n\\ncontract TBTC is ERC20WithPermit, MisfundRecovery {\\n constructor() ERC20WithPermit(\\\"tBTC v2\\\", \\\"tBTC\\\") {}\\n}\\n\",\"keccak256\":\"0x4542aaa48f7682005b815253768b6433c811daff5d2727db256d5f1879f5ccbf\",\"license\":\"GPL-3.0-only\"}},\"version\":1}", + "bytecode": "0x60c06040523480156200001157600080fd5b50604051806040016040528060078152602001663a212a21903b1960c91b815250604051806040016040528060048152602001637442544360e01b8152506200006962000063620000a160201b60201c565b620000a5565b60056200007783826200024b565b5060066200008682826200024b565b504660805262000095620000f5565b60a05250620003959050565b3390565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f600560405162000129919062000317565b60408051918290038220828201825260018352603160f81b6020938401528151928301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b634e487b7160e01b600052604160045260246000fd5b600181811c90821680620001d157607f821691505b602082108103620001f257634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200024657600081815260208120601f850160051c81016020861015620002215750805b601f850160051c820191505b8181101562000242578281556001016200022d565b5050505b505050565b81516001600160401b03811115620002675762000267620001a6565b6200027f81620002788454620001bc565b84620001f8565b602080601f831160018114620002b757600084156200029e5750858301515b600019600386901b1c1916600185901b17855562000242565b600085815260208120601f198616915b82811015620002e857888601518255948401946001909101908401620002c7565b5085821015620003075787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60008083546200032781620001bc565b60018281168015620003425760018114620003585762000389565b60ff198416875282151583028701945062000389565b8760005260208060002060005b85811015620003805781548a82015290840190820162000365565b50505082870194505b50929695505050505050565b60805160a051611a61620003c96000396000818161034901526105a80152600081816102d901526105800152611a616000f3fe608060405234801561001057600080fd5b50600436106101985760003560e01c8063715018a6116100e3578063b4f94b2e1161008c578063dd62ed3e11610066578063dd62ed3e14610391578063f2fde38b146103bc578063fc4e51f6146103cf57600080fd5b8063b4f94b2e14610344578063cae9ca511461036b578063d505accf1461037e57600080fd5b80638da5cb5b116100bd5780638da5cb5b1461030e57806395d89b4114610329578063a9059cbb1461033157600080fd5b8063715018a6146102cc578063771da5c5146102d457806379cc6790146102fb57600080fd5b8063313ce5671161014557806342966c681161011f57806342966c681461027957806370a082311461028c57806370ae92d2146102ac57600080fd5b8063313ce567146102445780633644e5151461025e57806340c10f191461026657600080fd5b806318160ddd1161017657806318160ddd146101f357806323b872dd1461020a57806330adf81f1461021d57600080fd5b806306fdde031461019d578063095ea7b3146101bb5780631171bda9146101de575b600080fd5b6101a56103e2565b6040516101b29190611532565b60405180910390f35b6101ce6101c936600461155a565b610470565b60405190151581526020016101b2565b6101f16101ec366004611586565b610487565b005b6101fc60045481565b6040519081526020016101b2565b6101ce610218366004611586565b6104a8565b6101fc7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b61024c601281565b60405160ff90911681526020016101b2565b6101fc61057c565b6101f161027436600461155a565b6105d7565b6101f16102873660046115c7565b6106be565b6101fc61029a3660046115e0565b60016020526000908152604090205481565b6101fc6102ba3660046115e0565b60036020526000908152604090205481565b6101f16106cb565b6101fc7f000000000000000000000000000000000000000000000000000000000000000081565b6101f161030936600461155a565b6106df565b6000546040516001600160a01b0390911681526020016101b2565b6101a5610775565b6101ce61033f36600461155a565b610782565b6101fc7f000000000000000000000000000000000000000000000000000000000000000081565b6101ce610379366004611613565b61078f565b6101f161038c3660046116e0565b610830565b6101fc61039f366004611757565b600260209081526000928352604080842090915290825290205481565b6101f16103ca3660046115e0565b610b40565b6101f16103dd366004611790565b610bcd565b600580546103ef9061182f565b80601f016020809104026020016040519081016040528092919081815260200182805461041b9061182f565b80156104685780601f1061043d57610100808354040283529160200191610468565b820191906000526020600020905b81548152906001019060200180831161044b57829003601f168201915b505050505081565b600061047d338484610c50565b5060015b92915050565b61048f610d5e565b6104a36001600160a01b0384168383610db8565b505050565b6001600160a01b0383166000908152600260209081526040808320338452909152812054600019811461056457828110156105505760405162461bcd60e51b815260206004820152602160248201527f5472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6360448201527f650000000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610564853361055f868561187f565b610c50565b61056f858585610e38565b60019150505b9392505050565b60007f000000000000000000000000000000000000000000000000000000000000000046036105ca57507f000000000000000000000000000000000000000000000000000000000000000090565b6105d261103f565b905090565b6105df610d5e565b6001600160a01b0382166106355760405162461bcd60e51b815260206004820152601860248201527f4d696e7420746f20746865207a65726f206164647265737300000000000000006044820152606401610547565b80600460008282546106479190611892565b90915550506001600160a01b03821660009081526001602052604081208054839290610674908490611892565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b6106c8338261110a565b50565b6106d3610d5e565b6106dd60006111ee565b565b6001600160a01b0382166000908152600260209081526040808320338452909152902054600019811461076b578181101561075c5760405162461bcd60e51b815260206004820152601d60248201527f4275726e20616d6f756e74206578636565647320616c6c6f77616e63650000006044820152606401610547565b61076b833361055f858561187f565b6104a3838361110a565b600680546103ef9061182f565b600061047d338484610e38565b600061079b8484610470565b15610826576040517f8f4ffcb10000000000000000000000000000000000000000000000000000000081526001600160a01b03851690638f4ffcb1906107eb9033908790309088906004016118a5565b600060405180830381600087803b15801561080557600080fd5b505af1158015610819573d6000803e3d6000fd5b5050505060019050610575565b5060009392505050565b428410156108805760405162461bcd60e51b815260206004820152601260248201527f5065726d697373696f6e206578706972656400000000000000000000000000006044820152606401610547565b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08111156108f05760405162461bcd60e51b815260206004820152601b60248201527f496e76616c6964207369676e6174757265202773272076616c756500000000006044820152606401610547565b8260ff16601b148061090557508260ff16601c145b6109515760405162461bcd60e51b815260206004820152601b60248201527f496e76616c6964207369676e6174757265202776272076616c756500000000006044820152606401610547565b600061095b61057c565b6001600160a01b038916600090815260036020526040812080547f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9928c928c928c929091906109a9836118e1565b909155506040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810187905260e00160405160208183030381529060405280519060200120604051602001610a3d9291907f190100000000000000000000000000000000000000000000000000000000000081526002810192909252602282015260420190565b60408051601f198184030181528282528051602091820120600080855291840180845281905260ff88169284019290925260608301869052608083018590529092509060019060a0016020604051602081039080840390855afa158015610aa8573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811615801590610ade5750886001600160a01b0316816001600160a01b0316145b610b2a5760405162461bcd60e51b815260206004820152601160248201527f496e76616c6964207369676e61747572650000000000000000000000000000006044820152606401610547565b610b35898989610c50565b505050505050505050565b610b48610d5e565b6001600160a01b038116610bc45760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610547565b6106c8816111ee565b610bd5610d5e565b6040517fb88d4fde0000000000000000000000000000000000000000000000000000000081526001600160a01b0386169063b88d4fde90610c2290309088908890889088906004016118fa565b600060405180830381600087803b158015610c3c57600080fd5b505af1158015610b35573d6000803e3d6000fd5b6001600160a01b038316610ca65760405162461bcd60e51b815260206004820152601d60248201527f417070726f76652066726f6d20746865207a65726f20616464726573730000006044820152606401610547565b6001600160a01b038216610cfc5760405162461bcd60e51b815260206004820152601b60248201527f417070726f766520746f20746865207a65726f206164647265737300000000006044820152606401610547565b6001600160a01b0383811660008181526002602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000546001600160a01b031633146106dd5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610547565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001790526104a3908490611256565b6001600160a01b038316610e8e5760405162461bcd60e51b815260206004820152601e60248201527f5472616e736665722066726f6d20746865207a65726f206164647265737300006044820152606401610547565b6001600160a01b038216610ee45760405162461bcd60e51b815260206004820152601c60248201527f5472616e7366657220746f20746865207a65726f2061646472657373000000006044820152606401610547565b306001600160a01b03831603610f3c5760405162461bcd60e51b815260206004820152601d60248201527f5472616e7366657220746f2074686520746f6b656e20616464726573730000006044820152606401610547565b6001600160a01b03831660009081526001602052604090205481811015610fa55760405162461bcd60e51b815260206004820152601f60248201527f5472616e7366657220616d6f756e7420657863656564732062616c616e6365006044820152606401610547565b610faf828261187f565b6001600160a01b038086166000908152600160205260408082209390935590851681529081208054849290610fe5908490611892565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161103191815260200190565b60405180910390a350505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6005604051611071919061194e565b604080519182900382208282018252600183527f31000000000000000000000000000000000000000000000000000000000000006020938401528151928301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b6001600160a01b038216600090815260016020526040902054818110156111735760405162461bcd60e51b815260206004820152601b60248201527f4275726e20616d6f756e7420657863656564732062616c616e636500000000006044820152606401610547565b61117d828261187f565b6001600160a01b038416600090815260016020526040812091909155600480548492906111ab90849061187f565b90915550506040518281526000906001600160a01b038516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610d51565b600080546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006112ab826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661133b9092919063ffffffff16565b8051909150156104a357808060200190518101906112c991906119ed565b6104a35760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610547565b606061134a8484600085611352565b949350505050565b6060824710156113ca5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610547565b600080866001600160a01b031685876040516113e69190611a0f565b60006040518083038185875af1925050503d8060008114611423576040519150601f19603f3d011682016040523d82523d6000602084013e611428565b606091505b509150915061143987838387611444565b979650505050505050565b606083156114b35782516000036114ac576001600160a01b0385163b6114ac5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610547565b508161134a565b61134a83838151156114c85781518083602001fd5b8060405162461bcd60e51b81526004016105479190611532565b60005b838110156114fd5781810151838201526020016114e5565b50506000910152565b6000815180845261151e8160208601602086016114e2565b601f01601f19169290920160200192915050565b6020815260006105756020830184611506565b6001600160a01b03811681146106c857600080fd5b6000806040838503121561156d57600080fd5b823561157881611545565b946020939093013593505050565b60008060006060848603121561159b57600080fd5b83356115a681611545565b925060208401356115b681611545565b929592945050506040919091013590565b6000602082840312156115d957600080fd5b5035919050565b6000602082840312156115f257600080fd5b813561057581611545565b634e487b7160e01b600052604160045260246000fd5b60008060006060848603121561162857600080fd5b833561163381611545565b925060208401359150604084013567ffffffffffffffff8082111561165757600080fd5b818601915086601f83011261166b57600080fd5b81358181111561167d5761167d6115fd565b604051601f8201601f19908116603f011681019083821181831017156116a5576116a56115fd565b816040528281528960208487010111156116be57600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b600080600080600080600060e0888a0312156116fb57600080fd5b873561170681611545565b9650602088013561171681611545565b95506040880135945060608801359350608088013560ff8116811461173a57600080fd5b9699959850939692959460a0840135945060c09093013592915050565b6000806040838503121561176a57600080fd5b823561177581611545565b9150602083013561178581611545565b809150509250929050565b6000806000806000608086880312156117a857600080fd5b85356117b381611545565b945060208601356117c381611545565b935060408601359250606086013567ffffffffffffffff808211156117e757600080fd5b818801915088601f8301126117fb57600080fd5b81358181111561180a57600080fd5b89602082850101111561181c57600080fd5b9699959850939650602001949392505050565b600181811c9082168061184357607f821691505b60208210810361186357634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561048157610481611869565b8082018082111561048157610481611869565b60006001600160a01b038087168352856020840152808516604084015250608060608301526118d76080830184611506565b9695505050505050565b6000600182016118f3576118f3611869565b5060010190565b60006001600160a01b03808816835280871660208401525084604083015260806060830152826080830152828460a0840137600060a0848401015260a0601f19601f85011683010190509695505050505050565b600080835481600182811c91508083168061196a57607f831692505b6020808410820361198957634e487b7160e01b86526022600452602486fd5b81801561199d57600181146119b2576119df565b60ff19861689528415158502890196506119df565b60008a81526020902060005b868110156119d75781548b8201529085019083016119be565b505084890196505b509498975050505050505050565b6000602082840312156119ff57600080fd5b8151801515811461057557600080fd5b60008251611a218184602087016114e2565b919091019291505056fea2646970667358221220ac40ec86f0507767ccbaf14ca6220c6808fea4caff0cd29f52eba919adea057664736f6c63430008110033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101985760003560e01c8063715018a6116100e3578063b4f94b2e1161008c578063dd62ed3e11610066578063dd62ed3e14610391578063f2fde38b146103bc578063fc4e51f6146103cf57600080fd5b8063b4f94b2e14610344578063cae9ca511461036b578063d505accf1461037e57600080fd5b80638da5cb5b116100bd5780638da5cb5b1461030e57806395d89b4114610329578063a9059cbb1461033157600080fd5b8063715018a6146102cc578063771da5c5146102d457806379cc6790146102fb57600080fd5b8063313ce5671161014557806342966c681161011f57806342966c681461027957806370a082311461028c57806370ae92d2146102ac57600080fd5b8063313ce567146102445780633644e5151461025e57806340c10f191461026657600080fd5b806318160ddd1161017657806318160ddd146101f357806323b872dd1461020a57806330adf81f1461021d57600080fd5b806306fdde031461019d578063095ea7b3146101bb5780631171bda9146101de575b600080fd5b6101a56103e2565b6040516101b29190611532565b60405180910390f35b6101ce6101c936600461155a565b610470565b60405190151581526020016101b2565b6101f16101ec366004611586565b610487565b005b6101fc60045481565b6040519081526020016101b2565b6101ce610218366004611586565b6104a8565b6101fc7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b61024c601281565b60405160ff90911681526020016101b2565b6101fc61057c565b6101f161027436600461155a565b6105d7565b6101f16102873660046115c7565b6106be565b6101fc61029a3660046115e0565b60016020526000908152604090205481565b6101fc6102ba3660046115e0565b60036020526000908152604090205481565b6101f16106cb565b6101fc7f000000000000000000000000000000000000000000000000000000000000000081565b6101f161030936600461155a565b6106df565b6000546040516001600160a01b0390911681526020016101b2565b6101a5610775565b6101ce61033f36600461155a565b610782565b6101fc7f000000000000000000000000000000000000000000000000000000000000000081565b6101ce610379366004611613565b61078f565b6101f161038c3660046116e0565b610830565b6101fc61039f366004611757565b600260209081526000928352604080842090915290825290205481565b6101f16103ca3660046115e0565b610b40565b6101f16103dd366004611790565b610bcd565b600580546103ef9061182f565b80601f016020809104026020016040519081016040528092919081815260200182805461041b9061182f565b80156104685780601f1061043d57610100808354040283529160200191610468565b820191906000526020600020905b81548152906001019060200180831161044b57829003601f168201915b505050505081565b600061047d338484610c50565b5060015b92915050565b61048f610d5e565b6104a36001600160a01b0384168383610db8565b505050565b6001600160a01b0383166000908152600260209081526040808320338452909152812054600019811461056457828110156105505760405162461bcd60e51b815260206004820152602160248201527f5472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6360448201527f650000000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610564853361055f868561187f565b610c50565b61056f858585610e38565b60019150505b9392505050565b60007f000000000000000000000000000000000000000000000000000000000000000046036105ca57507f000000000000000000000000000000000000000000000000000000000000000090565b6105d261103f565b905090565b6105df610d5e565b6001600160a01b0382166106355760405162461bcd60e51b815260206004820152601860248201527f4d696e7420746f20746865207a65726f206164647265737300000000000000006044820152606401610547565b80600460008282546106479190611892565b90915550506001600160a01b03821660009081526001602052604081208054839290610674908490611892565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b6106c8338261110a565b50565b6106d3610d5e565b6106dd60006111ee565b565b6001600160a01b0382166000908152600260209081526040808320338452909152902054600019811461076b578181101561075c5760405162461bcd60e51b815260206004820152601d60248201527f4275726e20616d6f756e74206578636565647320616c6c6f77616e63650000006044820152606401610547565b61076b833361055f858561187f565b6104a3838361110a565b600680546103ef9061182f565b600061047d338484610e38565b600061079b8484610470565b15610826576040517f8f4ffcb10000000000000000000000000000000000000000000000000000000081526001600160a01b03851690638f4ffcb1906107eb9033908790309088906004016118a5565b600060405180830381600087803b15801561080557600080fd5b505af1158015610819573d6000803e3d6000fd5b5050505060019050610575565b5060009392505050565b428410156108805760405162461bcd60e51b815260206004820152601260248201527f5065726d697373696f6e206578706972656400000000000000000000000000006044820152606401610547565b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08111156108f05760405162461bcd60e51b815260206004820152601b60248201527f496e76616c6964207369676e6174757265202773272076616c756500000000006044820152606401610547565b8260ff16601b148061090557508260ff16601c145b6109515760405162461bcd60e51b815260206004820152601b60248201527f496e76616c6964207369676e6174757265202776272076616c756500000000006044820152606401610547565b600061095b61057c565b6001600160a01b038916600090815260036020526040812080547f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9928c928c928c929091906109a9836118e1565b909155506040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810187905260e00160405160208183030381529060405280519060200120604051602001610a3d9291907f190100000000000000000000000000000000000000000000000000000000000081526002810192909252602282015260420190565b60408051601f198184030181528282528051602091820120600080855291840180845281905260ff88169284019290925260608301869052608083018590529092509060019060a0016020604051602081039080840390855afa158015610aa8573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811615801590610ade5750886001600160a01b0316816001600160a01b0316145b610b2a5760405162461bcd60e51b815260206004820152601160248201527f496e76616c6964207369676e61747572650000000000000000000000000000006044820152606401610547565b610b35898989610c50565b505050505050505050565b610b48610d5e565b6001600160a01b038116610bc45760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610547565b6106c8816111ee565b610bd5610d5e565b6040517fb88d4fde0000000000000000000000000000000000000000000000000000000081526001600160a01b0386169063b88d4fde90610c2290309088908890889088906004016118fa565b600060405180830381600087803b158015610c3c57600080fd5b505af1158015610b35573d6000803e3d6000fd5b6001600160a01b038316610ca65760405162461bcd60e51b815260206004820152601d60248201527f417070726f76652066726f6d20746865207a65726f20616464726573730000006044820152606401610547565b6001600160a01b038216610cfc5760405162461bcd60e51b815260206004820152601b60248201527f417070726f766520746f20746865207a65726f206164647265737300000000006044820152606401610547565b6001600160a01b0383811660008181526002602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000546001600160a01b031633146106dd5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610547565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001790526104a3908490611256565b6001600160a01b038316610e8e5760405162461bcd60e51b815260206004820152601e60248201527f5472616e736665722066726f6d20746865207a65726f206164647265737300006044820152606401610547565b6001600160a01b038216610ee45760405162461bcd60e51b815260206004820152601c60248201527f5472616e7366657220746f20746865207a65726f2061646472657373000000006044820152606401610547565b306001600160a01b03831603610f3c5760405162461bcd60e51b815260206004820152601d60248201527f5472616e7366657220746f2074686520746f6b656e20616464726573730000006044820152606401610547565b6001600160a01b03831660009081526001602052604090205481811015610fa55760405162461bcd60e51b815260206004820152601f60248201527f5472616e7366657220616d6f756e7420657863656564732062616c616e6365006044820152606401610547565b610faf828261187f565b6001600160a01b038086166000908152600160205260408082209390935590851681529081208054849290610fe5908490611892565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161103191815260200190565b60405180910390a350505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6005604051611071919061194e565b604080519182900382208282018252600183527f31000000000000000000000000000000000000000000000000000000000000006020938401528151928301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b6001600160a01b038216600090815260016020526040902054818110156111735760405162461bcd60e51b815260206004820152601b60248201527f4275726e20616d6f756e7420657863656564732062616c616e636500000000006044820152606401610547565b61117d828261187f565b6001600160a01b038416600090815260016020526040812091909155600480548492906111ab90849061187f565b90915550506040518281526000906001600160a01b038516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610d51565b600080546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006112ab826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661133b9092919063ffffffff16565b8051909150156104a357808060200190518101906112c991906119ed565b6104a35760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610547565b606061134a8484600085611352565b949350505050565b6060824710156113ca5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610547565b600080866001600160a01b031685876040516113e69190611a0f565b60006040518083038185875af1925050503d8060008114611423576040519150601f19603f3d011682016040523d82523d6000602084013e611428565b606091505b509150915061143987838387611444565b979650505050505050565b606083156114b35782516000036114ac576001600160a01b0385163b6114ac5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610547565b508161134a565b61134a83838151156114c85781518083602001fd5b8060405162461bcd60e51b81526004016105479190611532565b60005b838110156114fd5781810151838201526020016114e5565b50506000910152565b6000815180845261151e8160208601602086016114e2565b601f01601f19169290920160200192915050565b6020815260006105756020830184611506565b6001600160a01b03811681146106c857600080fd5b6000806040838503121561156d57600080fd5b823561157881611545565b946020939093013593505050565b60008060006060848603121561159b57600080fd5b83356115a681611545565b925060208401356115b681611545565b929592945050506040919091013590565b6000602082840312156115d957600080fd5b5035919050565b6000602082840312156115f257600080fd5b813561057581611545565b634e487b7160e01b600052604160045260246000fd5b60008060006060848603121561162857600080fd5b833561163381611545565b925060208401359150604084013567ffffffffffffffff8082111561165757600080fd5b818601915086601f83011261166b57600080fd5b81358181111561167d5761167d6115fd565b604051601f8201601f19908116603f011681019083821181831017156116a5576116a56115fd565b816040528281528960208487010111156116be57600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b600080600080600080600060e0888a0312156116fb57600080fd5b873561170681611545565b9650602088013561171681611545565b95506040880135945060608801359350608088013560ff8116811461173a57600080fd5b9699959850939692959460a0840135945060c09093013592915050565b6000806040838503121561176a57600080fd5b823561177581611545565b9150602083013561178581611545565b809150509250929050565b6000806000806000608086880312156117a857600080fd5b85356117b381611545565b945060208601356117c381611545565b935060408601359250606086013567ffffffffffffffff808211156117e757600080fd5b818801915088601f8301126117fb57600080fd5b81358181111561180a57600080fd5b89602082850101111561181c57600080fd5b9699959850939650602001949392505050565b600181811c9082168061184357607f821691505b60208210810361186357634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561048157610481611869565b8082018082111561048157610481611869565b60006001600160a01b038087168352856020840152808516604084015250608060608301526118d76080830184611506565b9695505050505050565b6000600182016118f3576118f3611869565b5060010190565b60006001600160a01b03808816835280871660208401525084604083015260806060830152826080830152828460a0840137600060a0848401015260a0601f19601f85011683010190509695505050505050565b600080835481600182811c91508083168061196a57607f831692505b6020808410820361198957634e487b7160e01b86526022600452602486fd5b81801561199d57600181146119b2576119df565b60ff19861689528415158502890196506119df565b60008a81526020902060005b868110156119d75781548b8201529085019083016119be565b505084890196505b509498975050505050505050565b6000602082840312156119ff57600080fd5b8151801515811461057557600080fd5b60008251611a218184602087016114e2565b919091019291505056fea2646970667358221220ac40ec86f0507767ccbaf14ca6220c6808fea4caff0cd29f52eba919adea057664736f6c63430008110033", + "devdoc": { + "kind": "dev", + "methods": { + "approve(address,uint256)": { + "details": "If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance. Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729", + "returns": { + "_0": "True if the operation succeeded." + } + }, + "approveAndCall(address,uint256,bytes)": { + "details": "If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.", + "returns": { + "_0": "True if both approval and `receiveApproval` calls succeeded." + } + }, + "burn(uint256)": { + "details": "Requirements: - the caller must have a balance of at least `amount`." + }, + "burnFrom(address,uint256)": { + "details": "Requirements: - `account` must have a balance of at least `amount`, - the caller must have allowance for `account`'s tokens of at least `amount`." + }, + "mint(address,uint256)": { + "details": "Requirements: - `recipient` cannot be the zero address." + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { + "details": "The deadline argument can be set to `type(uint256).max to create permits that effectively never expire. If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance." + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner." + }, + "transfer(address,uint256)": { + "details": "Requirements: - `recipient` cannot be the zero address, - the caller must have a balance of at least `amount`.", + "returns": { + "_0": "True if the operation succeeded, reverts otherwise." + } + }, + "transferFrom(address,address,uint256)": { + "details": "Requirements: - `spender` and `recipient` cannot be the zero address, - `spender` must have a balance of at least `amount`, - the caller must have allowance for `spender`'s tokens of at least `amount`.", + "returns": { + "_0": "True if the operation succeeded, reverts otherwise." + } + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "DOMAIN_SEPARATOR()": { + "notice": "Returns hash of EIP712 Domain struct with the token name as a signing domain and token contract as a verifying contract. Used to construct EIP2612 signature provided to `permit` function." + }, + "PERMIT_TYPEHASH()": { + "notice": "Returns EIP2612 Permit message hash. Used to construct EIP2612 signature provided to `permit` function." + }, + "allowance(address,address)": { + "notice": "The remaining number of tokens that spender will be allowed to spend on behalf of owner through `transferFrom` and `burnFrom`. This is zero by default." + }, + "approve(address,uint256)": { + "notice": "Sets `amount` as the allowance of `spender` over the caller's tokens." + }, + "approveAndCall(address,uint256,bytes)": { + "notice": "Calls `receiveApproval` function on spender previously approving the spender to withdraw from the caller multiple times, up to the `amount` amount. If this function is called again, it overwrites the current allowance with `amount`. Reverts if the approval reverted or if `receiveApproval` call on the spender reverted." + }, + "balanceOf(address)": { + "notice": "The amount of tokens owned by the given account." + }, + "burn(uint256)": { + "notice": "Destroys `amount` tokens from the caller." + }, + "burnFrom(address,uint256)": { + "notice": "Destroys `amount` of tokens from `account` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`." + }, + "decimals()": { + "notice": "The decimals places of the token." + }, + "mint(address,uint256)": { + "notice": "Creates `amount` tokens and assigns them to `account`, increasing the total supply." + }, + "name()": { + "notice": "The name of the token." + }, + "nonce(address)": { + "notice": "Returns the current nonce for EIP2612 permission for the provided token owner for a replay protection. Used to construct EIP2612 signature provided to `permit` function." + }, + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { + "notice": "EIP2612 approval made with secp256k1 signature. Users can authorize a transfer of their tokens with a signature conforming EIP712 standard, rather than an on-chain transaction from their address. Anyone can submit this signature on the user's behalf by calling the permit function, paying gas fees, and possibly performing other actions in the same transaction." + }, + "symbol()": { + "notice": "The symbol of the token." + }, + "totalSupply()": { + "notice": "The amount of tokens in existence." + }, + "transfer(address,uint256)": { + "notice": "Moves `amount` tokens from the caller's account to `recipient`." + }, + "transferFrom(address,address,uint256)": { + "notice": "Moves `amount` tokens from `spender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`." + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 17269, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 21530, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "balanceOf", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 21538, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "allowance", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 21544, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "nonce", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 21559, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "totalSupply", + "offset": 0, + "slot": "4", + "type": "t_uint256" + }, + { + "astId": 21563, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "name", + "offset": 0, + "slot": "5", + "type": "t_string_storage" + }, + { + "astId": 21567, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "symbol", + "offset": 0, + "slot": "6", + "type": "t_string_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } +} \ No newline at end of file diff --git a/core/hardhat.config.ts b/core/hardhat.config.ts index 2390d0d19..9d8131afe 100644 --- a/core/hardhat.config.ts +++ b/core/hardhat.config.ts @@ -1,4 +1,4 @@ -import { HardhatUserConfig } from "hardhat/config" +import type { HardhatUserConfig } from "hardhat/config" import "@nomicfoundation/hardhat-toolbox" import "hardhat-deploy" @@ -44,6 +44,13 @@ const config: HardhatUserConfig = { }, }, + external: { + deployments: { + sepolia: ["./external/sepolia"], + mainnet: ["./external/mainnet"], + }, + }, + etherscan: { apiKey: { sepolia: process.env.ETHERSCAN_API_KEY, diff --git a/core/scripts/fetch_external_artifacts.sh b/core/scripts/fetch_external_artifacts.sh new file mode 100755 index 000000000..a15c0f108 --- /dev/null +++ b/core/scripts/fetch_external_artifacts.sh @@ -0,0 +1,48 @@ +#! /bin/bash +set -eou pipefail + +ROOT_DIR="$(realpath "$(dirname $0)/../")" +TMP_DIR=${ROOT_DIR}/tmp/external-artifacts +EXTERNAL_ARTIFACTS_DIR=${ROOT_DIR}/external + +# Prepare temporary directory for NPM packages. +rm -rf ${TMP_DIR} +mkdir -p ${TMP_DIR} + +# fetch_external_artifact is a function that fetches a contract deployment artifact +# from a package published to the NPM registry. It assumes a package is published +# following the rules established by Keep Network deployments: +# 1. Packages are tagged with network name and contain the latest version of +# deployment artifacts for the given network. +# 2. Deployment artfiacts files located under `artifacts/` directory. +# 3. Deployment artifacts are JSON files with a file name corresponding to the +# contract deployment name. +fetch_external_artifact() { + # Tag used for the package. + network=$1 + # Package name. + package=$2 + # Name of the deployment artifact file. + contractName=$3 + # Destination directory to save the extracted artifact. + destination_dir=${EXTERNAL_ARTIFACTS_DIR}/${network} + + # Resolve a package ID for package and network tag. + package=$(npm view ${package}@${network} _id) + + # Download compressed NPM package to a temporary directory. + npm pack --silent \ + --pack-destination=${TMP_DIR} \ + ${package} | + # Extract deployment artifact to the destination directory. + xargs -I{} tar -zxf ${TMP_DIR}/{} -C ${destination_dir} --strip-components 2 package/artifacts/${contractName}.json + + printf "Succesfully fetched ${contractName} contract artifact from ${package} to ${destination_dir}\n" +} + +# Fetch TBTC contract from @keep-network/tbtc-v2 package. +fetch_external_artifact "mainnet" "@keep-network/tbtc-v2" "TBTC" +fetch_external_artifact "sepolia" "@keep-network/tbtc-v2" "TBTC" + +# Remove downloaded NPM packages. +rm -rf ${TMP_DIR} From 63aac1b03c50b43ea5bd2362345b6bcd4b3ccfe7 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 4 Dec 2023 06:18:08 +0100 Subject: [PATCH 36/89] Use unnamed return in the `stake` fn We preffer unnamed return here. --- core/contracts/Acre.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index fda7ab237..2d67a3151 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -21,8 +21,8 @@ contract Acre is ERC4626 { uint256 assets, address receiver, bytes32 referral - ) public returns (uint256 shares) { - shares = deposit(assets, receiver); + ) public returns (uint256) { + uint256 shares = deposit(assets, receiver); emit Staked(referral, assets, shares); From e923f5ee03640f9596374c0a76cc777549f2ab48 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 4 Dec 2023 06:21:31 +0100 Subject: [PATCH 37/89] Leave TODO in the `stake` fn We should probably revisit the type of `referral` in the future. --- core/contracts/Acre.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 2d67a3151..c2fc1631b 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -22,6 +22,7 @@ contract Acre is ERC4626 { address receiver, bytes32 referral ) public returns (uint256) { + // TODO: revisit the type of referral. uint256 shares = deposit(assets, receiver); emit Staked(referral, assets, shares); From 5403ac3ccea60e8014109af9cab3d703b1f53434 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 4 Dec 2023 06:41:32 +0100 Subject: [PATCH 38/89] Rename constructor parameter in Acre `_token` -> `tbtc`. We know that this will only be the tBTC token, so we want to have a clear parameter name. --- core/contracts/Acre.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index c2fc1631b..2cac7beed 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -7,10 +7,10 @@ contract Acre is ERC4626 { event Staked(bytes32 indexed referral, uint256 assets, uint256 shares); constructor( - IERC20 _token - ) ERC4626(_token) ERC20("Acre Staked Bitcoin", "stBTC") {} + IERC20 tbtc + ) ERC4626(tbtc) ERC20("Acre Staked Bitcoin", "stBTC") {} - /// @notice Stakes a given amount of underlying token and mints shares to a + /// @notice Stakes a given amount of tBTC token and mints shares to a /// receiver. /// @dev This function calls `deposit` function from `ERC4626` contract. /// @param assets Approved amount for the transfer and stake. From 6511fefeb016da4a3f75a0b3ee21a1c2b23f0ffc Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 4 Dec 2023 07:33:18 +0100 Subject: [PATCH 39/89] Remove `@thesis/solidity-contracts` dependency Since we decided to not deal with receive approval pattern during the staking work this dependency is no longer needed at this stage. --- core/contracts/test/TestToken.sol | 21 +-------------------- core/package.json | 3 +-- core/test/Acre.test.ts | 6 +++--- pnpm-lock.yaml | 15 --------------- 4 files changed, 5 insertions(+), 40 deletions(-) diff --git a/core/contracts/test/TestToken.sol b/core/contracts/test/TestToken.sol index 5a83eabd8..44e5e14dc 100644 --- a/core/contracts/test/TestToken.sol +++ b/core/contracts/test/TestToken.sol @@ -2,30 +2,11 @@ pragma solidity ^0.8.21; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@thesis/solidity-contracts/contracts/token/IApproveAndCall.sol"; -import "@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol"; -contract TestToken is ERC20, IApproveAndCall { +contract TestERC20 is ERC20 { constructor() ERC20("Test Token", "TEST") {} function mint(address account, uint256 value) external { _mint(account, value); } - - function approveAndCall( - address spender, - uint256 amount, - bytes memory extraData - ) external returns (bool) { - if (approve(spender, amount)) { - IReceiveApproval(spender).receiveApproval( - msg.sender, - amount, - address(this), - extraData - ); - return true; - } - return false; - } } diff --git a/core/package.json b/core/package.json index 783907808..603cb8643 100644 --- a/core/package.json +++ b/core/package.json @@ -57,7 +57,6 @@ "typescript": "^5.3.2" }, "dependencies": { - "@openzeppelin/contracts": "^5.0.0", - "@thesis/solidity-contracts": "github:thesis/solidity-contracts#c315b9d" + "@openzeppelin/contracts": "^5.0.0" } } diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index a3b714169..d8a703fe5 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -7,11 +7,11 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import { ethers } from "hardhat" import { expect } from "chai" import { WeiPerEther } from "ethers" -import type { TestToken, Acre } from "../typechain" +import type { TestERC20, Acre } from "../typechain" async function acreFixture() { const [_, staker] = await ethers.getSigners() - const Token = await ethers.getContractFactory("TestToken") + const Token = await ethers.getContractFactory("TestERC20") const tbtc = await Token.deploy() const amountToMint = WeiPerEther * 100000n @@ -26,7 +26,7 @@ async function acreFixture() { describe("Acre", () => { let acre: Acre - let tbtc: TestToken + let tbtc: TestERC20 let staker: HardhatEthersSigner before(async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d7a91c33..3e6723390 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,6 @@ importers: '@openzeppelin/contracts': specifier: ^5.0.0 version: 5.0.0 - '@thesis/solidity-contracts': - specifier: github:thesis/solidity-contracts#c315b9d - version: github.com/thesis/solidity-contracts/c315b9d devDependencies: '@nomicfoundation/hardhat-chai-matchers': specifier: ^2.0.2 @@ -4416,10 +4413,6 @@ packages: - supports-color dev: true - /@openzeppelin/contracts@4.9.3: - resolution: {integrity: sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg==} - dev: false - /@openzeppelin/contracts@5.0.0: resolution: {integrity: sha512-bv2sdS6LKqVVMLI5+zqnNrNU/CA+6z6CmwFXm/MzmOPBRSO5reEJN7z0Gbzvs0/bv/MZZXNklubpwy3v2+azsw==} dev: false @@ -15362,11 +15355,3 @@ packages: dependencies: solhint: 4.0.0 dev: true - - github.com/thesis/solidity-contracts/c315b9d: - resolution: {tarball: https://codeload.github.com/thesis/solidity-contracts/tar.gz/c315b9d} - name: '@thesis-co/solidity-contracts' - version: 0.0.1-pre - dependencies: - '@openzeppelin/contracts': 4.9.3 - dev: false From 84bdf8e9376aeeb1ffa159f04faa280aae10a761 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 4 Dec 2023 07:59:53 +0100 Subject: [PATCH 40/89] Add unit test heleprs Add `to1e18` function to deal with the token amount conversion. --- core/test/Acre.test.ts | 12 ++++++------ core/test/utils/index.ts | 1 + core/test/utils/number.ts | 10 ++++++++++ 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 core/test/utils/index.ts create mode 100644 core/test/utils/number.ts diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index d8a703fe5..eee9aff4d 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -6,15 +6,15 @@ import { import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import { ethers } from "hardhat" import { expect } from "chai" -import { WeiPerEther } from "ethers" import type { TestERC20, Acre } from "../typechain" +import { to1e18 } from "./utils" async function acreFixture() { const [_, staker] = await ethers.getSigners() const Token = await ethers.getContractFactory("TestERC20") const tbtc = await Token.deploy() - const amountToMint = WeiPerEther * 100000n + const amountToMint = to1e18(100000) tbtc.mint(staker, amountToMint) @@ -38,7 +38,7 @@ describe("Acre", () => { let snapshot: SnapshotRestorer context("when staking via Acre contract", () => { - const amountToStake = 1000n * WeiPerEther + const amountToStake = to1e18(1000) beforeEach(async () => { snapshot = await takeSnapshot() @@ -120,8 +120,8 @@ describe("Acre", () => { before(async () => { const [staker1, staker2] = await ethers.getSigners() - const staker1AmountToStake = WeiPerEther * 75n - const staker2AmountToStake = WeiPerEther * 25n + const staker1AmountToStake = to1e18(75) + const staker2AmountToStake = to1e18(25) // Infinite approval for staking contract. await tbtc .connect(staker1) @@ -213,7 +213,7 @@ describe("Acre", () => { stakerASharesBefore = await acre.balanceOf(stakerA.address) stakerBSharesBefore = await acre.balanceOf(stakerB.address) - vaultYield = WeiPerEther * 100n + vaultYield = to1e18(100) // Staker A shares = 75 // Staker B shares = 25 // Total assets = 75(staker A) + 25(staker) + 100(yield) diff --git a/core/test/utils/index.ts b/core/test/utils/index.ts new file mode 100644 index 000000000..6654cca0e --- /dev/null +++ b/core/test/utils/index.ts @@ -0,0 +1 @@ +export * from "./number" diff --git a/core/test/utils/number.ts b/core/test/utils/number.ts new file mode 100644 index 000000000..9784b499c --- /dev/null +++ b/core/test/utils/number.ts @@ -0,0 +1,10 @@ +export function to1ePrecision( + n: string | number | bigint, + precision: number, +): bigint { + return BigInt(n) * 10n ** BigInt(precision) +} + +export function to1e18(n: string | number | bigint): bigint { + return to1ePrecision(n, 18) +} From ce16fbade77fd216d5b0e1a90fc632f07d4e909b Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 4 Dec 2023 09:13:49 +0100 Subject: [PATCH 41/89] Add more unit tests for staking To cover more cases. --- core/test/Acre.test.ts | 185 ++++++++++++++++++++++++++++++----------- 1 file changed, 138 insertions(+), 47 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index eee9aff4d..9660ced74 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -6,6 +6,7 @@ import { import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import { ethers } from "hardhat" import { expect } from "chai" +import { ContractTransactionResponse, ZeroAddress } from "ethers" import type { TestERC20, Acre } from "../typechain" import { to1e18 } from "./utils" @@ -37,74 +38,164 @@ describe("Acre", () => { const referral = ethers.encodeBytes32String("referral") let snapshot: SnapshotRestorer - context("when staking via Acre contract", () => { - const amountToStake = to1e18(1000) - + context("when staking", () => { beforeEach(async () => { snapshot = await takeSnapshot() - - await tbtc - .connect(staker) - .approve(await acre.getAddress(), amountToStake) }) afterEach(async () => { await snapshot.restore() }) - it("should stake tokens and receive shares", async () => { - const stakerAddress = staker.address - const balanceOfBeforeStake = await tbtc.balanceOf(stakerAddress) - - const tx = await acre - .connect(staker) - .stake(amountToStake, stakerAddress, referral) - - const stakedTokens = amountToStake + context("with a referral", () => { + const amountToStake = to1e18(1000) // In this test case there is only one staker and // the token vault has not earned anythig yet so received shares are // equal to staked tokens amount. - const receivedShares = amountToStake - - expect(tx).to.emit(acre, "Deposit").withArgs( - // Caller. - stakerAddress, - // Receiver. - stakerAddress, - // Staked tokens. - stakedTokens, - // Received shares. - receivedShares, - ) - - expect(tx) - .to.emit(acre, "Staked") - .withArgs(referral, stakedTokens, receivedShares) - - expect(await acre.balanceOf(stakerAddress)).to.be.eq(amountToStake) - expect(await tbtc.balanceOf(stakerAddress)).to.be.eq( - balanceOfBeforeStake - amountToStake, - ) + const expectedReceivedShares = amountToStake + + let tx: ContractTransactionResponse + + beforeEach(async () => { + await tbtc + .connect(staker) + .approve(await acre.getAddress(), amountToStake) + + tx = await acre + .connect(staker) + .stake(amountToStake, staker.address, referral) + }) + + it("should emit Deposit event", () => { + expect(tx).to.emit(acre, "Deposit").withArgs( + // Caller. + staker.address, + // Receiver. + staker.address, + // Staked tokens. + amountToStake, + // Received shares. + expectedReceivedShares, + ) + }) + + it("should emit Staked event", () => { + expect(tx) + .to.emit(acre, "Staked") + .withArgs(referral, amountToStake, expectedReceivedShares) + }) + + it("should mint stBTC tokens", async () => { + await expect(tx).to.changeTokenBalances( + acre, + [staker.address], + [amountToStake], + ) + }) + + it("should transfer tBTC tokens", async () => { + await expect(tx).to.changeTokenBalances( + tbtc, + [staker.address, acre], + [-amountToStake, amountToStake], + ) + }) }) - it("should not revert if the referral is zero value", async () => { + context("without referral", () => { const emptyReferral = ethers.encodeBytes32String("") + let tx: ContractTransactionResponse + + beforeEach(async () => { + await tbtc.connect(staker).approve(await acre.getAddress(), 1) - await expect( - acre + tx = await acre .connect(staker) - .stake(amountToStake, staker.address, emptyReferral), - ).to.be.not.reverted + .stake(1, staker.address, emptyReferral) + }) + + it("should not revert", async () => { + await expect(tx).to.be.not.reverted + }) }) - it("should revert if a staker wants to stake more tokens than approved", async () => { - await expect( - acre + context( + "when amount to stake is greater than the approved amount", + () => { + const amountToStake = to1e18(10) + beforeEach(async () => { + await tbtc + .connect(staker) + .approve(await acre.getAddress(), amountToStake) + }) + + it("should revert", async () => { + await expect( + acre + .connect(staker) + .stake(amountToStake + 1n, staker.address, referral), + ).to.be.reverted + }) + }, + ) + + context("when amount to stake is 1", () => { + const amountToStake = 1 + + beforeEach(async () => { + await tbtc .connect(staker) - .stake(amountToStake + 1n, staker.address, referral), - ).to.be.reverted + .approve(await acre.getAddress(), amountToStake) + }) + + it("should not revert", async () => { + await expect( + acre.connect(staker).stake(amountToStake, staker.address, referral), + ).to.not.be.reverted + }) }) + + context("when the receiver is zero address", () => { + const amountToStake = to1e18(10) + + beforeEach(async () => { + await tbtc + .connect(staker) + .approve(await acre.getAddress(), amountToStake) + }) + + it("should revert", async () => { + await expect( + acre.connect(staker).stake(amountToStake, ZeroAddress, referral), + ).to.be.revertedWithCustomError(acre, "ERC20InvalidReceiver") + }) + }) + + context( + "when a staker approved and staked tokens and wants to stake more but w/o another apporval", + () => { + const amountToStake = to1e18(10) + + beforeEach(async () => { + await tbtc + .connect(staker) + .approve(await acre.getAddress(), amountToStake) + + await acre + .connect(staker) + .stake(amountToStake, staker.address, referral) + }) + + it("should revert", async () => { + await expect( + acre + .connect(staker) + .stake(amountToStake, staker.address, referral), + ).to.be.revertedWithCustomError(acre, "ERC20InsufficientAllowance") + }) + }, + ) }) context("when there are two stakers, A and B ", () => { From c3a2bd89d37d4d3ee9b73fbe3beb3d7a59af248b Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 4 Dec 2023 09:38:52 +0100 Subject: [PATCH 42/89] Update solhint config We fixed this rule in the `thesis/solhint-config` repo see https://github.com/thesis/solhint-config/pull/3 so we can remove it from this config. --- core/.solhint.json | 5 +---- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/core/.solhint.json b/core/.solhint.json index b53dba35b..f54af0b9a 100644 --- a/core/.solhint.json +++ b/core/.solhint.json @@ -1,7 +1,4 @@ { "extends": "thesis", - "plugins": [], - "rules": { - "func-visibility": ["off", { "ignoreConstructors": true }] - } + "plugins": [] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e6723390..9a82d03ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,7 +92,7 @@ importers: version: 4.0.0 solhint-config-thesis: specifier: github:thesis/solhint-config - version: github.com/thesis/solhint-config/159587cd6103f21aaa7fbadc17a06717a51b5b42(solhint@4.0.0) + version: github.com/thesis/solhint-config/266de12d96d58f01171e20858b855ec80520de8d(solhint@4.0.0) solidity-coverage: specifier: ^0.8.5 version: 0.8.5(hardhat@2.19.1) @@ -15344,9 +15344,9 @@ packages: prettier: 3.1.0 dev: true - github.com/thesis/solhint-config/159587cd6103f21aaa7fbadc17a06717a51b5b42(solhint@4.0.0): - resolution: {tarball: https://codeload.github.com/thesis/solhint-config/tar.gz/159587cd6103f21aaa7fbadc17a06717a51b5b42} - id: github.com/thesis/solhint-config/159587cd6103f21aaa7fbadc17a06717a51b5b42 + github.com/thesis/solhint-config/266de12d96d58f01171e20858b855ec80520de8d(solhint@4.0.0): + resolution: {tarball: https://codeload.github.com/thesis/solhint-config/tar.gz/266de12d96d58f01171e20858b855ec80520de8d} + id: github.com/thesis/solhint-config/266de12d96d58f01171e20858b855ec80520de8d name: solhint-config-thesis version: 0.1.0 engines: {node: '>=0.10.0'} From b20259eb8cacb331eaecb4117284ec70d03898dc Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 4 Dec 2023 10:19:17 +0100 Subject: [PATCH 43/89] Add more stakers to the Acre test fixture --- core/test/Acre.test.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 9660ced74..4a31307fd 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -11,27 +11,29 @@ import type { TestERC20, Acre } from "../typechain" import { to1e18 } from "./utils" async function acreFixture() { - const [_, staker] = await ethers.getSigners() + const [staker, staker2] = await ethers.getSigners() const Token = await ethers.getContractFactory("TestERC20") const tbtc = await Token.deploy() const amountToMint = to1e18(100000) tbtc.mint(staker, amountToMint) + tbtc.mint(staker2, amountToMint) const Acre = await ethers.getContractFactory("Acre") const acre = await Acre.deploy(await tbtc.getAddress()) - return { acre, tbtc, staker } + return { acre, tbtc, staker, staker2 } } describe("Acre", () => { let acre: Acre let tbtc: TestERC20 let staker: HardhatEthersSigner + let staker2: HardhatEthersSigner before(async () => { - ;({ acre, tbtc, staker } = await loadFixture(acreFixture)) + ;({ acre, tbtc, staker, staker2 } = await loadFixture(acreFixture)) }) describe("Staking", () => { @@ -210,24 +212,23 @@ describe("Acre", () => { let afterSimulatingYieldSnapshot: SnapshotRestorer before(async () => { - const [staker1, staker2] = await ethers.getSigners() const staker1AmountToStake = to1e18(75) const staker2AmountToStake = to1e18(25) // Infinite approval for staking contract. await tbtc - .connect(staker1) + .connect(staker) .approve(await acre.getAddress(), ethers.MaxUint256) await tbtc .connect(staker2) .approve(await acre.getAddress(), ethers.MaxUint256) // Mint tokens. - await tbtc.connect(staker1).mint(staker1.address, staker1AmountToStake) + await tbtc.connect(staker).mint(staker.address, staker1AmountToStake) await tbtc.connect(staker2).mint(staker2.address, staker2AmountToStake) stakerA = { - signer: staker1, - address: staker1.address, + signer: staker, + address: staker.address, amountToStake: staker1AmountToStake, } stakerB = { From 7530c3d2c01b5ed5b64d84c2276d82c2ea4819e0 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Mon, 4 Dec 2023 13:46:46 +0100 Subject: [PATCH 44/89] Transfer ownership of Acre contract on deployment Ownership of Acre contract will have to be transferred once Acre extends Ownable. --- core/deploy/21_transfer_ownership_acre.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 core/deploy/21_transfer_ownership_acre.ts diff --git a/core/deploy/21_transfer_ownership_acre.ts b/core/deploy/21_transfer_ownership_acre.ts new file mode 100644 index 000000000..c62708641 --- /dev/null +++ b/core/deploy/21_transfer_ownership_acre.ts @@ -0,0 +1,23 @@ +import type { HardhatRuntimeEnvironment } from "hardhat/types" +import type { DeployFunction } from "hardhat-deploy/types" + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { getNamedAccounts, deployments } = hre + const { deployer, governance } = await getNamedAccounts() + const { log } = deployments + + log(`transferring ownership of Acre contract to ${governance}`) + + await deployments.execute( + "Acre", + { from: deployer, log: true, waitConfirmations: 1 }, + "transferOwnership", + governance, + ) +} + +export default func + +func.tags = ["TransferOwnershipAcre"] +// TODO: Enable once Acre extends Ownable +func.skip = async () => true From 300b2a916124d52a442f0642bb293b58296f3c9b Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 09:59:46 +0100 Subject: [PATCH 45/89] Rename variable in Acre unit tests `staker1AmountToStake` -> `stakerAmountToStake`. Just for consistency with the `staker` variable (we do not add `1` suffix). --- core/test/Acre.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 4a31307fd..d52c23876 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -212,7 +212,7 @@ describe("Acre", () => { let afterSimulatingYieldSnapshot: SnapshotRestorer before(async () => { - const staker1AmountToStake = to1e18(75) + const stakerAmountToStake = to1e18(75) const staker2AmountToStake = to1e18(25) // Infinite approval for staking contract. await tbtc @@ -223,13 +223,13 @@ describe("Acre", () => { .approve(await acre.getAddress(), ethers.MaxUint256) // Mint tokens. - await tbtc.connect(staker).mint(staker.address, staker1AmountToStake) + await tbtc.connect(staker).mint(staker.address, stakerAmountToStake) await tbtc.connect(staker2).mint(staker2.address, staker2AmountToStake) stakerA = { signer: staker, address: staker.address, - amountToStake: staker1AmountToStake, + amountToStake: stakerAmountToStake, } stakerB = { signer: staker2, From 44b3f858d41568f282b9b3d9a9686506fb2bd68f Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 10:07:42 +0100 Subject: [PATCH 46/89] Update comment in Acre unit tests To clarify that the staker `B` stakes a given amount of tokens. --- core/test/Acre.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index d52c23876..496d68e97 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -308,7 +308,7 @@ describe("Acre", () => { vaultYield = to1e18(100) // Staker A shares = 75 // Staker B shares = 25 - // Total assets = 75(staker A) + 25(staker) + 100(yield) + // Total assets = 75(staker A) + 25(staker B) + 100(yield) expectedTotalAssets = stakerA.amountToStake + stakerB.amountToStake + vaultYield // Total shares = 75 + 25 = 100 From b84ee1bf108732532f5f04dd9601c1d8589d4514 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 11:47:46 +0100 Subject: [PATCH 47/89] Update staking flow unit tests Use more concrete numbers, as we did in previous tests, and put some math in the comments to make the calculations easier to follow. --- core/test/Acre.test.ts | 43 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 496d68e97..369f80704 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -373,26 +373,63 @@ describe("Acre", () => { }) context("when staker A stakes more tokens", () => { + const newAmountToStake = to1e18(20) let sharesBefore: bigint let availableToRedeemBefore: bigint + let totalAssetsBefore: bigint + let totalSupplyBefore: bigint + let expectedSharesToMint: bigint + let expectedTotalSupply: bigint + let expectedTotalAssets: bigint before(async () => { + // Current state: + // Total assets = 75(staker A) + 25(staker B) + 100(yield) + // Total shares = 75 + 25 = 100 await afterSimulatingYieldSnapshot.restore() sharesBefore = await acre.balanceOf(stakerA.address) availableToRedeemBefore = await acre.previewRedeem(sharesBefore) + totalAssetsBefore = await acre.totalAssets() + totalSupplyBefore = await acre.totalSupply() - tbtc.mint(stakerA.address, stakerA.amountToStake) + tbtc.mint(stakerA.address, newAmountToStake) - await acre.stake(stakerA.amountToStake, stakerA.address, referral) + expectedSharesToMint = + (newAmountToStake * (totalSupplyBefore + 1n)) / + (totalAssetsBefore + 1n) + + await acre.stake(newAmountToStake, stakerA.address, referral) + + // State after stake: + // Total assets = 75(staker A) + 25(staker B) + 100(yield) + 20(staker + // A) = 220 + // Total shares = 75 + 25 + 10 = 110 + expectedTotalAssets = totalAssetsBefore + newAmountToStake + expectedTotalSupply = totalSupplyBefore + expectedSharesToMint }) it("should receive more shares", async () => { const shares = await acre.balanceOf(stakerA.address) - const availableToRedeem = await acre.previewRedeem(shares) expect(shares).to.be.greaterThan(sharesBefore) + expect(shares).to.be.eq(sharesBefore + expectedSharesToMint) + }) + + it("should be able to redeem more tokens than before", async () => { + const shares = await acre.balanceOf(stakerA.address) + const availableToRedeem = await acre.previewRedeem(shares) + + // Expected amount w/o rounding: 85 * 220 / 110 = 170 + // Expected amount w/ support for rounding: 169999999999999999999 in + // tBTC token precision. + const expectedTotalAssetsAvailableToRedeem = + (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n) + expect(availableToRedeem).to.be.greaterThan(availableToRedeemBefore) + expect(availableToRedeem).to.be.eq( + expectedTotalAssetsAvailableToRedeem, + ) }) }) }) From 3a312f7784118c5afbd941b0cd3527b7acf27c73 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 12:01:32 +0100 Subject: [PATCH 48/89] Add documentation comments for `Acre` contract --- core/contracts/Acre.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 2cac7beed..bb8f357e9 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -3,6 +3,13 @@ pragma solidity ^0.8.21; import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +/// @title Acre +/// @notice Implementation of the ERR-4626 tokenized vault standard. ERC-4626 is +/// a standard to optimize and unify the technical parameters of +/// yield-bearing vaults. This contract allows the minting and burning +/// of shares, represented as standard ERC20 token, in exchange for tBTC +/// tokens. +/// @dev ERC-4626 standard extends the ERC-20 token. contract Acre is ERC4626 { event Staked(bytes32 indexed referral, uint256 assets, uint256 shares); From 277dc1ed2d857c8bf88fc944494d02022464be9d Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 15:33:45 +0100 Subject: [PATCH 49/89] Rename `Staked` event We decided to rename this event to `StakeReferral` and remove the `shares` param. --- core/contracts/Acre.sol | 4 ++-- core/test/Acre.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index bb8f357e9..59bf4b69d 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -11,7 +11,7 @@ import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; /// tokens. /// @dev ERC-4626 standard extends the ERC-20 token. contract Acre is ERC4626 { - event Staked(bytes32 indexed referral, uint256 assets, uint256 shares); + event StakeReferral(bytes32 indexed referral, uint256 assets); constructor( IERC20 tbtc @@ -32,7 +32,7 @@ contract Acre is ERC4626 { // TODO: revisit the type of referral. uint256 shares = deposit(assets, receiver); - emit Staked(referral, assets, shares); + emit StakeReferral(referral, assets); return shares; } diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 369f80704..3c0934ca4 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -82,10 +82,10 @@ describe("Acre", () => { ) }) - it("should emit Staked event", () => { + it("should emit StakeReferral event", () => { expect(tx) - .to.emit(acre, "Staked") - .withArgs(referral, amountToStake, expectedReceivedShares) + .to.emit(acre, "StakeReferral") + .withArgs(referral, amountToStake) }) it("should mint stBTC tokens", async () => { From eb91b4d6a126d5c358e415e1bdf4bcc438afa31c Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 15:36:00 +0100 Subject: [PATCH 50/89] Update the `stake` fn Emit the `StakeReferral` event only if the referral is not empty value. Actually, we need this data when the referral is present so it doesn't make sense to emit this event with empty referral value. --- core/contracts/Acre.sol | 4 +++- core/test/Acre.test.ts | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 59bf4b69d..7f2bdb019 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -32,7 +32,9 @@ contract Acre is ERC4626 { // TODO: revisit the type of referral. uint256 shares = deposit(assets, receiver); - emit StakeReferral(referral, assets); + if (referral != bytes32(0)) { + emit StakeReferral(referral, assets); + } return shares; } diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 3c0934ca4..c3902c301 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -120,6 +120,10 @@ describe("Acre", () => { it("should not revert", async () => { await expect(tx).to.be.not.reverted }) + + it("should not emit the StakeReferral event", async () => { + await expect(tx).to.not.emit(acre, "StakeReferral") + }) }) context( From fbb2085d5465051361eb42715421cce23292d312 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 15:40:08 +0100 Subject: [PATCH 51/89] Update `stake` fn docs Clarify under the `dev` tag that the amount of the assets has to be pre-approved in the tBTC contract. --- core/contracts/Acre.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 7f2bdb019..84f33722c 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -19,7 +19,8 @@ contract Acre is ERC4626 { /// @notice Stakes a given amount of tBTC token and mints shares to a /// receiver. - /// @dev This function calls `deposit` function from `ERC4626` contract. + /// @dev This function calls `deposit` function from `ERC4626` contract. The + /// amount of the assets has to be pre-approved in the tBTC contract. /// @param assets Approved amount for the transfer and stake. /// @param receiver The address to which the shares will be minted. /// @param referral Data used for referral program. From 48eeed3c2b655bc57cb15786691237c07126ae27 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 15:44:26 +0100 Subject: [PATCH 52/89] Rename variable in Acre unit tests Since we have `staker2` let's rename `staker` to `staker1`. --- core/test/Acre.test.ts | 68 ++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index c3902c301..a16e00930 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -11,29 +11,29 @@ import type { TestERC20, Acre } from "../typechain" import { to1e18 } from "./utils" async function acreFixture() { - const [staker, staker2] = await ethers.getSigners() + const [staker1, staker2] = await ethers.getSigners() const Token = await ethers.getContractFactory("TestERC20") const tbtc = await Token.deploy() const amountToMint = to1e18(100000) - tbtc.mint(staker, amountToMint) + tbtc.mint(staker1, amountToMint) tbtc.mint(staker2, amountToMint) const Acre = await ethers.getContractFactory("Acre") const acre = await Acre.deploy(await tbtc.getAddress()) - return { acre, tbtc, staker, staker2 } + return { acre, tbtc, staker1, staker2 } } describe("Acre", () => { let acre: Acre let tbtc: TestERC20 - let staker: HardhatEthersSigner + let staker1: HardhatEthersSigner let staker2: HardhatEthersSigner before(async () => { - ;({ acre, tbtc, staker, staker2 } = await loadFixture(acreFixture)) + ;({ acre, tbtc, staker1, staker2 } = await loadFixture(acreFixture)) }) describe("Staking", () => { @@ -61,20 +61,20 @@ describe("Acre", () => { beforeEach(async () => { await tbtc - .connect(staker) + .connect(staker1) .approve(await acre.getAddress(), amountToStake) tx = await acre - .connect(staker) - .stake(amountToStake, staker.address, referral) + .connect(staker1) + .stake(amountToStake, staker1.address, referral) }) it("should emit Deposit event", () => { expect(tx).to.emit(acre, "Deposit").withArgs( // Caller. - staker.address, + staker1.address, // Receiver. - staker.address, + staker1.address, // Staked tokens. amountToStake, // Received shares. @@ -91,7 +91,7 @@ describe("Acre", () => { it("should mint stBTC tokens", async () => { await expect(tx).to.changeTokenBalances( acre, - [staker.address], + [staker1.address], [amountToStake], ) }) @@ -99,7 +99,7 @@ describe("Acre", () => { it("should transfer tBTC tokens", async () => { await expect(tx).to.changeTokenBalances( tbtc, - [staker.address, acre], + [staker1.address, acre], [-amountToStake, amountToStake], ) }) @@ -110,11 +110,11 @@ describe("Acre", () => { let tx: ContractTransactionResponse beforeEach(async () => { - await tbtc.connect(staker).approve(await acre.getAddress(), 1) + await tbtc.connect(staker1).approve(await acre.getAddress(), 1) tx = await acre - .connect(staker) - .stake(1, staker.address, emptyReferral) + .connect(staker1) + .stake(1, staker1.address, emptyReferral) }) it("should not revert", async () => { @@ -132,15 +132,15 @@ describe("Acre", () => { const amountToStake = to1e18(10) beforeEach(async () => { await tbtc - .connect(staker) + .connect(staker1) .approve(await acre.getAddress(), amountToStake) }) it("should revert", async () => { await expect( acre - .connect(staker) - .stake(amountToStake + 1n, staker.address, referral), + .connect(staker1) + .stake(amountToStake + 1n, staker1.address, referral), ).to.be.reverted }) }, @@ -151,13 +151,15 @@ describe("Acre", () => { beforeEach(async () => { await tbtc - .connect(staker) + .connect(staker1) .approve(await acre.getAddress(), amountToStake) }) it("should not revert", async () => { await expect( - acre.connect(staker).stake(amountToStake, staker.address, referral), + acre + .connect(staker1) + .stake(amountToStake, staker1.address, referral), ).to.not.be.reverted }) }) @@ -167,13 +169,13 @@ describe("Acre", () => { beforeEach(async () => { await tbtc - .connect(staker) + .connect(staker1) .approve(await acre.getAddress(), amountToStake) }) it("should revert", async () => { await expect( - acre.connect(staker).stake(amountToStake, ZeroAddress, referral), + acre.connect(staker1).stake(amountToStake, ZeroAddress, referral), ).to.be.revertedWithCustomError(acre, "ERC20InvalidReceiver") }) }) @@ -185,19 +187,19 @@ describe("Acre", () => { beforeEach(async () => { await tbtc - .connect(staker) + .connect(staker1) .approve(await acre.getAddress(), amountToStake) await acre - .connect(staker) - .stake(amountToStake, staker.address, referral) + .connect(staker1) + .stake(amountToStake, staker1.address, referral) }) it("should revert", async () => { await expect( acre - .connect(staker) - .stake(amountToStake, staker.address, referral), + .connect(staker1) + .stake(amountToStake, staker1.address, referral), ).to.be.revertedWithCustomError(acre, "ERC20InsufficientAllowance") }) }, @@ -216,24 +218,24 @@ describe("Acre", () => { let afterSimulatingYieldSnapshot: SnapshotRestorer before(async () => { - const stakerAmountToStake = to1e18(75) + const staker1AmountToStake = to1e18(75) const staker2AmountToStake = to1e18(25) // Infinite approval for staking contract. await tbtc - .connect(staker) + .connect(staker1) .approve(await acre.getAddress(), ethers.MaxUint256) await tbtc .connect(staker2) .approve(await acre.getAddress(), ethers.MaxUint256) // Mint tokens. - await tbtc.connect(staker).mint(staker.address, stakerAmountToStake) + await tbtc.connect(staker1).mint(staker1.address, staker1AmountToStake) await tbtc.connect(staker2).mint(staker2.address, staker2AmountToStake) stakerA = { - signer: staker, - address: staker.address, - amountToStake: stakerAmountToStake, + signer: staker1, + address: staker1.address, + amountToStake: staker1AmountToStake, } stakerB = { signer: staker2, From f7003b81bf8637e84ffe43f4aa3fc8b3c68c6af7 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 15:50:30 +0100 Subject: [PATCH 53/89] Rename variable in `acreFixture` `Token` -> `TestERC20` to be consistent. --- core/test/Acre.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index a16e00930..dff30aab1 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -12,8 +12,8 @@ import { to1e18 } from "./utils" async function acreFixture() { const [staker1, staker2] = await ethers.getSigners() - const Token = await ethers.getContractFactory("TestERC20") - const tbtc = await Token.deploy() + const TestERC20 = await ethers.getContractFactory("TestERC20") + const tbtc = await TestERC20.deploy() const amountToMint = to1e18(100000) From 076a29cd15221243abeca5175dc4635872229226 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 15:56:05 +0100 Subject: [PATCH 54/89] Update the `acreFixture` fn Let's deploy the contracts first and then perform the minting. --- core/test/Acre.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index dff30aab1..9e167f32e 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -12,17 +12,17 @@ import { to1e18 } from "./utils" async function acreFixture() { const [staker1, staker2] = await ethers.getSigners() + const TestERC20 = await ethers.getContractFactory("TestERC20") const tbtc = await TestERC20.deploy() - const amountToMint = to1e18(100000) + const Acre = await ethers.getContractFactory("Acre") + const acre = await Acre.deploy(await tbtc.getAddress()) + const amountToMint = to1e18(100000) tbtc.mint(staker1, amountToMint) tbtc.mint(staker2, amountToMint) - const Acre = await ethers.getContractFactory("Acre") - const acre = await Acre.deploy(await tbtc.getAddress()) - return { acre, tbtc, staker1, staker2 } } From 961580b996da7f7cc4fbf9861984d9eb226cb0c7 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 15:57:02 +0100 Subject: [PATCH 55/89] Update the test name It's worth to match the test name with the function name. --- core/test/Acre.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 9e167f32e..01b4be062 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -36,7 +36,7 @@ describe("Acre", () => { ;({ acre, tbtc, staker1, staker2 } = await loadFixture(acreFixture)) }) - describe("Staking", () => { + describe("stake", () => { const referral = ethers.encodeBytes32String("referral") let snapshot: SnapshotRestorer From 9674f5e5a72c9fbf36831f0ff141bbdcd40cf4e6 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 16:12:16 +0100 Subject: [PATCH 56/89] Rename file `TestToken` -> `TestERC20`. The file name should match the contract name. --- core/contracts/test/{TestToken.sol => TestERC20.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/contracts/test/{TestToken.sol => TestERC20.sol} (100%) diff --git a/core/contracts/test/TestToken.sol b/core/contracts/test/TestERC20.sol similarity index 100% rename from core/contracts/test/TestToken.sol rename to core/contracts/test/TestERC20.sol From 9737ed8f443c22685c04c80a118684360587e1fc Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 16:26:17 +0100 Subject: [PATCH 57/89] Avoid infinite approvals in unit tests --- core/test/Acre.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 01b4be062..5964b3b8d 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -220,13 +220,13 @@ describe("Acre", () => { before(async () => { const staker1AmountToStake = to1e18(75) const staker2AmountToStake = to1e18(25) - // Infinite approval for staking contract. + await tbtc .connect(staker1) - .approve(await acre.getAddress(), ethers.MaxUint256) + .approve(await acre.getAddress(), staker1AmountToStake) await tbtc .connect(staker2) - .approve(await acre.getAddress(), ethers.MaxUint256) + .approve(await acre.getAddress(), staker2AmountToStake) // Mint tokens. await tbtc.connect(staker1).mint(staker1.address, staker1AmountToStake) @@ -401,6 +401,10 @@ describe("Acre", () => { tbtc.mint(stakerA.address, newAmountToStake) + await tbtc + .connect(stakerA.signer) + .approve(await acre.getAddress(), newAmountToStake) + expectedSharesToMint = (newAmountToStake * (totalSupplyBefore + 1n)) / (totalAssetsBefore + 1n) From d33c5527735da7d58690a67b791546a39557983b Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 16:40:33 +0100 Subject: [PATCH 58/89] Get rid of unnecessary `Staker` type in unit tests --- core/test/Acre.test.ts | 85 ++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 53 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 5964b3b8d..c574240da 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -207,20 +207,12 @@ describe("Acre", () => { }) context("when there are two stakers, A and B ", () => { - type Staker = { - signer: HardhatEthersSigner - address: string - amountToStake: bigint - } - let stakerA: Staker - let stakerB: Staker + const staker1AmountToStake = to1e18(75) + const staker2AmountToStake = to1e18(25) let afterStakesSnapshot: SnapshotRestorer let afterSimulatingYieldSnapshot: SnapshotRestorer before(async () => { - const staker1AmountToStake = to1e18(75) - const staker2AmountToStake = to1e18(25) - await tbtc .connect(staker1) .approve(await acre.getAddress(), staker1AmountToStake) @@ -231,17 +223,6 @@ describe("Acre", () => { // Mint tokens. await tbtc.connect(staker1).mint(staker1.address, staker1AmountToStake) await tbtc.connect(staker2).mint(staker2.address, staker2AmountToStake) - - stakerA = { - signer: staker1, - address: staker1.address, - amountToStake: staker1AmountToStake, - } - stakerB = { - signer: staker2, - address: staker2.address, - amountToStake: staker2AmountToStake, - } }) context( @@ -255,15 +236,15 @@ describe("Acre", () => { it("should stake tokens correctly", async () => { await expect( acre - .connect(stakerA.signer) - .stake(stakerA.amountToStake, stakerA.address, referral), + .connect(staker1) + .stake(staker1AmountToStake, staker1.address, referral), ).to.be.not.reverted }) it("should receive shares equal to a staked amount", async () => { - const shares = await acre.balanceOf(stakerA.address) + const shares = await acre.balanceOf(staker1.address) - expect(shares).to.eq(stakerA.amountToStake) + expect(shares).to.eq(staker1AmountToStake) }) }) @@ -271,15 +252,15 @@ describe("Acre", () => { it("should stake tokens correctly", async () => { await expect( acre - .connect(stakerB.signer) - .stake(stakerB.amountToStake, stakerB.address, referral), + .connect(staker2) + .stake(staker2AmountToStake, staker2.address, referral), ).to.be.not.reverted }) it("should receive shares equal to a staked amount", async () => { - const shares = await acre.balanceOf(stakerB.address) + const shares = await acre.balanceOf(staker2.address) - expect(shares).to.eq(stakerB.amountToStake) + expect(shares).to.eq(staker2AmountToStake) }) }) }, @@ -293,15 +274,13 @@ describe("Acre", () => { it("the total assets amount should be equal to all staked tokens", async () => { const totalAssets = await acre.totalAssets() - expect(totalAssets).to.eq( - stakerA.amountToStake + stakerB.amountToStake, - ) + expect(totalAssets).to.eq(staker1AmountToStake + staker2AmountToStake) }) }) context("when vault earns yield", () => { - let stakerASharesBefore: bigint - let stakerBSharesBefore: bigint + let staker1SharesBefore: bigint + let staker2SharesBefore: bigint let vaultYield: bigint let expectedTotalAssets: bigint let expectedTotalSupply: bigint @@ -309,16 +288,16 @@ describe("Acre", () => { before(async () => { await afterStakesSnapshot.restore() - stakerASharesBefore = await acre.balanceOf(stakerA.address) - stakerBSharesBefore = await acre.balanceOf(stakerB.address) + staker1SharesBefore = await acre.balanceOf(staker1.address) + staker2SharesBefore = await acre.balanceOf(staker2.address) vaultYield = to1e18(100) // Staker A shares = 75 // Staker B shares = 25 // Total assets = 75(staker A) + 25(staker B) + 100(yield) expectedTotalAssets = - stakerA.amountToStake + stakerB.amountToStake + vaultYield + staker1AmountToStake + staker2AmountToStake + vaultYield // Total shares = 75 + 25 = 100 - expectedTotalSupply = stakerA.amountToStake + stakerB.amountToStake + expectedTotalSupply = staker1AmountToStake + staker2AmountToStake // Simulating yield returned from strategies. The vault now contains // more tokens than deposited which causes the exchange rate to @@ -332,21 +311,21 @@ describe("Acre", () => { it("the vault should hold more assets", async () => { expect(await acre.totalAssets()).to.be.eq( - stakerA.amountToStake + stakerB.amountToStake + vaultYield, + staker1AmountToStake + staker2AmountToStake + vaultYield, ) }) it("the staker's shares should be the same", async () => { - expect(await acre.balanceOf(stakerA.address)).to.be.eq( - stakerASharesBefore, + expect(await acre.balanceOf(staker1.address)).to.be.eq( + staker1SharesBefore, ) - expect(await acre.balanceOf(stakerB.address)).to.be.eq( - stakerBSharesBefore, + expect(await acre.balanceOf(staker2.address)).to.be.eq( + staker2SharesBefore, ) }) it("the staker A should be able to redeem more tokens than before", async () => { - const shares = await acre.balanceOf(stakerA.address) + const shares = await acre.balanceOf(staker1.address) const availableAssetsToRedeem = await acre.previewRedeem(shares) // Expected amount w/o rounding: 75 * 200 / 100 = 150 @@ -356,13 +335,13 @@ describe("Acre", () => { (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n) expect(availableAssetsToRedeem).to.be.greaterThan( - stakerA.amountToStake, + staker1AmountToStake, ) expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem) }) it("the staker B should be able to redeem more tokens than before", async () => { - const shares = await acre.balanceOf(stakerB.address) + const shares = await acre.balanceOf(staker2.address) const availableAssetsToRedeem = await acre.previewRedeem(shares) // Expected amount w/o rounding: 25 * 200 / 100 = 50 @@ -372,7 +351,7 @@ describe("Acre", () => { (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n) expect(availableAssetsToRedeem).to.be.greaterThan( - stakerB.amountToStake, + staker2AmountToStake, ) expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem) }) @@ -394,22 +373,22 @@ describe("Acre", () => { // Total shares = 75 + 25 = 100 await afterSimulatingYieldSnapshot.restore() - sharesBefore = await acre.balanceOf(stakerA.address) + sharesBefore = await acre.balanceOf(staker1.address) availableToRedeemBefore = await acre.previewRedeem(sharesBefore) totalAssetsBefore = await acre.totalAssets() totalSupplyBefore = await acre.totalSupply() - tbtc.mint(stakerA.address, newAmountToStake) + tbtc.mint(staker1.address, newAmountToStake) await tbtc - .connect(stakerA.signer) + .connect(staker1) .approve(await acre.getAddress(), newAmountToStake) expectedSharesToMint = (newAmountToStake * (totalSupplyBefore + 1n)) / (totalAssetsBefore + 1n) - await acre.stake(newAmountToStake, stakerA.address, referral) + await acre.stake(newAmountToStake, staker1.address, referral) // State after stake: // Total assets = 75(staker A) + 25(staker B) + 100(yield) + 20(staker @@ -420,14 +399,14 @@ describe("Acre", () => { }) it("should receive more shares", async () => { - const shares = await acre.balanceOf(stakerA.address) + const shares = await acre.balanceOf(staker1.address) expect(shares).to.be.greaterThan(sharesBefore) expect(shares).to.be.eq(sharesBefore + expectedSharesToMint) }) it("should be able to redeem more tokens than before", async () => { - const shares = await acre.balanceOf(stakerA.address) + const shares = await acre.balanceOf(staker1.address) const availableToRedeem = await acre.previewRedeem(shares) // Expected amount w/o rounding: 85 * 220 / 110 = 170 From eef74bc541400fca0dc2d08d04d626f4037e62bf Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 16:50:58 +0100 Subject: [PATCH 59/89] Hardcode expected values in unit tests To avoid a false-positive result where both contracts and tests calculations are wrong. --- core/test/Acre.test.ts | 39 +++++++++------------------------------ 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index c574240da..c46b6ef4d 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -282,22 +282,17 @@ describe("Acre", () => { let staker1SharesBefore: bigint let staker2SharesBefore: bigint let vaultYield: bigint - let expectedTotalAssets: bigint - let expectedTotalSupply: bigint before(async () => { + // Current state: + // Staker A shares = 75 + // Staker B shares = 25 + // Total assets = 75(staker A) + 25(staker B) + 100(yield) await afterStakesSnapshot.restore() staker1SharesBefore = await acre.balanceOf(staker1.address) staker2SharesBefore = await acre.balanceOf(staker2.address) vaultYield = to1e18(100) - // Staker A shares = 75 - // Staker B shares = 25 - // Total assets = 75(staker A) + 25(staker B) + 100(yield) - expectedTotalAssets = - staker1AmountToStake + staker2AmountToStake + vaultYield - // Total shares = 75 + 25 = 100 - expectedTotalSupply = staker1AmountToStake + staker2AmountToStake // Simulating yield returned from strategies. The vault now contains // more tokens than deposited which causes the exchange rate to @@ -331,8 +326,7 @@ describe("Acre", () => { // Expected amount w/o rounding: 75 * 200 / 100 = 150 // Expected amount w/ support for rounding: 149999999999999999999 in // tBTC token precision. - const expectedAssetsToRedeem = - (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n) + const expectedAssetsToRedeem = 149999999999999999999n expect(availableAssetsToRedeem).to.be.greaterThan( staker1AmountToStake, @@ -347,8 +341,7 @@ describe("Acre", () => { // Expected amount w/o rounding: 25 * 200 / 100 = 50 // Expected amount w/ support for rounding: 49999999999999999999 in // tBTC token precision. - const expectedAssetsToRedeem = - (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n) + const expectedAssetsToRedeem = 49999999999999999999n expect(availableAssetsToRedeem).to.be.greaterThan( staker2AmountToStake, @@ -359,13 +352,9 @@ describe("Acre", () => { context("when staker A stakes more tokens", () => { const newAmountToStake = to1e18(20) + const expectedSharesToMint = to1e18(10) let sharesBefore: bigint let availableToRedeemBefore: bigint - let totalAssetsBefore: bigint - let totalSupplyBefore: bigint - let expectedSharesToMint: bigint - let expectedTotalSupply: bigint - let expectedTotalAssets: bigint before(async () => { // Current state: @@ -375,8 +364,6 @@ describe("Acre", () => { sharesBefore = await acre.balanceOf(staker1.address) availableToRedeemBefore = await acre.previewRedeem(sharesBefore) - totalAssetsBefore = await acre.totalAssets() - totalSupplyBefore = await acre.totalSupply() tbtc.mint(staker1.address, newAmountToStake) @@ -384,18 +371,11 @@ describe("Acre", () => { .connect(staker1) .approve(await acre.getAddress(), newAmountToStake) - expectedSharesToMint = - (newAmountToStake * (totalSupplyBefore + 1n)) / - (totalAssetsBefore + 1n) - - await acre.stake(newAmountToStake, staker1.address, referral) - // State after stake: // Total assets = 75(staker A) + 25(staker B) + 100(yield) + 20(staker // A) = 220 // Total shares = 75 + 25 + 10 = 110 - expectedTotalAssets = totalAssetsBefore + newAmountToStake - expectedTotalSupply = totalSupplyBefore + expectedSharesToMint + await acre.stake(newAmountToStake, staker1.address, referral) }) it("should receive more shares", async () => { @@ -412,8 +392,7 @@ describe("Acre", () => { // Expected amount w/o rounding: 85 * 220 / 110 = 170 // Expected amount w/ support for rounding: 169999999999999999999 in // tBTC token precision. - const expectedTotalAssetsAvailableToRedeem = - (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n) + const expectedTotalAssetsAvailableToRedeem = 169999999999999999999n expect(availableToRedeem).to.be.greaterThan(availableToRedeemBefore) expect(availableToRedeem).to.be.eq( From 8a243cf30d65b13c2f964c99b6db5fee036c4def Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 16:58:37 +0100 Subject: [PATCH 60/89] Remove unnecessary check in unit test We validate the exact value in the next line so this check seems redundant. --- core/test/Acre.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index c46b6ef4d..863d1800e 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -381,7 +381,6 @@ describe("Acre", () => { it("should receive more shares", async () => { const shares = await acre.balanceOf(staker1.address) - expect(shares).to.be.greaterThan(sharesBefore) expect(shares).to.be.eq(sharesBefore + expectedSharesToMint) }) From b812107a2e8c22649758f7fcec68b075fef3e333 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 17:01:48 +0100 Subject: [PATCH 61/89] Rename test context name `when staking` -> `when staking as a first staker`. --- core/test/Acre.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 863d1800e..445cf0618 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -40,7 +40,7 @@ describe("Acre", () => { const referral = ethers.encodeBytes32String("referral") let snapshot: SnapshotRestorer - context("when staking", () => { + context("when staking as first staker", () => { beforeEach(async () => { snapshot = await takeSnapshot() }) From 910a3992e6cf68d056abcfca6fe1f1599896f273 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 17:03:43 +0100 Subject: [PATCH 62/89] Update `without referral` test case Define a variable `amountToStake` instead of passing number directly to functions - to improve readability. --- core/test/Acre.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 445cf0618..d043adf8d 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -106,15 +106,18 @@ describe("Acre", () => { }) context("without referral", () => { + const amountToStake = to1e18(10) const emptyReferral = ethers.encodeBytes32String("") let tx: ContractTransactionResponse beforeEach(async () => { - await tbtc.connect(staker1).approve(await acre.getAddress(), 1) + await tbtc + .connect(staker1) + .approve(await acre.getAddress(), amountToStake) tx = await acre .connect(staker1) - .stake(1, staker1.address, emptyReferral) + .stake(amountToStake, staker1.address, emptyReferral) }) it("should not revert", async () => { From a868dcec1dc4e2483357397e6a9fc78fbaeb933b Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 17:16:56 +0100 Subject: [PATCH 63/89] Improve test case Since the variable is named `amountToStake`, the amount we pass to `stake` function should be exactly `amountToStake`. Here we add a new variable `approvedAmount` which will be approved in the `before` hook and we use this value when defining the `amountToStake` which is equal `amountToStake + 1`. This will imporve readability. --- core/test/Acre.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index d043adf8d..14d6db66b 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -132,18 +132,20 @@ describe("Acre", () => { context( "when amount to stake is greater than the approved amount", () => { - const amountToStake = to1e18(10) + const approvedAmount = to1e18(10) + const amountToStake = approvedAmount + 1n + beforeEach(async () => { await tbtc .connect(staker1) - .approve(await acre.getAddress(), amountToStake) + .approve(await acre.getAddress(), approvedAmount) }) it("should revert", async () => { await expect( acre .connect(staker1) - .stake(amountToStake + 1n, staker1.address, referral), + .stake(amountToStake, staker1.address, referral), ).to.be.reverted }) }, From 567b52ca96eb50eae82a35f339b685d81200f197 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 17:17:34 +0100 Subject: [PATCH 64/89] Make sure the tx is reverted with expected reason --- core/test/Acre.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 14d6db66b..5d9c70f99 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -146,7 +146,7 @@ describe("Acre", () => { acre .connect(staker1) .stake(amountToStake, staker1.address, referral), - ).to.be.reverted + ).to.be.revertedWithCustomError(tbtc, "ERC20InsufficientAllowance") }) }, ) From 4889a0355677f39359fbc434899b084a021191a3 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 17:18:58 +0100 Subject: [PATCH 65/89] Use dedicated variable in unit test We should use `expectedReceivedShares` while checking `stBTC` balance after staking. --- core/test/Acre.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 5d9c70f99..0d13690fd 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -92,7 +92,7 @@ describe("Acre", () => { await expect(tx).to.changeTokenBalances( acre, [staker1.address], - [amountToStake], + [expectedReceivedShares], ) }) From cc62ff08a8e65e3cfa5d4c27cbf4d7c9e000caa5 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 5 Dec 2023 17:19:01 +0100 Subject: [PATCH 66/89] Use TestERC20 as TBTC token stub The contract has been renamed from TestToken to TestERC20 --- core/deploy/00_resolve_tbtc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/deploy/00_resolve_tbtc.ts b/core/deploy/00_resolve_tbtc.ts index 83f796ceb..dc1bfeff5 100644 --- a/core/deploy/00_resolve_tbtc.ts +++ b/core/deploy/00_resolve_tbtc.ts @@ -17,7 +17,7 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { log("deploying TBTC contract stub") await deployments.deploy("TBTC", { - contract: "TestToken", // TODO: Rename to TestERC20 + contract: "TestERC20", from: deployer, log: true, waitConfirmations: 1, From 9cac49c8d9be916656bc0bf546fbdfeb1af4f467 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 18:00:56 +0100 Subject: [PATCH 67/89] Update staking unit tests Since the ERC4626 deposit function has different caller and receiver roles, let's test them here. This will reflect the flow for tBTC minting and staking in one transaction, where an external contract will call the stake function in the name of the staker. --- core/test/Acre.test.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 0d13690fd..051732352 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -58,23 +58,28 @@ describe("Acre", () => { const expectedReceivedShares = amountToStake let tx: ContractTransactionResponse + let tbtcHolder: HardhatEthersSigner + let receiver: HardhatEthersSigner beforeEach(async () => { + tbtcHolder = staker1 + receiver = staker2 + await tbtc - .connect(staker1) + .connect(tbtcHolder) .approve(await acre.getAddress(), amountToStake) tx = await acre - .connect(staker1) - .stake(amountToStake, staker1.address, referral) + .connect(tbtcHolder) + .stake(amountToStake, receiver.address, referral) }) it("should emit Deposit event", () => { expect(tx).to.emit(acre, "Deposit").withArgs( // Caller. - staker1.address, + tbtcHolder.address, // Receiver. - staker1.address, + receiver.address, // Staked tokens. amountToStake, // Received shares. @@ -91,7 +96,7 @@ describe("Acre", () => { it("should mint stBTC tokens", async () => { await expect(tx).to.changeTokenBalances( acre, - [staker1.address], + [receiver.address], [expectedReceivedShares], ) }) @@ -99,7 +104,7 @@ describe("Acre", () => { it("should transfer tBTC tokens", async () => { await expect(tx).to.changeTokenBalances( tbtc, - [staker1.address, acre], + [tbtcHolder.address, acre], [-amountToStake, amountToStake], ) }) From 88ee4d6f9ad8462ea90ffb3fed9ce0ad34246b14 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 5 Dec 2023 18:27:44 +0100 Subject: [PATCH 68/89] Complicate the math in test scenario Complicate the math by using `50` instead of `100` to use a different value than `100` which we already have in `totalShares` (25+75=100). --- core/test/Acre.test.ts | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index 051732352..c858bc43e 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -297,12 +297,12 @@ describe("Acre", () => { // Current state: // Staker A shares = 75 // Staker B shares = 25 - // Total assets = 75(staker A) + 25(staker B) + 100(yield) + // Total assets = 75(staker A) + 25(staker B) + 50(yield) await afterStakesSnapshot.restore() staker1SharesBefore = await acre.balanceOf(staker1.address) staker2SharesBefore = await acre.balanceOf(staker2.address) - vaultYield = to1e18(100) + vaultYield = to1e18(50) // Simulating yield returned from strategies. The vault now contains // more tokens than deposited which causes the exchange rate to @@ -333,10 +333,10 @@ describe("Acre", () => { const shares = await acre.balanceOf(staker1.address) const availableAssetsToRedeem = await acre.previewRedeem(shares) - // Expected amount w/o rounding: 75 * 200 / 100 = 150 - // Expected amount w/ support for rounding: 149999999999999999999 in + // Expected amount w/o rounding: 75 * 150 / 100 = 112.5 + // Expected amount w/ support for rounding: 112499999999999999999 in // tBTC token precision. - const expectedAssetsToRedeem = 149999999999999999999n + const expectedAssetsToRedeem = 112499999999999999999n expect(availableAssetsToRedeem).to.be.greaterThan( staker1AmountToStake, @@ -348,10 +348,10 @@ describe("Acre", () => { const shares = await acre.balanceOf(staker2.address) const availableAssetsToRedeem = await acre.previewRedeem(shares) - // Expected amount w/o rounding: 25 * 200 / 100 = 50 - // Expected amount w/ support for rounding: 49999999999999999999 in + // Expected amount w/o rounding: 25 * 150 / 100 = 37.5 + // Expected amount w/ support for rounding: 37499999999999999999 in // tBTC token precision. - const expectedAssetsToRedeem = 49999999999999999999n + const expectedAssetsToRedeem = 37499999999999999999n expect(availableAssetsToRedeem).to.be.greaterThan( staker2AmountToStake, @@ -362,14 +362,16 @@ describe("Acre", () => { context("when staker A stakes more tokens", () => { const newAmountToStake = to1e18(20) - const expectedSharesToMint = to1e18(10) + // Current state: + // Total assets = 75(staker A) + 25(staker B) + 50(yield) + // Total shares = 75 + 25 = 100 + // 20 * 100 / 150 = 13.(3) -> 13333333333333333333 in stBTC token + /// precision + const expectedSharesToMint = 13333333333333333333n let sharesBefore: bigint let availableToRedeemBefore: bigint before(async () => { - // Current state: - // Total assets = 75(staker A) + 25(staker B) + 100(yield) - // Total shares = 75 + 25 = 100 await afterSimulatingYieldSnapshot.restore() sharesBefore = await acre.balanceOf(staker1.address) @@ -382,9 +384,9 @@ describe("Acre", () => { .approve(await acre.getAddress(), newAmountToStake) // State after stake: - // Total assets = 75(staker A) + 25(staker B) + 100(yield) + 20(staker - // A) = 220 - // Total shares = 75 + 25 + 10 = 110 + // Total assets = 75(staker A) + 25(staker B) + 50(yield) + 20(staker + // A) = 170 + // Total shares = 75 + 25 + 13.(3) = 113.(3) await acre.stake(newAmountToStake, staker1.address, referral) }) @@ -398,10 +400,10 @@ describe("Acre", () => { const shares = await acre.balanceOf(staker1.address) const availableToRedeem = await acre.previewRedeem(shares) - // Expected amount w/o rounding: 85 * 220 / 110 = 170 - // Expected amount w/ support for rounding: 169999999999999999999 in + // Expected amount w/o rounding: 88.(3) * 170 / 113.(3) = 132.5 + // Expected amount w/ support for rounding: 132499999999999999999 in // tBTC token precision. - const expectedTotalAssetsAvailableToRedeem = 169999999999999999999n + const expectedTotalAssetsAvailableToRedeem = 132499999999999999999n expect(availableToRedeem).to.be.greaterThan(availableToRedeemBefore) expect(availableToRedeem).to.be.eq( From 05e1a959c02c25368da0e8f2bc87c7f2dae13758 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 6 Dec 2023 08:14:26 +0100 Subject: [PATCH 69/89] 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 70/89] 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 71/89] 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 72/89] 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 73/89] 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 5e4849885d07165bee4937eab2d3b1dcef8b3e2d Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 6 Dec 2023 10:17:28 +0100 Subject: [PATCH 74/89] Add a basic typography --- dapp/src/components/Typography/index.tsx | 33 ++++++++++++++++++++++++ dapp/src/theme/index.ts | 4 +++ dapp/src/theme/utils/fonts.ts | 22 ++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 dapp/src/components/Typography/index.tsx create mode 100644 dapp/src/theme/utils/fonts.ts diff --git a/dapp/src/components/Typography/index.tsx b/dapp/src/components/Typography/index.tsx new file mode 100644 index 000000000..6e1231239 --- /dev/null +++ b/dapp/src/components/Typography/index.tsx @@ -0,0 +1,33 @@ +/* eslint-disable react/jsx-props-no-spreading */ +import React from "react" +import { Text, TextProps } from "@chakra-ui/react" + +export function TextXl(props: TextProps) { + return ( + + ) +} + +export function TextLg(props: TextProps) { + return ( + + ) +} + +export function TextMd(props: TextProps) { + return ( + + ) +} + +export function TextSm(props: TextProps) { + return ( + + ) +} + +export function TextXs(props: TextProps) { + return ( + + ) +} diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index 980c42838..366e952a6 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -2,9 +2,13 @@ import { StyleFunctionProps, extendTheme } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" import Button from "./Button" import { colors } from "./utils" +import { fontSizes, fontWeights, lineHeights } from "./utils/fonts" const defaultTheme = { colors, + fontSizes, + fontWeights, + lineHeights, styles: { global: (props: StyleFunctionProps) => ({ body: { diff --git a/dapp/src/theme/utils/fonts.ts b/dapp/src/theme/utils/fonts.ts new file mode 100644 index 000000000..18a482760 --- /dev/null +++ b/dapp/src/theme/utils/fonts.ts @@ -0,0 +1,22 @@ +export const fontSizes = { + xs: "0.75rem", + sm: "0.875rem", + md: "1rem", + lg: "1.125rem", + xl: "1.25rem", +} + +export const fontWeights = { + medium: 500, + semibold: 600, + bold: 700, + black: 900, +} + +export const lineHeights = { + xs: "1.125rem", + sm: "1.25rem", + md: "1.5rem", + lg: "1.75rem", + xl: "1.875rem", +} From b037200f87be5a2715d2a61c50cc54ea6e68d643 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 6 Dec 2023 12:20:26 +0100 Subject: [PATCH 75/89] Fix for incorrect import --- dapp/src/theme/index.ts | 3 +-- dapp/src/theme/utils/index.ts | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index 366e952a6..39ec6d51b 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -1,8 +1,7 @@ import { StyleFunctionProps, extendTheme } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" import Button from "./Button" -import { colors } from "./utils" -import { fontSizes, fontWeights, lineHeights } from "./utils/fonts" +import { colors, fontSizes, fontWeights, lineHeights } from "./utils" const defaultTheme = { colors, diff --git a/dapp/src/theme/utils/index.ts b/dapp/src/theme/utils/index.ts index c0b5e2654..9f377b3e3 100644 --- a/dapp/src/theme/utils/index.ts +++ b/dapp/src/theme/utils/index.ts @@ -1 +1,2 @@ export * from "./colors" +export * from "./fonts" From 275028608474e5e288c4be7c39b0e849a6f97697 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 6 Dec 2023 13:33:07 +0100 Subject: [PATCH 76/89] Improve Acre contract docs comment Add short description what is the purpose of Acre system. --- core/contracts/Acre.sol | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol index 84f33722c..0e1cf640f 100644 --- a/core/contracts/Acre.sol +++ b/core/contracts/Acre.sol @@ -4,12 +4,16 @@ pragma solidity ^0.8.21; import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; /// @title Acre -/// @notice Implementation of the ERR-4626 tokenized vault standard. ERC-4626 is -/// a standard to optimize and unify the technical parameters of -/// yield-bearing vaults. This contract allows the minting and burning -/// of shares, represented as standard ERC20 token, in exchange for tBTC -/// tokens. -/// @dev ERC-4626 standard extends the ERC-20 token. +/// @notice This contract implements the ERC-4626 tokenized vault standard. By +/// staking tBTC, users acquire a liquid staking token called stBTC, +/// commonly referred to as "shares". The staked tBTC is securely +/// deposited into Acre's vaults, where it generates yield over time. +/// Users have the flexibility to redeem stBTC, enabling them to +/// withdraw their staked tBTC along with the accrued yield. +/// @dev ERC-4626 is a standard to optimize and unify the technical parameters +/// of yield-bearing vaults. This contract facilitates the minting and +/// burning of shares (stBTC), which are represented as standard ERC20 +/// tokens, providing a seamless exchange with tBTC tokens. contract Acre is ERC4626 { event StakeReferral(bytes32 indexed referral, uint256 assets); From ed73109db8c39dd10cdcc5ddedbcb5e5b086ee5c Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 6 Dec 2023 13:38:24 +0100 Subject: [PATCH 77/89] Remove unnecessary check expectation in test The equality check is enough given we hardcode the expected values. --- core/test/Acre.test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index c858bc43e..ab9fcb0a2 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -338,9 +338,6 @@ describe("Acre", () => { // tBTC token precision. const expectedAssetsToRedeem = 112499999999999999999n - expect(availableAssetsToRedeem).to.be.greaterThan( - staker1AmountToStake, - ) expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem) }) @@ -353,9 +350,6 @@ describe("Acre", () => { // tBTC token precision. const expectedAssetsToRedeem = 37499999999999999999n - expect(availableAssetsToRedeem).to.be.greaterThan( - staker2AmountToStake, - ) expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem) }) }) From 68b81d0c540b54b1d69bce767a6bbe0d9641abdc Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 6 Dec 2023 13:49:52 +0100 Subject: [PATCH 78/89] Remove unnecessary check If the call reverts it will throw an exception in `beforeEach` hook for `await acre.connect(staker1).stake(...)`. --- core/test/Acre.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts index ab9fcb0a2..74e764ca0 100644 --- a/core/test/Acre.test.ts +++ b/core/test/Acre.test.ts @@ -125,10 +125,6 @@ describe("Acre", () => { .stake(amountToStake, staker1.address, emptyReferral) }) - it("should not revert", async () => { - await expect(tx).to.be.not.reverted - }) - it("should not emit the StakeReferral event", async () => { await expect(tx).to.not.emit(acre, "StakeReferral") }) From 0c448568d4a0e8492dc947eef5638bdede87d69a Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 7 Dec 2023 11:08:13 +0100 Subject: [PATCH 79/89] Update helpers dir reference in tsconfig Removed `/`to be consistent with other items. --- core/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tsconfig.json b/core/tsconfig.json index 3f8c9f270..f9167f325 100644 --- a/core/tsconfig.json +++ b/core/tsconfig.json @@ -9,5 +9,5 @@ "resolveJsonModule": true }, "files": ["./hardhat.config.ts"], - "include": ["./deploy", "./test", "./typechain", "./helpers/"] + "include": ["./deploy", "./test", "./typechain", "./helpers"] } From 332d6a91bda2986c5d58e71accb797f958a63995 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 7 Dec 2023 12:14:50 +0100 Subject: [PATCH 80/89] Add tests for address helper --- core/test/helpers.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 core/test/helpers.test.ts diff --git a/core/test/helpers.test.ts b/core/test/helpers.test.ts new file mode 100644 index 000000000..bb5641643 --- /dev/null +++ b/core/test/helpers.test.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +import { expect } from "chai" +import { ZeroAddress } from "ethers" + +import { isNonZeroAddress } from "../helpers/address" + +const ADDRESS_1: string = "0xb894c3967CFb58A5c55f1de4131d126B1eFA1EE0" +const ADDRESS_1_LOWERCASE: string = ADDRESS_1.toLowerCase() +const ADDRESS_1_NO_PREFIX: string = ADDRESS_1.substring(2) +const ADDRESS_INVALID: string = "0xXYZ4c3967CFb58A5c55f1de4131d126B1eFA1EE0" +const ADDRESS_ZERO: string = ZeroAddress + +describe("Helpers", () => { + describe("address", () => { + describe("isNonZeroAddress", () => { + it("should return true for valid checksumed address", () => { + expect(isNonZeroAddress(ADDRESS_1)).to.be.true + }) + + it("should return true for lowercase address", () => { + expect(isNonZeroAddress(ADDRESS_1_LOWERCASE)).to.be.true + }) + + it("should return true for address without 0x prefix", () => { + expect(isNonZeroAddress(ADDRESS_1_NO_PREFIX)).to.be.true + }) + + it("should return false for zero address", () => { + expect(isNonZeroAddress(ADDRESS_ZERO)).to.be.false + }) + + it("should throw an error for address containing invalid character", () => { + expect(() => { + isNonZeroAddress(ADDRESS_INVALID) + }).to.throw("invalid address") + }) + }) + }) +}) From 9866cad8621765e76594bf30900888b8d0661d1f Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 7 Dec 2023 12:16:58 +0100 Subject: [PATCH 81/89] Add test case for empty address string --- core/test/helpers.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/test/helpers.test.ts b/core/test/helpers.test.ts index bb5641643..c5a507eb8 100644 --- a/core/test/helpers.test.ts +++ b/core/test/helpers.test.ts @@ -29,6 +29,12 @@ describe("Helpers", () => { expect(isNonZeroAddress(ADDRESS_ZERO)).to.be.false }) + it("should throw an error for empty string", () => { + expect(() => { + isNonZeroAddress("") + }).to.throw("invalid address") + }) + it("should throw an error for address containing invalid character", () => { expect(() => { isNonZeroAddress(ADDRESS_INVALID) From 805f28c7d8fe6772ba2ba3443e51ccf5d8cfe9e9 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 7 Dec 2023 12:20:18 +0100 Subject: [PATCH 82/89] Update core/scripts/fetch_external_artifacts.sh 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/scripts/fetch_external_artifacts.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/scripts/fetch_external_artifacts.sh b/core/scripts/fetch_external_artifacts.sh index a15c0f108..26bf2a499 100755 --- a/core/scripts/fetch_external_artifacts.sh +++ b/core/scripts/fetch_external_artifacts.sh @@ -9,9 +9,9 @@ EXTERNAL_ARTIFACTS_DIR=${ROOT_DIR}/external rm -rf ${TMP_DIR} mkdir -p ${TMP_DIR} -# fetch_external_artifact is a function that fetches a contract deployment artifact -# from a package published to the NPM registry. It assumes a package is published -# following the rules established by Keep Network deployments: +# fetch_external_artifact is a function that fetches a contract deployment +# artifact from a package published to the NPM registry. It assumes a package is +# published following the rules established by Keep Network deployments: # 1. Packages are tagged with network name and contain the latest version of # deployment artifacts for the given network. # 2. Deployment artfiacts files located under `artifacts/` directory. From d4d656c927a7db10a922f3658567a7891bf00465 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Thu, 7 Dec 2023 12:20:26 +0100 Subject: [PATCH 83/89] Update core/scripts/fetch_external_artifacts.sh 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/scripts/fetch_external_artifacts.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/scripts/fetch_external_artifacts.sh b/core/scripts/fetch_external_artifacts.sh index 26bf2a499..5c043568e 100755 --- a/core/scripts/fetch_external_artifacts.sh +++ b/core/scripts/fetch_external_artifacts.sh @@ -35,7 +35,8 @@ fetch_external_artifact() { --pack-destination=${TMP_DIR} \ ${package} | # Extract deployment artifact to the destination directory. - xargs -I{} tar -zxf ${TMP_DIR}/{} -C ${destination_dir} --strip-components 2 package/artifacts/${contractName}.json + xargs -I{} tar -zxf ${TMP_DIR}/{} -C ${destination_dir} \ + --strip-components 2 package/artifacts/${contractName}.json printf "Succesfully fetched ${contractName} contract artifact from ${package} to ${destination_dir}\n" } From a71401d45b848fc9ce8855e153699aecf46e94bf Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 08:16:55 +0100 Subject: [PATCH 84/89] Use the chakra `Box` component as the `main` --- dapp/src/DApp.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index 085b6fbe8..f38c6379e 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -1,5 +1,5 @@ import React from "react" -import { ChakraProvider } from "@chakra-ui/react" +import { Box, ChakraProvider } from "@chakra-ui/react" import { useDetectThemeMode } from "./hooks" import theme from "./theme" import { LedgerWalletAPIProvider, WalletContextProvider } from "./contexts" @@ -12,9 +12,9 @@ function DApp() { return ( <>
-
+ -
+ ) } From 078f0bb223461528fe90c6fb6a761516182bf7c4 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 08:50:27 +0100 Subject: [PATCH 85/89] Use a `Card` component for overview elements --- .../components/Overview/PositionDetails.tsx | 43 ++++++++----------- dapp/src/components/Overview/Statistics.tsx | 11 +++-- .../Overview/TransactionHistory.tsx | 11 +++-- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/dapp/src/components/Overview/PositionDetails.tsx b/dapp/src/components/Overview/PositionDetails.tsx index 972db661f..e82d49620 100644 --- a/dapp/src/components/Overview/PositionDetails.tsx +++ b/dapp/src/components/Overview/PositionDetails.tsx @@ -2,42 +2,37 @@ import React from "react" import { Text, Button, - HStack, Tooltip, Icon, useColorModeValue, - Flex, + CardBody, + Card, + CardFooter, } 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} - - {/* TODO: Add correct text for tooltip */} - - - - - - - + + 34.75 {BITCOIN.symbol} + + + 1.245.148,1 {USD.symbol} + {/* TODO: Add correct text for tooltip */} + + + + + + {/* TODO: Handle click actions */} - - + + ) } 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 86/89] 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 87/89] 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 da2d27f5b933f2cb0604e8199f828058638dbe35 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 8 Dec 2023 10:58:34 +0100 Subject: [PATCH 88/89] 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 89/89] 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"