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)], + ) }) }) })