diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts
index aa3f77354..b4429a856 100644
--- a/core/test/Acre.test.ts
+++ b/core/test/Acre.test.ts
@@ -8,8 +8,13 @@ import { ethers } from "hardhat"
import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"
import type { SnapshotRestorer } from "@nomicfoundation/hardhat-toolbox/network-helpers"
-import { deployment } from "./helpers/context"
-import { getUnnamedSigner, getNamedSigner } from "./helpers/signer"
+import {
+ beforeAfterEachSnapshotWrapper,
+ beforeAfterSnapshotWrapper,
+ deployment,
+ getNamedSigner,
+ getUnnamedSigner,
+} from "./helpers"
import { to1e18 } from "./utils"
@@ -46,16 +51,8 @@ describe("Acre", () => {
})
describe("stake", () => {
- let snapshot: SnapshotRestorer
-
context("when staking as first staker", () => {
- beforeEach(async () => {
- snapshot = await takeSnapshot()
- })
-
- afterEach(async () => {
- await snapshot.restore()
- })
+ beforeAfterEachSnapshotWrapper()
context("with a referral", () => {
const amountToStake = to1e18(1)
@@ -258,287 +255,278 @@ describe("Acre", () => {
)
})
- context("when there are two stakers", () => {
+ describe("when staking by multiple stakers", () => {
+ beforeAfterSnapshotWrapper()
+
const staker1AmountToStake = to1e18(7)
const staker2AmountToStake = to1e18(3)
+ const earnedYield = to1e18(5)
+
let afterStakesSnapshot: SnapshotRestorer
let afterSimulatingYieldSnapshot: SnapshotRestorer
before(async () => {
+ // Mint tBTC.
+ await tbtc.mint(staker1.address, staker1AmountToStake)
+ await tbtc.mint(staker2.address, staker2AmountToStake)
+
+ // Approve tBTC.
await tbtc
.connect(staker1)
.approve(await acre.getAddress(), staker1AmountToStake)
await tbtc
.connect(staker2)
.approve(await acre.getAddress(), staker2AmountToStake)
-
- // Mint tokens.
- await tbtc.connect(staker1).mint(staker1.address, staker1AmountToStake)
- await tbtc.connect(staker2).mint(staker2.address, staker2AmountToStake)
})
- after(async () => {
- await snapshot.restore()
- })
+ context("when the vault is in initial state", () => {
+ describe("when two stakers stake", () => {
+ let stakeTx1: ContractTransactionResponse
+ let stakeTx2: ContractTransactionResponse
+
+ before(async () => {
+ stakeTx1 = await acre
+ .connect(staker1)
+ .stake(staker1AmountToStake, staker1.address, referral)
+
+ stakeTx2 = await acre
+ .connect(staker2)
+ .stake(staker2AmountToStake, staker2.address, referral)
- 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 receive shares equal to a staked amount", async () => {
- const tx = await acre
- .connect(staker1)
- .stake(staker1AmountToStake, staker1.address, referral)
-
- await expect(tx).to.changeTokenBalances(
- acre,
- [staker1.address],
- [staker1AmountToStake],
- )
- })
+ it("staker A should receive shares equal to a staked amount", async () => {
+ await expect(stakeTx1).to.changeTokenBalances(
+ acre,
+ [staker1.address],
+ [staker1AmountToStake],
+ )
})
- context("when staker B stakes tokens", () => {
- it("should receive shares equal to a staked amount", async () => {
- const tx = await acre
- .connect(staker2)
- .stake(staker2AmountToStake, staker2.address, referral)
-
- await expect(tx).to.changeTokenBalances(
- acre,
- [staker2.address],
- [staker2AmountToStake],
- )
- })
+ it("staker B should receive shares equal to a staked amount", async () => {
+ await expect(stakeTx2).to.changeTokenBalances(
+ acre,
+ [staker2.address],
+ [staker2AmountToStake],
+ )
})
- },
- )
- 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(staker1AmountToStake + staker2AmountToStake)
+ it("the total assets amount should be equal to all staked tokens", async () => {
+ expect(await acre.totalAssets()).to.eq(
+ staker1AmountToStake + staker2AmountToStake,
+ )
+ })
})
})
- context("when vault earns yield", () => {
- let staker1SharesBefore: bigint
- let staker2SharesBefore: bigint
- let vaultYield: bigint
+ context("when vault has two stakers", () => {
+ context("when vault earns yield", () => {
+ let staker1SharesBefore: bigint
+ let staker2SharesBefore: bigint
- before(async () => {
- // Current state:
- // Staker A shares = 7
- // Staker B shares = 3
- // Total assets = 7(staker A) + 3(staker B) + 5(yield)
- await afterStakesSnapshot.restore()
-
- staker1SharesBefore = await acre.balanceOf(staker1.address)
- staker2SharesBefore = await acre.balanceOf(staker2.address)
- vaultYield = to1e18(5)
-
- // 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 assets", async () => {
- expect(await acre.totalAssets()).to.be.eq(
- staker1AmountToStake + staker2AmountToStake + vaultYield,
- )
- })
-
- it("the staker's shares should be the same", async () => {
- expect(await acre.balanceOf(staker1.address)).to.be.eq(
- staker1SharesBefore,
- )
- expect(await acre.balanceOf(staker2.address)).to.be.eq(
- staker2SharesBefore,
- )
- })
-
- it("the staker A should redeem more tokens than before", async () => {
- const shares = await acre.balanceOf(staker1.address)
- const availableAssetsToRedeem = await acre.previewRedeem(shares)
-
- // Expected amount w/o rounding: 7 * 15 / 10 = 10.5
- // Expected amount w/ support for rounding: 10499999999999999999 in
- // tBTC token precision.
- const expectedAssetsToRedeem = 10499999999999999999n
-
- expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem)
- })
-
- it("the staker B should redeem more tokens than before", async () => {
- const shares = await acre.balanceOf(staker2.address)
- const availableAssetsToRedeem = await acre.previewRedeem(shares)
+ before(async () => {
+ // Current state:
+ // Staker A shares = 7
+ // Staker B shares = 3
+ // Total assets = 7(staker A) + 3(staker B) + 5(yield)
+ await afterStakesSnapshot.restore()
- // Expected amount w/o rounding: 3 * 15 / 10 = 4.5
- // Expected amount w/ support for rounding: 4499999999999999999 in
- // tBTC token precision.
- const expectedAssetsToRedeem = 4499999999999999999n
+ staker1SharesBefore = await acre.balanceOf(staker1.address)
+ staker2SharesBefore = await acre.balanceOf(staker2.address)
- expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem)
- })
- })
+ // 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(), earnedYield)
+ })
- context("when staker A stakes more tokens", () => {
- context(
- "when total tBTC amount after staking would not exceed max amount",
- () => {
- const newAmountToStake = to1e18(2)
- // Current state:
- // Total assets = 7(staker A) + 3(staker B) + 5(yield)
- // Total shares = 7 + 3 = 10
- // Shares to mint = 2 * 10 / 15 = 1.(3) -> 1333333333333333333 in stBTC
- // token precision
- const expectedSharesToMint = 1333333333333333333n
- let sharesBefore: bigint
- let availableToRedeemBefore: bigint
+ after(async () => {
+ afterSimulatingYieldSnapshot = await takeSnapshot()
+ })
- before(async () => {
- await afterSimulatingYieldSnapshot.restore()
+ it("the vault should hold more assets", async () => {
+ expect(await acre.totalAssets()).to.be.eq(
+ staker1AmountToStake + staker2AmountToStake + earnedYield,
+ )
+ })
- sharesBefore = await acre.balanceOf(staker1.address)
- availableToRedeemBefore = await acre.previewRedeem(sharesBefore)
+ it("the stakers shares should be the same", async () => {
+ expect(await acre.balanceOf(staker1.address)).to.be.eq(
+ staker1SharesBefore,
+ )
+ expect(await acre.balanceOf(staker2.address)).to.be.eq(
+ staker2SharesBefore,
+ )
+ })
- await tbtc.mint(staker1.address, newAmountToStake)
+ it("the staker A should be able to redeem more tokens than before", async () => {
+ const shares = await acre.balanceOf(staker1.address)
+ const availableAssetsToRedeem = await acre.previewRedeem(shares)
- await tbtc
- .connect(staker1)
- .approve(await acre.getAddress(), newAmountToStake)
+ // Expected amount w/o rounding: 7 * 15 / 10 = 10.5
+ // Expected amount w/ support for rounding: 10499999999999999999 in
+ // tBTC token precision.
+ const expectedAssetsToRedeem = 10499999999999999999n
- // State after stake:
- // Total assets = 7(staker A) + 3(staker B) + 5(yield) + 2(staker
- // A) = 17
- // Total shares = 7 + 3 + 1.(3) = 11.(3)
- await acre
- .connect(staker1)
- .stake(newAmountToStake, staker1.address, referral)
- })
-
- it("should receive more shares", async () => {
- const shares = await acre.balanceOf(staker1.address)
-
- expect(shares).to.be.eq(sharesBefore + expectedSharesToMint)
- })
-
- it("should redeem more tokens than before", async () => {
- const shares = await acre.balanceOf(staker1.address)
- const availableToRedeem = await acre.previewRedeem(shares)
-
- // Expected amount w/o rounding: 8.(3) * 17 / 11.(3) = 12.5
- // Expected amount w/ support for rounding: 12499999999999999999 in
- // tBTC token precision.
- const expectedTotalAssetsAvailableToRedeem = 12499999999999999999n
-
- expect(availableToRedeem).to.be.greaterThan(
- availableToRedeemBefore,
- )
- expect(availableToRedeem).to.be.eq(
- expectedTotalAssetsAvailableToRedeem,
- )
- })
- },
- )
+ expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem)
+ })
- context(
- "when total tBTC amount after staking would exceed max amount",
- () => {
- let possibleMaxAmountToStake: bigint
- let amountToStake: bigint
+ it("the staker B should be able to redeem more tokens than before", async () => {
+ const shares = await acre.balanceOf(staker2.address)
+ const availableAssetsToRedeem = await acre.previewRedeem(shares)
- before(async () => {
- await afterSimulatingYieldSnapshot.restore()
+ // Expected amount w/o rounding: 3 * 15 / 10 = 4.5
+ // Expected amount w/ support for rounding: 4499999999999999999 in
+ // tBTC token precision.
+ const expectedAssetsToRedeem = 4499999999999999999n
- // In the current implementation of the `maxDeposit` the
- // `address` param is not taken into account - it means it will
- // return the same value for any address.
- possibleMaxAmountToStake = await acre.maxDeposit(staker1.address)
- amountToStake = possibleMaxAmountToStake + 1n
+ expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem)
+ })
+ })
- await tbtc
- .connect(staker1)
- .approve(await acre.getAddress(), amountToStake)
- })
-
- it("should revert", async () => {
- await expect(acre.stake(amountToStake, staker1.address, referral))
- .to.be.revertedWithCustomError(
- acre,
- "ERC4626ExceededMaxDeposit",
+ context("when staker A stakes more tokens", () => {
+ context(
+ "when total tBTC amount after staking would not exceed max amount",
+ () => {
+ const newAmountToStake = to1e18(2)
+ // Current state:
+ // Total assets = 7(staker A) + 3(staker B) + 5(yield)
+ // Total shares = 7 + 3 = 10
+ // New stake amount = 2
+ // Shares to mint = 2 * 10 / 15 = 1.(3) -> 1333333333333333333 in stBTC
+ // token precision
+ const expectedSharesToMint = 1333333333333333333n
+ let sharesBefore: bigint
+ let availableToRedeemBefore: bigint
+
+ before(async () => {
+ await afterSimulatingYieldSnapshot.restore()
+
+ sharesBefore = await acre.balanceOf(staker1.address)
+ availableToRedeemBefore = await acre.previewRedeem(sharesBefore)
+
+ await tbtc.mint(staker1.address, newAmountToStake)
+
+ await tbtc
+ .connect(staker1)
+ .approve(await acre.getAddress(), newAmountToStake)
+
+ // State after stake:
+ // Total assets = 7(staker A) + 3(staker B) + 5(yield) + 2(staker
+ // A) = 17
+ // Total shares = 7 + 3 + 1.(3) = 11.(3)
+ await acre
+ .connect(staker1)
+ .stake(newAmountToStake, staker1.address, referral)
+ })
+
+ it("should receive more shares", async () => {
+ const shares = await acre.balanceOf(staker1.address)
+
+ expect(shares).to.be.eq(sharesBefore + expectedSharesToMint)
+ })
+
+ it("should be able to redeem more tokens than before", async () => {
+ const shares = await acre.balanceOf(staker1.address)
+ const availableToRedeem = await acre.previewRedeem(shares)
+
+ // Expected amount w/o rounding: 8.(3) * 17 / 11.(3) = 12.5
+ // Expected amount w/ support for rounding: 12499999999999999999 in
+ // tBTC token precision.
+ const expectedTotalAssetsAvailableToRedeem =
+ 12499999999999999999n
+
+ expect(availableToRedeem).to.be.greaterThan(
+ availableToRedeemBefore,
)
- .withArgs(
- staker1.address,
- amountToStake,
- possibleMaxAmountToStake,
+ expect(availableToRedeem).to.be.eq(
+ expectedTotalAssetsAvailableToRedeem,
)
- })
- },
- )
-
- context(
- "when total tBTC amount after staking would be equal to the max amount",
- () => {
- let amountToStake: bigint
- let tx: ContractTransactionResponse
-
- before(async () => {
- amountToStake = await acre.maxDeposit(staker1.address)
+ })
+ },
+ )
- await tbtc
- .connect(staker1)
- .approve(await acre.getAddress(), amountToStake)
+ context(
+ "when total tBTC amount after staking would exceed max amount",
+ () => {
+ let possibleMaxAmountToStake: bigint
+ let amountToStake: bigint
- tx = await acre.stake(amountToStake, staker1, referral)
- })
+ before(async () => {
+ await afterSimulatingYieldSnapshot.restore()
- it("should stake tokens correctly", async () => {
- await expect(tx).to.emit(acre, "Deposit")
- })
+ // In the current implementation of the `maxDeposit` the
+ // `address` param is not taken into account - it means it will
+ // return the same value for any address.
+ possibleMaxAmountToStake = await acre.maxDeposit(
+ staker1.address,
+ )
+ amountToStake = possibleMaxAmountToStake + 1n
- it("the max deposit amount should be equal 0", async () => {
- expect(await acre.maxDeposit(staker1)).to.eq(0)
- })
+ await tbtc
+ .connect(staker1)
+ .approve(await acre.getAddress(), amountToStake)
+ })
- it("should not stake more tokens", async () => {
- await expect(acre.stake(amountToStake, staker1, referral))
- .to.be.revertedWithCustomError(
- acre,
- "ERC4626ExceededMaxDeposit",
+ it("should revert", async () => {
+ await expect(
+ acre.stake(amountToStake, staker1.address, referral),
)
- .withArgs(staker1.address, amountToStake, 0)
- })
- },
- )
+ .to.be.revertedWithCustomError(
+ acre,
+ "ERC4626ExceededMaxDeposit",
+ )
+ .withArgs(
+ staker1.address,
+ amountToStake,
+ possibleMaxAmountToStake,
+ )
+ })
+ },
+ )
+
+ context(
+ "when total tBTC amount after staking would be equal to the max amount",
+ () => {
+ let amountToStake: bigint
+ let tx: ContractTransactionResponse
+
+ before(async () => {
+ amountToStake = await acre.maxDeposit(staker1.address)
+
+ await tbtc
+ .connect(staker1)
+ .approve(await acre.getAddress(), amountToStake)
+
+ tx = await acre.stake(amountToStake, staker1, referral)
+ })
+
+ it("should stake tokens correctly", async () => {
+ await expect(tx).to.emit(acre, "Deposit")
+ })
+
+ it("the max deposit amount should be equal 0", async () => {
+ expect(await acre.maxDeposit(staker1)).to.eq(0)
+ })
+
+ it("should not be able to stake more tokens", async () => {
+ await expect(acre.stake(amountToStake, staker1, referral))
+ .to.be.revertedWithCustomError(
+ acre,
+ "ERC4626ExceededMaxDeposit",
+ )
+ .withArgs(staker1.address, amountToStake, 0)
+ })
+ },
+ )
+ })
})
})
})
describe("mint", () => {
- let snapshot: SnapshotRestorer
-
- beforeEach(async () => {
- snapshot = await takeSnapshot()
- })
-
- afterEach(async () => {
- await snapshot.restore()
- })
+ beforeAfterEachSnapshotWrapper()
context("when minting as first staker", () => {
const amountToStake = to1e18(1)
@@ -635,17 +623,10 @@ describe("Acre", () => {
})
describe("updateDepositParameters", () => {
+ beforeAfterEachSnapshotWrapper()
+
const validMinimumDepositAmount = to1e18(1)
const validMaximumTotalAssetsAmount = to1e18(30)
- let snapshot: SnapshotRestorer
-
- beforeEach(async () => {
- snapshot = await takeSnapshot()
- })
-
- afterEach(async () => {
- await snapshot.restore()
- })
context("when is called by governance", () => {
context("when all parameters are valid", () => {
@@ -729,20 +710,16 @@ describe("Acre", () => {
})
describe("maxDeposit", () => {
+ beforeAfterEachSnapshotWrapper()
+
let maximumTotalAssets: bigint
let minimumDepositAmount: bigint
- let snapshot: SnapshotRestorer
beforeEach(async () => {
- snapshot = await takeSnapshot()
;[minimumDepositAmount, maximumTotalAssets] =
await acre.depositParameters()
})
- afterEach(async () => {
- await snapshot.restore()
- })
-
context(
"when total assets is greater than maximum total assets amount",
() => {
@@ -884,20 +861,16 @@ describe("Acre", () => {
})
describe("maxMint", () => {
+ beforeAfterEachSnapshotWrapper()
+
let maximumTotalAssets: bigint
let minimumDepositAmount: bigint
- let snapshot: SnapshotRestorer
beforeEach(async () => {
- snapshot = await takeSnapshot()
;[minimumDepositAmount, maximumTotalAssets] =
await acre.depositParameters()
})
- afterEach(async () => {
- await snapshot.restore()
- })
-
context(
"when total assets is greater than maximum total assets amount",
() => {
@@ -988,26 +961,24 @@ describe("Acre", () => {
})
describe("deposit", () => {
+ beforeAfterEachSnapshotWrapper()
+
+ const receiver = ethers.Wallet.createRandom()
+
let amountToDeposit: bigint
let minimumDepositAmount: bigint
- let snapshot: SnapshotRestorer
beforeEach(async () => {
- snapshot = await takeSnapshot()
minimumDepositAmount = await acre.minimumDepositAmount()
})
- afterEach(async () => {
- await snapshot.restore()
- })
-
context("when the deposit amount is less than minimum", () => {
beforeEach(() => {
amountToDeposit = minimumDepositAmount - 1n
})
it("should revert", async () => {
- await expect(acre.deposit(amountToDeposit, staker1.address))
+ await expect(acre.deposit(amountToDeposit, receiver.address))
.to.be.revertedWithCustomError(acre, "DepositAmountLessThanMin")
.withArgs(amountToDeposit, minimumDepositAmount)
})
@@ -1024,7 +995,9 @@ describe("Acre", () => {
expectedReceivedShares = amountToDeposit
await tbtc.approve(await acre.getAddress(), amountToDeposit)
- tx = await acre.deposit(amountToDeposit, staker1.address)
+ tx = await acre
+ .connect(staker1)
+ .deposit(amountToDeposit, receiver.address)
})
it("should emit Deposit event", async () => {
@@ -1032,7 +1005,7 @@ describe("Acre", () => {
// Caller.
staker1.address,
// Receiver.
- staker1.address,
+ receiver.address,
// Staked tokens.
amountToDeposit,
// Received shares.
@@ -1043,7 +1016,7 @@ describe("Acre", () => {
it("should mint stBTC tokens", async () => {
await expect(tx).to.changeTokenBalances(
acre,
- [staker1.address],
+ [receiver.address],
[expectedReceivedShares],
)
})
diff --git a/core/test/Dispatcher.test.ts b/core/test/Dispatcher.test.ts
index 435ee2808..eabcc46ac 100644
--- a/core/test/Dispatcher.test.ts
+++ b/core/test/Dispatcher.test.ts
@@ -1,15 +1,19 @@
import { ethers } from "hardhat"
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"
import { expect } from "chai"
+import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"
+
+import { ContractTransactionResponse, ZeroAddress } from "ethers"
import {
- SnapshotRestorer,
- takeSnapshot,
- loadFixture,
-} from "@nomicfoundation/hardhat-toolbox/network-helpers"
-import { ZeroAddress } from "ethers"
+ beforeAfterEachSnapshotWrapper,
+ beforeAfterSnapshotWrapper,
+ deployment,
+ getNamedSigner,
+ getUnnamedSigner,
+} from "./helpers"
+
import type { Dispatcher, TestERC4626, Acre, TestERC20 } from "../typechain"
-import { deployment } from "./helpers/context"
-import { getNamedSigner, getUnnamedSigner } from "./helpers/signer"
+
import { to1e18 } from "./utils"
async function fixture() {
@@ -21,8 +25,6 @@ async function fixture() {
}
describe("Dispatcher", () => {
- let snapshot: SnapshotRestorer
-
let dispatcher: Dispatcher
let vault: TestERC4626
let tbtc: TestERC20
@@ -46,16 +48,12 @@ describe("Dispatcher", () => {
vaultAddress4 = await ethers.Wallet.createRandom().getAddress()
})
- beforeEach(async () => {
- snapshot = await takeSnapshot()
- })
-
- afterEach(async () => {
- await snapshot.restore()
- })
-
describe("authorizeVault", () => {
+ beforeAfterSnapshotWrapper()
+
context("when caller is not a governance account", () => {
+ beforeAfterSnapshotWrapper()
+
it("should revert when adding a vault", async () => {
await expect(
dispatcher.connect(thirdParty).authorizeVault(vaultAddress1),
@@ -69,9 +67,11 @@ describe("Dispatcher", () => {
})
context("when caller is a governance account", () => {
+ beforeAfterSnapshotWrapper()
+
let tx: ContractTransactionResponse
- beforeEach(async () => {
+ before(async () => {
tx = await dispatcher.connect(governance).authorizeVault(vaultAddress1)
await dispatcher.connect(governance).authorizeVault(vaultAddress2)
await dispatcher.connect(governance).authorizeVault(vaultAddress3)
@@ -103,7 +103,9 @@ describe("Dispatcher", () => {
})
describe("deauthorizeVault", () => {
- beforeEach(async () => {
+ beforeAfterSnapshotWrapper()
+
+ before(async () => {
await dispatcher.connect(governance).authorizeVault(vaultAddress1)
await dispatcher.connect(governance).authorizeVault(vaultAddress2)
await dispatcher.connect(governance).authorizeVault(vaultAddress3)
@@ -123,6 +125,8 @@ describe("Dispatcher", () => {
})
context("when caller is a governance account", () => {
+ beforeAfterEachSnapshotWrapper()
+
it("should deauthorize vaults", async () => {
await dispatcher.connect(governance).deauthorizeVault(vaultAddress1)
@@ -168,6 +172,8 @@ describe("Dispatcher", () => {
})
describe("depositToVault", () => {
+ beforeAfterSnapshotWrapper()
+
const assetsToAllocate = to1e18(100)
const minSharesOut = to1e18(100)
@@ -177,6 +183,8 @@ describe("Dispatcher", () => {
})
context("when caller is not maintainer", () => {
+ beforeAfterSnapshotWrapper()
+
it("should revert when depositing to a vault", async () => {
await expect(
dispatcher
@@ -192,6 +200,8 @@ describe("Dispatcher", () => {
context("when caller is maintainer", () => {
context("when vault is not authorized", () => {
+ beforeAfterSnapshotWrapper()
+
it("should revert", async () => {
const randomAddress = await ethers.Wallet.createRandom().getAddress()
await expect(
@@ -210,6 +220,8 @@ describe("Dispatcher", () => {
})
context("when allocation is successful", () => {
+ beforeAfterSnapshotWrapper()
+
let tx: ContractTransactionResponse
before(async () => {
@@ -244,6 +256,8 @@ describe("Dispatcher", () => {
context(
"when the expected returned shares are less than the actual returned shares",
() => {
+ beforeAfterSnapshotWrapper()
+
const sharesOut = assetsToAllocate
const minShares = to1e18(101)
@@ -263,6 +277,8 @@ describe("Dispatcher", () => {
})
describe("updateMaintainer", () => {
+ beforeAfterSnapshotWrapper()
+
let newMaintainer: string
before(async () => {
@@ -270,6 +286,8 @@ describe("Dispatcher", () => {
})
context("when caller is not an owner", () => {
+ beforeAfterSnapshotWrapper()
+
it("should revert", async () => {
await expect(
dispatcher.connect(thirdParty).updateMaintainer(newMaintainer),
@@ -284,6 +302,8 @@ describe("Dispatcher", () => {
context("when caller is an owner", () => {
context("when maintainer is a zero address", () => {
+ beforeAfterSnapshotWrapper()
+
it("should revert", async () => {
await expect(
dispatcher.connect(governance).updateMaintainer(ZeroAddress),
@@ -292,6 +312,8 @@ describe("Dispatcher", () => {
})
context("when maintainer is not a zero address", () => {
+ beforeAfterSnapshotWrapper()
+
let tx: ContractTransactionResponse
before(async () => {
diff --git a/core/test/helpers/context.ts b/core/test/helpers/context.ts
index d6b41a822..02d2cf8fa 100644
--- a/core/test/helpers/context.ts
+++ b/core/test/helpers/context.ts
@@ -1,5 +1,4 @@
import { deployments } from "hardhat"
-
import { getDeployedContract } from "./contract"
import type {
diff --git a/core/test/helpers/index.ts b/core/test/helpers/index.ts
index 27ddcb0b9..e4df2196a 100644
--- a/core/test/helpers/index.ts
+++ b/core/test/helpers/index.ts
@@ -1,3 +1,4 @@
export * from "./context"
export * from "./contract"
export * from "./signer"
+export * from "./snapshot"
diff --git a/core/test/helpers/snapshot.ts b/core/test/helpers/snapshot.ts
index 9a5caf2c9..804c0455d 100644
--- a/core/test/helpers/snapshot.ts
+++ b/core/test/helpers/snapshot.ts
@@ -1,4 +1,3 @@
-/* eslint-disable import/prefer-default-export */
import {
SnapshotRestorer,
takeSnapshot,
@@ -19,3 +18,19 @@ export function beforeAfterSnapshotWrapper(): void {
await snapshot.restore()
})
}
+
+/**
+ * Adds a beforeEach/afterEach hook pair to snapshot the EVM state before and
+ * after each of tests in a test suite.
+ */
+export function beforeAfterEachSnapshotWrapper(): void {
+ let snapshot: SnapshotRestorer
+
+ beforeEach(async () => {
+ snapshot = await takeSnapshot()
+ })
+
+ afterEach(async () => {
+ await snapshot.restore()
+ })
+}
diff --git a/dapp/.eslintrc b/dapp/.eslintrc
index 4317c87a7..35437e4ed 100644
--- a/dapp/.eslintrc
+++ b/dapp/.eslintrc
@@ -15,6 +15,73 @@
2,
{ "allowRequiredDefaults": true }
],
- "react/require-default-props": [0]
+ "react/require-default-props": [0],
+ },
+ // FIXME:
+ // This is temporary solution after changes of the eslint-config version: @thesis-co/eslint-config: "github:thesis/eslint-config#7b9bc8c"
+ // Overrides rules should be fixed file by file.
+ "overrides": [
+ {
+ "files": [
+ "src/components/Header/ConnectWallet.tsx",
+ "src/components/Modals/Support/MissingAccount.tsx",
+ "src/components/Modals/Staking/SignMessage.tsx",
+ "src/hooks/useDepositBTCTransaction.ts",
+ "src/components/shared/Form/FormTokenBalanceInput.tsx"
+ ],
+ "rules": {
+ "@typescript-eslint/no-misused-promises": "off"
+ }
+ },
+ {
+ "files": [
+ "src/hooks/useSignMessage.ts"
+ ],
+ "rules": {
+ "@typescript-eslint/no-floating-promises": "off"
+ }
+ },
+ {
+ "files": [
+ "src/theme/*"
+ ],
+ "rules": {
+ "@typescript-eslint/unbound-method": "off"
+ }
+ },
+ {
+ "files": [
+ "src/theme/Alert.ts"
+ ],
+ "rules": {
+ "@typescript-eslint/no-unsafe-member-access": "off"
+ }
+ },
+ {
+ "files": [
+ "src/components/shared/Form/FormTokenBalanceInput.tsx"
+ ],
+ "rules": {
+ "@typescript-eslint/no-unsafe-assignment": "off"
+ }
+ },
+ {
+ "files": [
+ "src/components/shared/TokenAmountForm/index.tsx"
+ ],
+ "rules": {
+ "@typescript-eslint/require-await": "off"
+ }
+ }
+ ],
+ "settings": {
+ "import/resolver": {
+ "alias": {
+ "map": [
+ ["#", "./src"]
+ ],
+ "extensions": [".js", ".jsx",".ts", ".tsx"]
+ }
+ }
}
}
diff --git a/dapp/manifest-ledger-live-app.json b/dapp/manifest-ledger-live-app.json
index 29f63c113..265928a59 100644
--- a/dapp/manifest-ledger-live-app.json
+++ b/dapp/manifest-ledger-live-app.json
@@ -18,6 +18,6 @@
"en": "Bitcoin Liquid Staking"
}
},
- "permissions": ["account.request"],
+ "permissions": ["account.request", "message.sign"],
"domains": ["http://*"]
}
diff --git a/dapp/package.json b/dapp/package.json
index 2d160d0a4..34cc514a8 100644
--- a/dapp/package.json
+++ b/dapp/package.json
@@ -20,21 +20,25 @@
"@emotion/styled": "^11.11.0",
"@ledgerhq/wallet-api-client": "^1.5.0",
"@ledgerhq/wallet-api-client-react": "^1.3.0",
+ "formik": "^2.4.5",
"framer-motion": "^10.16.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-number-format": "^5.3.1"
},
"devDependencies": {
- "@thesis-co/eslint-config": "^0.6.1",
+ "@thesis-co/eslint-config": "github:thesis/eslint-config#7b9bc8c",
"@types/react": "^18.2.38",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"@vitejs/plugin-react": "^4.2.0",
"eslint": "^8.54.0",
+ "eslint-import-resolver-alias": "^1.1.2",
+ "eslint-plugin-import": "^2.29.1",
"prettier": "^3.1.0",
"typescript": "^5.3.2",
- "vite": "^5.0.2"
+ "vite": "^5.0.2",
+ "vite-plugin-node-polyfills": "^0.19.0"
}
}
diff --git a/dapp/src/components/DocsDrawer/index.tsx b/dapp/src/components/DocsDrawer/index.tsx
index 5082af57b..060c6e731 100644
--- a/dapp/src/components/DocsDrawer/index.tsx
+++ b/dapp/src/components/DocsDrawer/index.tsx
@@ -5,8 +5,8 @@ import {
DrawerContent,
DrawerOverlay,
} from "@chakra-ui/react"
-import { useDocsDrawer } from "../../hooks"
-import { TextMd } from "../shared/Typography"
+import { useDocsDrawer } from "#/hooks"
+import { TextMd } from "#/components/shared/Typography"
export default function DocsDrawer() {
const { isOpen, onClose } = useDocsDrawer()
diff --git a/dapp/src/components/GlobalStyles/index.tsx b/dapp/src/components/GlobalStyles/index.tsx
index 80b36e6e8..352fe7af0 100644
--- a/dapp/src/components/GlobalStyles/index.tsx
+++ b/dapp/src/components/GlobalStyles/index.tsx
@@ -1,11 +1,11 @@
import React from "react"
import { Global } from "@emotion/react"
-import SegmentRegular from "../../fonts/Segment-Regular.otf"
-import SegmentMedium from "../../fonts/Segment-Medium.otf"
-import SegmentSemiBold from "../../fonts/Segment-SemiBold.otf"
-import SegmentBold from "../../fonts/Segment-Bold.otf"
-import SegmentBlack from "../../fonts/Segment-Black.otf"
+import SegmentRegular from "#/fonts/Segment-Regular.otf"
+import SegmentMedium from "#/fonts/Segment-Medium.otf"
+import SegmentSemiBold from "#/fonts/Segment-SemiBold.otf"
+import SegmentBold from "#/fonts/Segment-Bold.otf"
+import SegmentBlack from "#/fonts/Segment-Black.otf"
export default function GlobalStyles() {
return (
diff --git a/dapp/src/components/Header/ConnectWallet.tsx b/dapp/src/components/Header/ConnectWallet.tsx
index 3bb5af7d9..5319a04ca 100644
--- a/dapp/src/components/Header/ConnectWallet.tsx
+++ b/dapp/src/components/Header/ConnectWallet.tsx
@@ -1,15 +1,15 @@
import React from "react"
import { Button, HStack, Icon } from "@chakra-ui/react"
import { Account } from "@ledgerhq/wallet-api-client"
-import { Bitcoin, Ethereum } from "../../static/icons"
import {
useRequestBitcoinAccount,
useRequestEthereumAccount,
useWalletContext,
-} from "../../hooks"
-import { truncateAddress } from "../../utils"
-import { CurrencyBalance } from "../shared/CurrencyBalance"
-import { TextMd } from "../shared/Typography"
+} from "#/hooks"
+import { CurrencyBalance } from "#/components/shared/CurrencyBalance"
+import { TextMd } from "#/components/shared/Typography"
+import { Bitcoin, Ethereum } from "#/static/icons"
+import { truncateAddress } from "#/utils"
export type ConnectButtonsProps = {
leftIcon: typeof Icon
@@ -46,7 +46,7 @@ export default function ConnectWallet() {
Balance
diff --git a/dapp/src/components/Header/index.tsx b/dapp/src/components/Header/index.tsx
index 544fbdee9..480cb502e 100644
--- a/dapp/src/components/Header/index.tsx
+++ b/dapp/src/components/Header/index.tsx
@@ -1,7 +1,7 @@
import React from "react"
import { Flex, HStack, Icon } from "@chakra-ui/react"
+import { AcreLogo } from "#/static/icons"
import ConnectWallet from "./ConnectWallet"
-import { AcreLogo } from "../../static/icons"
export default function Header() {
return (
diff --git a/dapp/src/components/Modals/ActionForm/index.tsx b/dapp/src/components/Modals/ActionForm/index.tsx
new file mode 100644
index 000000000..eabb156bb
--- /dev/null
+++ b/dapp/src/components/Modals/ActionForm/index.tsx
@@ -0,0 +1,41 @@
+import React from "react"
+import {
+ ModalBody,
+ Tabs,
+ TabList,
+ Tab,
+ TabPanels,
+ TabPanel,
+} from "@chakra-ui/react"
+import { useModalFlowContext } from "#/hooks"
+import StakeForm from "../Staking/StakeForm"
+
+const TABS = ["stake", "unstake"] as const
+
+type Action = (typeof TABS)[number]
+
+function ActionForm({ action }: { action: Action }) {
+ const { goNext } = useModalFlowContext()
+
+ return (
+
+
+
+ {TABS.map((tab) => (
+
+ {tab}
+
+ ))}
+
+
+
+
+
+ {/* TODO: Add form for unstake */}
+
+
+
+ )
+}
+
+export default ActionForm
diff --git a/dapp/src/components/Modals/Staking/DepositBTC.tsx b/dapp/src/components/Modals/Staking/DepositBTC.tsx
new file mode 100644
index 000000000..924a0ee24
--- /dev/null
+++ b/dapp/src/components/Modals/Staking/DepositBTC.tsx
@@ -0,0 +1,20 @@
+import React from "react"
+import { useDepositBTCTransaction, useModalFlowContext } from "#/hooks"
+import Alert from "#/components/shared/Alert"
+import { TextMd } from "#/components/shared/Typography"
+import StakingSteps from "./components/StakingSteps"
+
+export default function DepositBTC() {
+ const { goNext } = useModalFlowContext()
+ const { depositBTC } = useDepositBTCTransaction(goNext)
+
+ return (
+
+
+
+ Make a Bitcoin transaction to deposit and stake your BTC.
+
+
+
+ )
+}
diff --git a/dapp/src/components/Modals/Staking/Overview/StakingSteps.tsx b/dapp/src/components/Modals/Staking/Overview/StakingSteps.tsx
deleted file mode 100644
index b34437226..000000000
--- a/dapp/src/components/Modals/Staking/Overview/StakingSteps.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import React from "react"
-import { StepNumber } from "@chakra-ui/react"
-import { TextLg, TextMd } from "../../../shared/Typography"
-import StepperBase from "../../../shared/StepperBase"
-
-function Title({ children }: { children: React.ReactNode }) {
- return {children}
-}
-
-function Description({ children }: { children: React.ReactNode }) {
- return {children}
-}
-
-const STEPS = [
- {
- id: "sign-message",
- title:
Sign message,
- description: (
-
- You will sign a gas-free Ethereum message to indicate the address where
- you'd like to get your stBTC liquid staking token.
-
- ),
- },
- {
- id: "deposit-btc",
- title: Deposit BTC,
- description: (
-
- You will make a Bitcoin transaction to deposit and stake your BTC.
-
- ),
- },
-]
-
-export default function StakingSteps() {
- return (
- }
- steps={STEPS}
- />
- )
-}
diff --git a/dapp/src/components/Modals/Staking/Overview/index.tsx b/dapp/src/components/Modals/Staking/Overview/index.tsx
index 04f858ec4..af2485db6 100644
--- a/dapp/src/components/Modals/Staking/Overview/index.tsx
+++ b/dapp/src/components/Modals/Staking/Overview/index.tsx
@@ -1,14 +1,30 @@
import React from "react"
-import { Button, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react"
-import StakingSteps from "./StakingSteps"
-import { ModalStep } from "../../../../contexts"
+import {
+ Button,
+ ModalBody,
+ ModalFooter,
+ ModalHeader,
+ StepNumber,
+} from "@chakra-ui/react"
+import StepperBase from "#/components/shared/StepperBase"
+import { useModalFlowContext } from "#/hooks"
+import { STEPS } from "./steps"
+
+export default function Overview() {
+ const { goNext } = useModalFlowContext()
-export default function Overview({ goNext }: ModalStep) {
return (
<>
Staking steps overview
-
+ }
+ steps={STEPS}
+ />