diff --git a/core/contracts/Acre.sol b/core/contracts/Acre.sol
index bb8f357e9..0e1cf640f 100644
--- a/core/contracts/Acre.sol
+++ b/core/contracts/Acre.sol
@@ -4,14 +4,18 @@ 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 Staked(bytes32 indexed referral, uint256 assets, uint256 shares);
+ event StakeReferral(bytes32 indexed referral, uint256 assets);
constructor(
IERC20 tbtc
@@ -19,7 +23,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.
@@ -32,7 +37,9 @@ contract Acre is ERC4626 {
// TODO: revisit the type of referral.
uint256 shares = deposit(assets, receiver);
- emit Staked(referral, assets, shares);
+ if (referral != bytes32(0)) {
+ emit StakeReferral(referral, assets);
+ }
return shares;
}
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
diff --git a/core/test/Acre.test.ts b/core/test/Acre.test.ts
index 369f80704..74e764ca0 100644
--- a/core/test/Acre.test.ts
+++ b/core/test/Acre.test.ts
@@ -11,36 +11,36 @@ import type { TestERC20, Acre } from "../typechain"
import { to1e18 } from "./utils"
async function acreFixture() {
- const [staker, staker2] = await ethers.getSigners()
- const Token = await ethers.getContractFactory("TestERC20")
- const tbtc = await Token.deploy()
+ const [staker1, staker2] = await ethers.getSigners()
- const amountToMint = to1e18(100000)
-
- tbtc.mint(staker, amountToMint)
- tbtc.mint(staker2, amountToMint)
+ const TestERC20 = await ethers.getContractFactory("TestERC20")
+ const tbtc = await TestERC20.deploy()
const Acre = await ethers.getContractFactory("Acre")
const acre = await Acre.deploy(await tbtc.getAddress())
- return { acre, tbtc, staker, staker2 }
+ const amountToMint = to1e18(100000)
+ tbtc.mint(staker1, amountToMint)
+ tbtc.mint(staker2, amountToMint)
+
+ 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", () => {
+ describe("stake", () => {
const referral = ethers.encodeBytes32String("referral")
let snapshot: SnapshotRestorer
- context("when staking", () => {
+ context("when staking as first staker", () => {
beforeEach(async () => {
snapshot = await takeSnapshot()
})
@@ -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(staker)
+ .connect(tbtcHolder)
.approve(await acre.getAddress(), amountToStake)
tx = await acre
- .connect(staker)
- .stake(amountToStake, staker.address, referral)
+ .connect(tbtcHolder)
+ .stake(amountToStake, receiver.address, referral)
})
it("should emit Deposit event", () => {
expect(tx).to.emit(acre, "Deposit").withArgs(
// Caller.
- staker.address,
+ tbtcHolder.address,
// Receiver.
- staker.address,
+ receiver.address,
// Staked tokens.
amountToStake,
// Received shares.
@@ -82,62 +87,67 @@ 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 () => {
await expect(tx).to.changeTokenBalances(
acre,
- [staker.address],
- [amountToStake],
+ [receiver.address],
+ [expectedReceivedShares],
)
})
it("should transfer tBTC tokens", async () => {
await expect(tx).to.changeTokenBalances(
tbtc,
- [staker.address, acre],
+ [tbtcHolder.address, acre],
[-amountToStake, amountToStake],
)
})
})
context("without referral", () => {
+ const amountToStake = to1e18(10)
const emptyReferral = ethers.encodeBytes32String("")
let tx: ContractTransactionResponse
beforeEach(async () => {
- await tbtc.connect(staker).approve(await acre.getAddress(), 1)
+ await tbtc
+ .connect(staker1)
+ .approve(await acre.getAddress(), amountToStake)
tx = await acre
- .connect(staker)
- .stake(1, staker.address, emptyReferral)
+ .connect(staker1)
+ .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")
})
})
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(staker)
- .approve(await acre.getAddress(), amountToStake)
+ .connect(staker1)
+ .approve(await acre.getAddress(), approvedAmount)
})
it("should revert", async () => {
await expect(
acre
- .connect(staker)
- .stake(amountToStake + 1n, staker.address, referral),
- ).to.be.reverted
+ .connect(staker1)
+ .stake(amountToStake, staker1.address, referral),
+ ).to.be.revertedWithCustomError(tbtc, "ERC20InsufficientAllowance")
})
},
)
@@ -147,13 +157,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
})
})
@@ -163,13 +175,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")
})
})
@@ -181,19 +193,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")
})
},
@@ -201,41 +213,22 @@ 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 stakerAmountToStake = to1e18(75)
- const staker2AmountToStake = to1e18(25)
- // Infinite approval for staking contract.
await tbtc
- .connect(staker)
- .approve(await acre.getAddress(), ethers.MaxUint256)
+ .connect(staker1)
+ .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(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,
- }
- stakerB = {
- signer: staker2,
- address: staker2.address,
- amountToStake: staker2AmountToStake,
- }
})
context(
@@ -249,15 +242,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)
})
})
@@ -265,15 +258,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)
})
})
},
@@ -287,32 +280,25 @@ 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
before(async () => {
- await afterStakesSnapshot.restore()
-
- stakerASharesBefore = await acre.balanceOf(stakerA.address)
- stakerBSharesBefore = await acre.balanceOf(stakerB.address)
- vaultYield = to1e18(100)
+ // Current state:
// Staker A shares = 75
// Staker B shares = 25
- // Total assets = 75(staker A) + 25(staker B) + 100(yield)
- expectedTotalAssets =
- stakerA.amountToStake + stakerB.amountToStake + vaultYield
- // Total shares = 75 + 25 = 100
- expectedTotalSupply = stakerA.amountToStake + stakerB.amountToStake
+ // 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(50)
// Simulating yield returned from strategies. The vault now contains
// more tokens than deposited which causes the exchange rate to
@@ -326,105 +312,88 @@ 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
- // 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 =
- (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n)
+ const expectedAssetsToRedeem = 112499999999999999999n
- 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 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 =
- (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n)
+ const expectedAssetsToRedeem = 37499999999999999999n
- expect(availableAssetsToRedeem).to.be.greaterThan(
- stakerB.amountToStake,
- )
expect(availableAssetsToRedeem).to.be.eq(expectedAssetsToRedeem)
})
})
context("when staker A stakes more tokens", () => {
const newAmountToStake = to1e18(20)
+ // 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
- 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)
+ sharesBefore = await acre.balanceOf(staker1.address)
availableToRedeemBefore = await acre.previewRedeem(sharesBefore)
- totalAssetsBefore = await acre.totalAssets()
- totalSupplyBefore = await acre.totalSupply()
-
- tbtc.mint(stakerA.address, newAmountToStake)
- expectedSharesToMint =
- (newAmountToStake * (totalSupplyBefore + 1n)) /
- (totalAssetsBefore + 1n)
+ tbtc.mint(staker1.address, newAmountToStake)
- await acre.stake(newAmountToStake, stakerA.address, referral)
+ await tbtc
+ .connect(staker1)
+ .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
- expectedTotalAssets = totalAssetsBefore + newAmountToStake
- expectedTotalSupply = totalSupplyBefore + expectedSharesToMint
+ // 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)
})
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
- // 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 =
- (shares * (expectedTotalAssets + 1n)) / (expectedTotalSupply + 1n)
+ const expectedTotalAssetsAvailableToRedeem = 132499999999999999999n
expect(availableToRedeem).to.be.greaterThan(availableToRedeemBefore)
expect(availableToRedeem).to.be.eq(
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..39ec6d51b 100644
--- a/dapp/src/theme/index.ts
+++ b/dapp/src/theme/index.ts
@@ -1,10 +1,13 @@
import { StyleFunctionProps, extendTheme } from "@chakra-ui/react"
import { mode } from "@chakra-ui/theme-tools"
import Button from "./Button"
-import { colors } from "./utils"
+import { colors, fontSizes, fontWeights, lineHeights } from "./utils"
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",
+}
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"