// SPDX-License-Identifier: MIT pragma solidity 0.8.35; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import {Options, Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; import {BankV1} from "../src/BankV1.sol"; import {BankV2} from "../src/BankV2.sol"; import {BankTestBase} from "./helpers/BankTestBase.sol"; event BalanceTransferred(address indexed from, address indexed to, uint256 amount); contract BankV2Test is BankTestBase { uint256 private constant ALICE_DEPOSIT = 1_000e6; uint256 private constant BOB_DEPOSIT = 500e6; BankV2 internal bankV2; function setUp() public override { super.setUp(); _deposit(alice, ALICE_DEPOSIT); _deposit(bob, BOB_DEPOSIT); Options memory opts; opts.referenceContract = "BankV1.sol:BankV1"; Upgrades.upgradeProxy(proxy, "BankV2.sol:BankV2", "", opts, owner); bankV2 = BankV2(proxy); } function testTransferBalanceMoves250MillionUnitsAndEmitsExactEvent() public { vm.expectEmit(true, true, false, true, proxy); emit BalanceTransferred(alice, bob, 250e6); vm.prank(alice); bankV2.transferBalance(bob, 250e6); assertEq(bankV2.balanceOf(alice), 750e6); assertEq(bankV2.balanceOf(bob), 750e6); } function testTransferBalanceLeavesLiabilitiesAndTokenReservesUnchanged() public { uint256 liabilitiesBefore = bankV2.totalLiabilities(); uint256 reservesBefore = token.balanceOf(proxy); vm.prank(alice); bankV2.transferBalance(bob, 250e6); assertEq(bankV2.totalLiabilities(), liabilitiesBefore); assertEq(token.balanceOf(proxy), reservesBefore); } function testTransferBalanceRejectsZeroAmount() public { vm.prank(alice); vm.expectRevert(BankV1.ZeroAmount.selector); bankV2.transferBalance(bob, 0); } function testTransferBalanceRejectsZeroRecipient() public { vm.prank(alice); vm.expectRevert(abi.encodeWithSelector(BankV2.InvalidRecipient.selector, address(0))); bankV2.transferBalance(address(0), 1); } function testTransferBalanceRejectsSenderAsRecipient() public { vm.prank(alice); vm.expectRevert(BankV2.SelfTransfer.selector); bankV2.transferBalance(alice, 1); } function testTransferBalanceReportsAvailableAndRequestedWhenBalanceIsInsufficient() public { vm.prank(alice); vm.expectRevert( abi.encodeWithSelector(BankV1.InsufficientBalance.selector, alice, ALICE_DEPOSIT, ALICE_DEPOSIT + 1) ); bankV2.transferBalance(bob, ALICE_DEPOSIT + 1); assertEq(bankV2.balanceOf(alice), ALICE_DEPOSIT); assertEq(bankV2.balanceOf(bob), BOB_DEPOSIT); } function testTransferBalanceRejectsCallsWhilePaused() public { vm.prank(owner); bankV2.pause(); vm.prank(alice); vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); bankV2.transferBalance(bob, 1); assertEq(bankV2.balanceOf(alice), ALICE_DEPOSIT); assertEq(bankV2.balanceOf(bob), BOB_DEPOSIT); } function testTransferBalanceCreditsRecipientWithNoPreviousBalance() public { assertEq(bankV2.balanceOf(stranger), 0); vm.prank(alice); bankV2.transferBalance(stranger, 125e6); assertEq(bankV2.balanceOf(alice), 875e6); assertEq(bankV2.balanceOf(stranger), 125e6); } function testFuzzTransferBalanceAcrossTrackedRecipientsAndSenderBoundedAmounts( uint256 recipientSeed, uint256 amountSeed ) public { address recipient = _trackedRecipient(bound(recipientSeed, 0, 2)); uint256 amount = bound(amountSeed, 1, ALICE_DEPOSIT); uint256 recipientBefore = bankV2.balanceOf(recipient); uint256 liabilitiesBefore = bankV2.totalLiabilities(); uint256 reservesBefore = token.balanceOf(proxy); vm.prank(alice); bankV2.transferBalance(recipient, amount); assertEq(bankV2.balanceOf(alice), ALICE_DEPOSIT - amount); assertEq(bankV2.balanceOf(recipient), recipientBefore + amount); assertEq(bankV2.totalLiabilities(), liabilitiesBefore); assertEq(token.balanceOf(proxy), reservesBefore); } function testV1DepositWithdrawalViewsPauseAndUnpauseStillWorkThroughV2Proxy() public { _mintAndApprove(stranger, 100e6); vm.prank(stranger); bankV2.deposit(100e6); vm.prank(alice); bankV2.withdraw(100e6); assertEq(address(bankV2.asset()), address(token)); assertEq(bankV2.owner(), owner); assertEq(bankV2.balanceOf(alice), 900e6); assertEq(bankV2.balanceOf(bob), BOB_DEPOSIT); assertEq(bankV2.balanceOf(stranger), 100e6); assertEq(bankV2.totalLiabilities(), 1_500e6); assertEq(token.balanceOf(proxy), 1_500e6); assertEq(bankV2.contractVersion(), 2); vm.prank(owner); bankV2.pause(); assertTrue(bankV2.paused()); vm.prank(owner); bankV2.unpause(); assertFalse(bankV2.paused()); } function _deposit(address account, uint256 amount) private { _mintAndApprove(account, amount); vm.prank(account); bank.deposit(amount); } function _mintAndApprove(address account, uint256 amount) private { vm.prank(owner); token.mint(account, amount); vm.prank(account); token.approve(proxy, amount); } function _trackedRecipient(uint256 index) private view returns (address) { if (index == 0) return bob; if (index == 1) return stranger; return owner; } }