From cb57f84b29a62d2a95fdfa10e1c521eb32e9e851 Mon Sep 17 00:00:00 2001 From: xhad Date: Sat, 14 Sep 2024 01:30:34 +0800 Subject: [PATCH] forge fmt --- .solhint.json | 15 ++++++ src/VaultFactory.sol | 7 +-- test/mocks/MockSingleVault.sol | 12 +++++ test/single/unit/invariants.sol | 86 +++++++++++++++++++++++++++++++++ test/single/unit/upgrade.t.sol | 76 +++++++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 .solhint.json create mode 100644 test/mocks/MockSingleVault.sol create mode 100644 test/single/unit/invariants.sol create mode 100644 test/single/unit/upgrade.t.sol diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..deaed7c --- /dev/null +++ b/.solhint.json @@ -0,0 +1,15 @@ +{ + "extends": "solhint:recommended", + "plugins": [], + "rules": { + "avoid-suicide": "error", + "avoid-sha3": "warn", + "compiler-version": ["error", "^0.8.0"], + "func-visibility": ["warn", { "ignoreConstructors": true }], + "reason-string": ["warn", { "maxLength": 64 }], + "not-rely-on-time": "warn", + "state-visibility": "error", + "max-line-length": ["warn", 120], + "no-console": "off" + } +} \ No newline at end of file diff --git a/src/VaultFactory.sol b/src/VaultFactory.sol index 71d9cfb..d3fbebc 100644 --- a/src/VaultFactory.sol +++ b/src/VaultFactory.sol @@ -1,12 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity ^0.8.24; -import { - AccessControlUpgradeable, - TransparentUpgradeableProxy, - IERC20, - IERC4626 -} from "src/Common.sol"; +import {AccessControlUpgradeable, TransparentUpgradeableProxy, IERC20, IERC4626} from "src/Common.sol"; import {IVaultFactory} from "src/IVaultFactory.sol"; diff --git a/test/mocks/MockSingleVault.sol b/test/mocks/MockSingleVault.sol new file mode 100644 index 0000000..4186af1 --- /dev/null +++ b/test/mocks/MockSingleVault.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.24; + +import {SingleVault} from "src/SingleVault.sol"; + +contract MockSingleVault is SingleVault { + constructor() { + _disableInitializers(); + } + + uint256 public constant R_TWO_D = 2; +} diff --git a/test/single/unit/invariants.sol b/test/single/unit/invariants.sol new file mode 100644 index 0000000..030b989 --- /dev/null +++ b/test/single/unit/invariants.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.24; + +import {SingleVault} from "src/SingleVault.sol"; +import {MockERC20} from "test/mocks/MockERC20.sol"; +import {Math} from "src/Common.sol"; +import {SetupHelper} from "test/helpers/Setup.sol"; +import {Etches} from "test/helpers/Etches.sol"; +import {TestConstants} from "test/helpers/Constants.sol"; +import {LocalActors} from "script/Actors.sol"; + +import "forge-std/Test.sol"; + +contract SingleInvariantTests is Test, LocalActors, TestConstants { + using Math for uint256; + + SingleVault public vault; + MockERC20 public asset; + address USER = address(33); + + function setUp() public { + vm.startPrank(ADMIN); + asset = MockERC20(address(new MockERC20(ASSET_NAME, ASSET_SYMBOL))); + + Etches etches = new Etches(); + etches.mockListaStakeManager(); + + SetupHelper setup = new SetupHelper(); + vault = setup.createVault(asset); + } + + event Log(uint256 amount, string name); + + function test_totalAssetsAlwaysCorrect(uint256 depositAmount) public { + if (depositAmount < 1) return; + if (depositAmount > 1e50) return; + + uint256 initialTotalAssets = vault.totalAssets(); + depositHelper(USER, depositAmount); + uint256 currentTotalAssets = vault.totalAssets(); + uint256 expectedDepositAssets = vault.previewDeposit(depositAmount); + uint256 expectedAssets = initialTotalAssets + expectedDepositAssets; + assertClose(currentTotalAssets, expectedAssets, 10, "Total assets mismatch after deposit"); + } + + function test_totalSupplyAlwaysCorrect(uint256 depositAmount) public { + if (depositAmount < 1) return; + if (depositAmount > 1e50) return; + + uint256 initialTotalSupply = vault.totalSupply(); + depositHelper(USER, depositAmount); + uint256 currentTotalSupply = vault.totalSupply(); + uint256 expectedDepositAssets = vault.previewDeposit(depositAmount); + uint256 expectedSupply = initialTotalSupply + expectedDepositAssets; + assertClose(currentTotalSupply, expectedSupply, 1, "Total supply mismatch after deposit"); + } + + function test_totalSupplyMatchesBalances(uint256 depositAmount) public { + if (depositAmount < 2) return; + if (depositAmount > 1e50) return; + + depositHelper(USER, depositAmount); + uint256 total = vault.totalSupply(); + assertEq(vault.convertToShares(depositAmount + 1 ether), total, "Total supply does not balances"); + } + + function test_conversionConsistency(uint256 depositAmount) public view { + if (depositAmount < 2) return; + if (depositAmount > 1e50) return; + uint256 shares = vault.convertToShares(depositAmount); + uint256 convertedAssets = vault.convertToAssets(shares); + assertClose(depositAmount, convertedAssets, 1, "Conversion inconsistency"); + } + + function depositHelper(address user, uint256 depositAmount) public { + vm.startPrank(user); + asset.mint(depositAmount); + asset.approve(address(vault), depositAmount); + vault.deposit(depositAmount, address(this)); + vm.stopPrank(); + } + + function assertClose(uint256 actual, uint256 expected, uint256 delta, string memory message) internal pure { + require(actual >= expected - delta && actual <= expected + delta, message); + } +} diff --git a/test/single/unit/upgrade.t.sol b/test/single/unit/upgrade.t.sol new file mode 100644 index 0000000..f123f8d --- /dev/null +++ b/test/single/unit/upgrade.t.sol @@ -0,0 +1,76 @@ +// BSD 3-Clause License +pragma solidity ^0.8.24; + +import {SingleVault} from "src/SingleVault.sol"; +import {MockERC20} from "test/mocks/MockERC20.sol"; +import {SetupHelper} from "test/helpers/Setup.sol"; +import {Etches} from "test/helpers/Etches.sol"; +import {LocalActors} from "script/Actors.sol"; +import {TestConstants} from "test/helpers/Constants.sol"; +import {IERC20} from "src/Common.sol"; +import {IVaultFactory} from "src/IVaultFactory.sol"; +import {MockSingleVault} from "test/mocks/MockSingleVault.sol"; +import {TimelockController} from "src/Common.sol"; + +import "forge-std/Test.sol"; + +contract SingleVaultUpgradeTests is Test, LocalActors, TestConstants { + SingleVault public vault; + MockERC20 public asset; + IVaultFactory public factory; + + function setUp() public { + vm.startPrank(ADMIN); + asset = MockERC20(address(new MockERC20(ASSET_NAME, ASSET_SYMBOL))); + + Etches etches = new Etches(); + etches.mockListaStakeManager(); + + SetupHelper setup = new SetupHelper(); + vault = setup.createVault(asset); + + factory = IVaultFactory(setup.factory()); + } + + function testUpgrade() public { + vm.startPrank(ADMIN); + SingleVault newVault = new MockSingleVault(); + + // the timelock on the factory is the admin for proxy upgrades + TimelockController timelock = TimelockController(payable(factory.timelock())); + + // schedule a proxy upgrade transaction on the timelock + // the traget is the proxy admin, created by foundry test + address target = 0xF094c1B2ec3E52f6D02603C4dB28dd4Ba0067048; + uint256 value = 0; + + bytes4 selector = bytes4(keccak256("upgradeAndCall(address,address,bytes)")); + + bytes memory data = abi.encodeWithSelector(selector, address(vault), address(newVault), ""); + + bytes32 predecessor = bytes32(0); + bytes32 salt = keccak256("chad"); + + uint256 delay = 1; + vm.startPrank(PROPOSER_1); + timelock.schedule(target, value, data, predecessor, salt, delay); + vm.stopPrank(); + + bytes32 id = keccak256(abi.encode(target, value, data, predecessor, salt)); + assert(timelock.getOperationState(id) == TimelockController.OperationState.Waiting); + + assertEq(timelock.isOperationReady(id), false); + assertEq(timelock.isOperationDone(id), false); + assertEq(timelock.isOperation(id), true); + + //execute the transaction + vm.warp(500); + vm.startPrank(EXECUTOR_1); + timelock.execute(target, value, data, predecessor, salt); + + // Verify the transaction was executed successfully + assertEq(timelock.isOperationReady(id), false); + assertEq(timelock.isOperationDone(id), true); + assert(timelock.getOperationState(id) == TimelockController.OperationState.Done); + } +}