From bb42bce291dc77a64d77a6500492ef23714aeb03 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 2 May 2024 14:28:43 +0200 Subject: [PATCH] Adding current balance of tBTC to depositBalancer var in MezoAllocator Included tbtc.balanceOf(address(this)) to calculate the total assets held by Mezo Allocator contract. This is to properly calculate all the assets including any 'donations' that were made directly to Mezo Allocator contract. As a result, new deposits will receive a correct amount of shares. Without this fix, new deposits could've received more shares that intended. --- solidity/contracts/MezoAllocator.sol | 5 +- .../test/upgrades/MezoAllocatorV2.sol | 5 +- solidity/test/MezoAllocator.test.ts | 204 +++++++++++++----- 3 files changed, 155 insertions(+), 59 deletions(-) diff --git a/solidity/contracts/MezoAllocator.sol b/solidity/contracts/MezoAllocator.sol index 59eac6adb..40a348bde 100644 --- a/solidity/contracts/MezoAllocator.sol +++ b/solidity/contracts/MezoAllocator.sol @@ -240,9 +240,10 @@ contract MezoAllocator is IDispatcher, Ownable2StepUpgradeable { emit MaintainerRemoved(maintainerToRemove); } - /// @notice Returns the total amount of tBTC allocated to MezoPortal. + /// @notice Returns the total amount of tBTC allocated to MezoPortal including + /// the amount that is currently hold by this contract. function totalAssets() external view returns (uint256) { - return depositBalance; + return depositBalance + tbtc.balanceOf(address(this)); } /// @notice Returns the list of maintainers. diff --git a/solidity/contracts/test/upgrades/MezoAllocatorV2.sol b/solidity/contracts/test/upgrades/MezoAllocatorV2.sol index 2457510a4..ef026c00d 100644 --- a/solidity/contracts/test/upgrades/MezoAllocatorV2.sol +++ b/solidity/contracts/test/upgrades/MezoAllocatorV2.sol @@ -238,9 +238,10 @@ contract MezoAllocatorV2 is IDispatcher, Ownable2StepUpgradeable { emit MaintainerRemoved(maintainerToRemove); } - /// @notice Returns the total amount of tBTC allocated to MezoPortal. + /// @notice Returns the total amount of tBTC allocated to MezoPortal including + /// the amount that is currently hold by this contract. function totalAssets() external view returns (uint256) { - return depositBalance; + return depositBalance + tbtc.balanceOf(address(this)); } /// @notice Returns the list of maintainers. diff --git a/solidity/test/MezoAllocator.test.ts b/solidity/test/MezoAllocator.test.ts index 4e45a09fe..6721f22ba 100644 --- a/solidity/test/MezoAllocator.test.ts +++ b/solidity/test/MezoAllocator.test.ts @@ -20,12 +20,13 @@ const { getNamedSigners, getUnnamedSigners } = helpers.signers async function fixture() { const { tbtc, stbtc, mezoAllocator, mezoPortal } = await deployment() const { governance, maintainer } = await getNamedSigners() - const [depositor, thirdParty] = await getUnnamedSigners() + const [depositor, depositor2, thirdParty] = await getUnnamedSigners() return { governance, thirdParty, depositor, + depositor2, maintainer, tbtc, stbtc, @@ -42,6 +43,7 @@ describe("MezoAllocator", () => { let thirdParty: HardhatEthersSigner let depositor: HardhatEthersSigner + let depositor2: HardhatEthersSigner let maintainer: HardhatEthersSigner let governance: HardhatEthersSigner @@ -49,6 +51,7 @@ describe("MezoAllocator", () => { ;({ thirdParty, depositor, + depositor2, maintainer, governance, tbtc, @@ -70,84 +73,175 @@ describe("MezoAllocator", () => { }) context("when the caller is maintainer", () => { - context("when a first deposit is made", () => { - let tx: ContractTransactionResponse + context("when two consecutive deposits are made", () => { + beforeAfterSnapshotWrapper() - before(async () => { - await tbtc.mint(await stbtc.getAddress(), to1e18(6)) - tx = await mezoAllocator.connect(maintainer).allocate() - }) + context("when a first deposit is made", () => { + let tx: ContractTransactionResponse - it("should deposit and transfer tBTC to Mezo Portal", async () => { - await expect(tx).to.changeTokenBalances( - tbtc, - [await mezoPortal.getAddress()], - [to1e18(6)], - ) - }) + before(async () => { + await tbtc.mint(await stbtc.getAddress(), to1e18(6)) + tx = await mezoAllocator.connect(maintainer).allocate() + }) - it("should not store any tBTC in Mezo Allocator", async () => { - expect( - await tbtc.balanceOf(await mezoAllocator.getAddress()), - ).to.equal(0) - }) + it("should deposit and transfer tBTC to Mezo Portal", async () => { + await expect(tx).to.changeTokenBalances( + tbtc, + [await mezoPortal.getAddress()], + [to1e18(6)], + ) + }) - it("should increment the deposit id", async () => { - const actualDepositId = await mezoAllocator.depositId() - expect(actualDepositId).to.equal(1) - }) + it("should not store any tBTC in Mezo Allocator", async () => { + expect( + await tbtc.balanceOf(await mezoAllocator.getAddress()), + ).to.equal(0) + }) - it("should increase tracked deposit balance amount", async () => { - const depositBalance = await mezoAllocator.depositBalance() - expect(depositBalance).to.equal(to1e18(6)) + it("should increment the deposit id", async () => { + const actualDepositId = await mezoAllocator.depositId() + expect(actualDepositId).to.equal(1) + }) + + it("should increase tracked deposit balance amount", async () => { + const depositBalance = await mezoAllocator.depositBalance() + expect(depositBalance).to.equal(to1e18(6)) + }) + + it("should emit DepositAllocated event", async () => { + await expect(tx) + .to.emit(mezoAllocator, "DepositAllocated") + .withArgs(0, 1, to1e18(6), to1e18(6)) + }) }) - it("should emit DepositAllocated event", async () => { - await expect(tx) - .to.emit(mezoAllocator, "DepositAllocated") - .withArgs(0, 1, to1e18(6), to1e18(6)) + context("when a second deposit is made", () => { + let tx: ContractTransactionResponse + + before(async () => { + await tbtc.mint(await stbtc.getAddress(), to1e18(5)) + + tx = await mezoAllocator.connect(maintainer).allocate() + }) + + it("should increment the deposit id", async () => { + const actualDepositId = await mezoAllocator.depositId() + expect(actualDepositId).to.equal(2) + }) + + it("should emit DepositAllocated event", async () => { + await expect(tx) + .to.emit(mezoAllocator, "DepositAllocated") + .withArgs(1, 2, to1e18(5), to1e18(11)) + }) + + it("should deposit and transfer tBTC to Mezo Portal", async () => { + expect( + await tbtc.balanceOf(await mezoPortal.getAddress()), + ).to.equal(to1e18(11)) + }) + + it("should increase tracked deposit balance amount", async () => { + const depositBalance = await mezoAllocator.depositBalance() + expect(depositBalance).to.equal(to1e18(11)) + }) + + it("should not store any tBTC in Mezo Allocator", async () => { + expect( + await tbtc.balanceOf(await mezoAllocator.getAddress()), + ).to.equal(0) + }) + + it("should not store any tBTC in stBTC", async () => { + expect(await tbtc.balanceOf(await stbtc.getAddress())).to.equal(0) + }) }) }) - context("when a second deposit is made", () => { - let tx: ContractTransactionResponse + context("when accounting for tBTC 'donation' to Mezo Allocator", () => { + let depositorDepositTx: ContractTransactionResponse + let depositorRedeemTx: ContractTransactionResponse + let depositor2DepositTx: ContractTransactionResponse + let depositor2RedeemTx: ContractTransactionResponse + + beforeAfterSnapshotWrapper() before(async () => { - await tbtc.mint(await stbtc.getAddress(), to1e18(5)) + await tbtc.mint(depositor, to1e18(1)) + await tbtc + .connect(depositor) + .approve(await stbtc.getAddress(), to1e18(1)) + // Deposit by the first depositor + depositorDepositTx = await stbtc + .connect(depositor) + .deposit(to1e18(1), depositor) + + // Mezo Portal first allocation + await mezoAllocator.connect(maintainer).allocate() - tx = await mezoAllocator.connect(maintainer).allocate() - }) + // Donation / rewards + await tbtc.mint(await mezoAllocator.getAddress(), to1e18(1)) + + await tbtc.mint(depositor2, to1e18(1)) + await tbtc + .connect(depositor2) + .approve(await stbtc.getAddress(), to1e18(1)) + // Deposit by the second depositor + depositor2DepositTx = await stbtc + .connect(depositor2) + .deposit(to1e18(1), depositor2) + // Mezo Portal second allocation + await mezoAllocator.connect(maintainer).allocate() - it("should increment the deposit id", async () => { - const actualDepositId = await mezoAllocator.depositId() - expect(actualDepositId).to.equal(2) - }) + // Redeeming shares by the first depositor + const stBTCdepositorBalance = await stbtc.balanceOf(depositor) + depositorRedeemTx = await stbtc + .connect(depositor) + .redeem(stBTCdepositorBalance, depositor, depositor) - it("should emit DepositAllocated event", async () => { - await expect(tx) - .to.emit(mezoAllocator, "DepositAllocated") - .withArgs(1, 2, to1e18(5), to1e18(11)) + // Redeeming shares by the second depositor + const stBTCdepositor2Balance = await stbtc.balanceOf(depositor2) + depositor2RedeemTx = await stbtc + .connect(depositor2) + .redeem(stBTCdepositor2Balance, depositor2, depositor2) }) - it("should deposit and transfer tBTC to Mezo Portal", async () => { - expect(await tbtc.balanceOf(await mezoPortal.getAddress())).to.equal( - to1e18(11), + it("should mint correct amount of shares for the first depositor", async () => { + await expect(depositorDepositTx).to.changeTokenBalances( + stbtc, + [depositor.address], + [to1e18(1)], ) }) - it("should increase tracked deposit balance amount", async () => { - const depositBalance = await mezoAllocator.depositBalance() - expect(depositBalance).to.equal(to1e18(11)) + it("should mint correct amount of shares for the second depositor", async () => { + // expected shares = (assets * total supply of shares) / total assets + // expected shares = (1 * 1 stBTC) / 2 tBTC = 0.5 + await expect(depositor2DepositTx).to.changeTokenBalances( + stbtc, + [depositor2.address], + [500000000000000000n], // 0.5 stBTC + ) }) - it("should not store any tBTC in Mezo Allocator", async () => { - expect( - await tbtc.balanceOf(await mezoAllocator.getAddress()), - ).to.equal(0) + it("should redeem shares with accounting for 'donation' for the first depositor", async () => { + // expected tBTC = shares * total assets / total supply of shares + // expected tBTC = (1 * 3) / 1.5 = 2 + await expect(depositorRedeemTx).to.changeTokenBalances( + tbtc, + [depositor.address], + [to1e18(2) - 1n], // adjusted for rounding + ) }) - it("should not store any tBTC in stBTC", async () => { - expect(await tbtc.balanceOf(await stbtc.getAddress())).to.equal(0) + it("should redeem shares without accounting for 'donation' for the second depositor", async () => { + // expected tBTC = shares * total assets / total supply of shares + // expected tBTC = (0.5 * 3) / 1.5 = 2 + await expect(depositor2RedeemTx).to.changeTokenBalances( + tbtc, + [depositor2.address], + [to1e18(1)], + ) }) }) })