From bc424d3c22a2e289dcfbe0b9bb19a7609179a53b Mon Sep 17 00:00:00 2001 From: aquariuslt Date: Tue, 14 May 2024 13:50:16 +0800 Subject: [PATCH 1/2] refactor(test): rename test case & layout follow practices --- test/{TestContext.t.sol => BaseTest.t.sol} | 4 +- test/TestPlus.sol | 125 ------------ test/core/YieldAggregator.t.sol | 97 ---------- test/lib/BondingCurve.t.sol | 137 ++++++++++++- test/unit/MestERC1155.t.sol | 12 +- test/unit/MestSharesFactory.t.sol | 32 +-- .../unit/MestSharesFactoryWithBlankAggr.t.sol | 182 ------------------ test/unit/YieldAggregator.t.sol | 108 +++++++---- 8 files changed, 223 insertions(+), 474 deletions(-) rename test/{TestContext.t.sol => BaseTest.t.sol} (97%) delete mode 100644 test/TestPlus.sol delete mode 100644 test/core/YieldAggregator.t.sol delete mode 100644 test/unit/MestSharesFactoryWithBlankAggr.t.sol diff --git a/test/TestContext.t.sol b/test/BaseTest.t.sol similarity index 97% rename from test/TestContext.t.sol rename to test/BaseTest.t.sol index 134275d..d90967d 100644 --- a/test/TestContext.t.sol +++ b/test/BaseTest.t.sol @@ -10,7 +10,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { MestERC1155 } from "contracts/core/MestERC1155.sol"; import { IAavePool } from "contracts/interface/IAave.sol"; -contract TestContext is Test { +contract BaseTest is Test { MestSharesFactoryV1 public sharesFactory; MestERC1155 public sharesNFT; @@ -48,7 +48,7 @@ contract TestContext is Test { sharesNFT = new MestERC1155(BASE_URI); sharesFactory = new MestSharesFactoryV1( - address(sharesNFT), BASE_PRICE, INFLECTION_POINT, INFLECTION_PRICE, LINEAR_PRICE_SLOPE, true + address(sharesNFT), BASE_PRICE, INFLECTION_POINT, INFLECTION_PRICE, LINEAR_PRICE_SLOPE ); aaveYieldAggregator = new AaveYieldAggregator(address(sharesFactory), WETH, AAVE_POOL, AAVE_WETH_GATEWAY); diff --git a/test/TestPlus.sol b/test/TestPlus.sol deleted file mode 100644 index 1857c82..0000000 --- a/test/TestPlus.sol +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.25; - -import { Test } from "forge-std/Test.sol"; - -contract TestPlus is Test { - /// @dev Returns a pseudorandom random number from [0 .. 2**256 - 1] (inclusive). - /// For usage in fuzz tests, please ensure that the function has an unnamed uint256 argument. - /// e.g. `testSomething(uint256) public`. - function _random() internal returns (uint256 r) { - /// @solidity memory-safe-assembly - assembly { - // This is the keccak256 of a very long string I randomly mashed on my keyboard. - let sSlot := 0xd715531fe383f818c5f158c342925dcf01b954d24678ada4d07c36af0f20e1ee - let sValue := sload(sSlot) - - mstore(0x20, sValue) - r := keccak256(0x20, 0x40) - - // If the storage is uninitialized, initialize it to the keccak256 of the calldata. - if iszero(sValue) { - sValue := sSlot - let m := mload(0x40) - calldatacopy(m, 0, calldatasize()) - r := keccak256(m, calldatasize()) - } - sstore(sSlot, add(r, 1)) - - // Do some biased sampling for more robust tests. - // prettier-ignore - for { } 1 { } { - let d := byte(0, r) - // With a 1/256 chance, randomly set `r` to any of 0,1,2. - if iszero(d) { - r := and(r, 3) - break - } - // With a 1/2 chance, set `r` to near a random power of 2. - if iszero(and(2, d)) { - // Set `t` either `not(0)` or `xor(sValue, r)`. - let t := xor(not(0), mul(iszero(and(4, d)), not(xor(sValue, r)))) - // Set `r` to `t` shifted left or right by a random multiple of 8. - switch and(8, d) - case 0 { - if iszero(and(16, d)) { t := 1 } - r := add(shl(shl(3, and(byte(3, r), 0x1f)), t), sub(and(r, 7), 3)) - } - default { - if iszero(and(16, d)) { t := shl(255, 1) } - r := add(shr(shl(3, and(byte(3, r), 0x1f)), t), sub(and(r, 7), 3)) - } - // With a 1/2 chance, negate `r`. - if iszero(and(0x20, d)) { r := not(r) } - break - } - // Otherwise, just set `r` to `xor(sValue, r)`. - r := xor(sValue, r) - break - } - } - } - - /// @dev Alias to `_hem`. - function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual override returns (uint256 result) { - result = _hem(x, min, max); - } - - /// @dev Adapted from `bound`: - /// https://github.com/foundry-rs/forge-std/blob/ff4bf7db008d096ea5a657f2c20516182252a3ed/src/StdUtils.sol#L10 - /// Differentially fuzzed tested against the original implementation. - function _hem(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) { - require(min <= max, "Max is less than min."); - - /// @solidity memory-safe-assembly - assembly { - // prettier-ignore - for { } 1 { } { - // If `x` is between `min` and `max`, return `x` directly. - // This is to ensure that dictionary values - // do not get shifted if the min is nonzero. - // More info: https://github.com/foundry-rs/forge-std/issues/188 - if iszero(or(lt(x, min), gt(x, max))) { - result := x - break - } - - let size := add(sub(max, min), 1) - if and(iszero(gt(x, 3)), gt(size, x)) { - result := add(min, x) - break - } - - let w := not(0) - if and(iszero(lt(x, sub(0, 4))), gt(size, sub(w, x))) { - result := sub(max, sub(w, x)) - break - } - - // Otherwise, wrap x into the range [min, max], - // i.e. the range is inclusive. - if iszero(lt(x, max)) { - let d := sub(x, max) - let r := mod(d, size) - if iszero(r) { - result := max - break - } - result := add(add(min, r), w) - break - } - let d := sub(min, x) - let r := mod(d, size) - if iszero(r) { - result := min - break - } - result := add(sub(max, r), 1) - break - } - } - } - - function testSuccess() public { } -} diff --git a/test/core/YieldAggregator.t.sol b/test/core/YieldAggregator.t.sol deleted file mode 100644 index 0bec15a..0000000 --- a/test/core/YieldAggregator.t.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.25; - -import { MestSharesFactoryV1 } from "contracts/core/MestSharesFactoryV1.sol"; -import { TestContext } from "../TestContext.t.sol"; -import { console } from "forge-std/console.sol"; - -contract YieldAggregatorTests is TestContext { - uint8 public curveType = 0; - address public addrAlice = address(1); - address public addrBob = address(2); - address public referralReceiver = address(3); - uint256 public defaultYieldBuffer = 1e12; - - function setUp() public { - createFactory(); - } - - function testMigrateNewYieldAggregator() public { - vm.prank(owner); - vm.expectRevert(bytes("Invalid yieldAggregator")); - sharesFactory.migrate(address(0)); - - vm.prank(owner); - sharesFactory.migrate(address(aaveYieldAggregator)); - } - - function testWithoutInitialYieldAggregator() public { - // test before set yieldAggregator buy fail - MestSharesFactoryV1 newSharesFactory = new MestSharesFactoryV1( - address(sharesNFT), - BASE_PRICE, // basePrice, - INFLECTION_POINT, // inflectionPoint, - INFLECTION_PRICE, // inflectionPrice - LINEAR_PRICE_SLOPE // linearPriceSlope, - ); - - vm.deal(addrAlice, 10 ether); - vm.prank(addrAlice); - vm.expectRevert(bytes("Invalid yieldAggregator")); - newSharesFactory.buyShare{ value: 5500050111111109 }(0, 1, referralReceiver); - } - - function testSetYieldBuffer() public { - vm.skip(true); - - // TODO: fix below - _testBuyShares(); - - vm.warp(YIELD_CLAIM_TIME); // need to fill a number gt current block.timestamp - uint256 depositedETHAmount = sharesFactory.depositedETHAmount(); - uint256 maxYield = aaveYieldAggregator.yieldMaxClaimable(depositedETHAmount); - assertEq(depositedETHAmount, 10000227777777775); - - uint256 withdrawableETHAmount = aWETH.balanceOf(address(sharesFactory)); - uint256 yieldBuffer = withdrawableETHAmount - depositedETHAmount - maxYield; - assertEq(yieldBuffer, defaultYieldBuffer); - - vm.prank(owner); - aaveYieldAggregator.setYieldBuffer(1e11); - uint256 maxYieldAfter = aaveYieldAggregator.yieldMaxClaimable(depositedETHAmount); - assertEq(maxYieldAfter - maxYield, 1e12 - 1e11); - } - - function _testBuyShares() public { - uint256 aliceBalBefore = addrAlice.balance; - uint256 bobBalBefore = addrBob.balance; - uint256 referrerBalBefore = referralReceiver.balance; - // uint256 factoryBalBefore = aWETH.balanceOf(address(sharesFactory)); - uint256 depositedETHAmountBefore = sharesFactory.depositedETHAmount(); - - vm.deal(addrBob, 10 ether); - _buyShare(addrBob, 0, 2, referralReceiver); - - uint256 aliceBalAfter = addrAlice.balance; - uint256 bobBalAfter = addrBob.balance; - uint256 referrerBalAfter = referralReceiver.balance; - // uint256 factoryBalAfter = aWETH.balanceOf(address(sharesFactory)); - uint256 depositedETHAmountAfter = sharesFactory.depositedETHAmount(); - - assertEq(bobBalBefore - bobBalAfter, 5500450999999993); // Bob buy 1 share - assertEq(aliceBalAfter - aliceBalBefore, 250020499999999); // Alice receive creator fee - assertEq(referrerBalAfter - referrerBalBefore, 250020499999999); // referral receive fee - // assertEq(factoryBalAfter - factoryBalBefore, 5000409999999995); // Factory aWETH balance with rounding error - assertEq(depositedETHAmountAfter - depositedETHAmountBefore, 5000409999999995); // Factory records ETH Amount - - uint256 bobShareBal = sharesNFT.balanceOf(addrBob, 0); - assertEq(bobShareBal, 2); - } - - function _buyShare(address sender, uint256 shareId, uint256 quantity, address referral) internal { - (uint256 buyPriceAfterFee,,,) = sharesFactory.getBuyPriceAfterFee(shareId, curveType, quantity, referral); - vm.prank(address(sender)); - sharesFactory.buyShare{ value: buyPriceAfterFee }(shareId, quantity, referral); - } -} diff --git a/test/lib/BondingCurve.t.sol b/test/lib/BondingCurve.t.sol index ccb0429..86feaaf 100644 --- a/test/lib/BondingCurve.t.sol +++ b/test/lib/BondingCurve.t.sol @@ -2,19 +2,19 @@ pragma solidity 0.8.25; -import { TestPlus } from "../TestPlus.sol"; +import { Test } from "forge-std/Test.sol"; import { BondingCurveLib } from "contracts/lib/BondingCurveLib.sol"; import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; -contract BondingCurveTests is TestPlus { - function testSigmoid2Sum(uint128 inflectionPrice, uint32 fromSupply, uint32 quantity) public pure { +contract BondingCurveTests is Test { + function test_sigmoid2Sum(uint128 inflectionPrice, uint32 fromSupply, uint32 quantity) public pure { uint32 inflectionPoint = uint32(type(uint32).max); quantity = uint32(_bound(quantity, 0, 256)); uint256 sum = _sigmoid2Sum(inflectionPoint, inflectionPrice, fromSupply, quantity); assertEq(sum, _mockSigmoid2Sum(inflectionPoint, inflectionPrice, fromSupply, quantity)); } - function testSigmoidMultiPurchase(uint32 g, uint96 h, uint32 s, uint8 q) public pure { + function test_sigmoidMultiPurchase(uint32 g, uint96 h, uint32 s, uint8 q) public pure { vm.assume(s <= type(uint32).max - q); uint256 sum; @@ -26,7 +26,7 @@ contract BondingCurveTests is TestPlus { assertTrue(multi == sum); } - function testSigmoidMultiSell(uint32 g, uint96 h, uint32 s, uint8 q) public pure { + function test_sigmoidMultiSell(uint32 g, uint96 h, uint32 s, uint8 q) public pure { vm.assume(s >= q); uint256 sum; @@ -39,7 +39,7 @@ contract BondingCurveTests is TestPlus { } // Check that the sum is monotonically increasing with the supply - function testSigmoidMonotony(uint32 g, uint96 h) public pure { + function test_sigmoidMonotony(uint32 g, uint96 h) public pure { unchecked { if (g < 3) g = 3; if (h == 0) h++; @@ -54,7 +54,7 @@ contract BondingCurveTests is TestPlus { } } - function testSigmoidBrutalized() public { + function test_sigmoidBrutalized() public { // Edge case tests // Test with inflection point at 0 and high inflection price _sigmoid2Brutalized(0, 5 * 1e16, 10, 1, 0); @@ -79,18 +79,18 @@ contract BondingCurveTests is TestPlus { _sigmoid2Brutalized(1500, 102500000000000000, 1501, 2, 205409999999999999); } - function testLinearSum() public pure { + function test_linearSum() public pure { uint256 sum = BondingCurveLib.linearSum(500000000000000, 0, 2); assertEq(sum, 1500000000000000); } - function testFromSupplyGreaterThanQuantity() public pure { + function test_fromSupplyGreaterThanQuantity() public pure { // fromSupply + quantity + 1 > inflectionPoint uint256 sum = BondingCurveLib.sigmoid2Sum(1500, 500000000000000, 1500, 1); assertEq(sum, 500000000000000); } - function testFromSupplyLessThanQuantity() public pure { + function test_fromSupplyLessThanQuantity() public pure { uint256 sum = BondingCurveLib.sigmoid2Sum(1495, 500000000000000, 1497, 1); assertEq(sum, 501672240802675); } @@ -153,4 +153,121 @@ contract BondingCurveTests is TestPlus { } assertEq(_sigmoid2Sum(inflectionPoint, inflectionPrice, supply, quantity), expectedResult); } + + /// @dev Returns a pseudorandom random number from [0 .. 2**256 - 1] (inclusive). + /// For usage in fuzz tests, please ensure that the function has an unnamed uint256 argument. + /// e.g. `testSomething(uint256) public`. + function _random() internal returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + // This is the keccak256 of a very long string I randomly mashed on my keyboard. + let sSlot := 0xd715531fe383f818c5f158c342925dcf01b954d24678ada4d07c36af0f20e1ee + let sValue := sload(sSlot) + + mstore(0x20, sValue) + r := keccak256(0x20, 0x40) + + // If the storage is uninitialized, initialize it to the keccak256 of the calldata. + if iszero(sValue) { + sValue := sSlot + let m := mload(0x40) + calldatacopy(m, 0, calldatasize()) + r := keccak256(m, calldatasize()) + } + sstore(sSlot, add(r, 1)) + + // Do some biased sampling for more robust tests. + // prettier-ignore + for { } 1 { } { + let d := byte(0, r) + // With a 1/256 chance, randomly set `r` to any of 0,1,2. + if iszero(d) { + r := and(r, 3) + break + } + // With a 1/2 chance, set `r` to near a random power of 2. + if iszero(and(2, d)) { + // Set `t` either `not(0)` or `xor(sValue, r)`. + let t := xor(not(0), mul(iszero(and(4, d)), not(xor(sValue, r)))) + // Set `r` to `t` shifted left or right by a random multiple of 8. + switch and(8, d) + case 0 { + if iszero(and(16, d)) { t := 1 } + r := add(shl(shl(3, and(byte(3, r), 0x1f)), t), sub(and(r, 7), 3)) + } + default { + if iszero(and(16, d)) { t := shl(255, 1) } + r := add(shr(shl(3, and(byte(3, r), 0x1f)), t), sub(and(r, 7), 3)) + } + // With a 1/2 chance, negate `r`. + if iszero(and(0x20, d)) { r := not(r) } + break + } + // Otherwise, just set `r` to `xor(sValue, r)`. + r := xor(sValue, r) + break + } + } + } + + /// @dev Alias to `_hem`. + function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual override returns (uint256 result) { + result = _hem(x, min, max); + } + + /// @dev Adapted from `bound`: + /// https://github.com/foundry-rs/forge-std/blob/ff4bf7db008d096ea5a657f2c20516182252a3ed/src/StdUtils.sol#L10 + /// Differentially fuzzed tested against the original implementation. + function _hem(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) { + require(min <= max, "Max is less than min."); + + result = 0; + /// @solidity memory-safe-assembly + assembly { + // prettier-ignore + for { } 1 { } { + // If `x` is between `min` and `max`, return `x` directly. + // This is to ensure that dictionary values + // do not get shifted if the min is nonzero. + // More info: https://github.com/foundry-rs/forge-std/issues/188 + if iszero(or(lt(x, min), gt(x, max))) { + result := x + break + } + + let size := add(sub(max, min), 1) + if and(iszero(gt(x, 3)), gt(size, x)) { + result := add(min, x) + break + } + + let w := not(0) + if and(iszero(lt(x, sub(0, 4))), gt(size, sub(w, x))) { + result := sub(max, sub(w, x)) + break + } + + // Otherwise, wrap x into the range [min, max], + // i.e. the range is inclusive. + if iszero(lt(x, max)) { + let d := sub(x, max) + let r := mod(d, size) + if iszero(r) { + result := max + break + } + result := add(add(min, r), w) + break + } + let d := sub(min, x) + let r := mod(d, size) + if iszero(r) { + result := min + break + } + result := add(sub(max, r), 1) + break + } + } + } } diff --git a/test/unit/MestERC1155.t.sol b/test/unit/MestERC1155.t.sol index 38cf5fb..7422c8a 100644 --- a/test/unit/MestERC1155.t.sol +++ b/test/unit/MestERC1155.t.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.25; -import { TestContext } from "../TestContext.t.sol"; +import {BaseTest} from "../BaseTest.t.sol"; -contract MestERC1155Tests is TestContext { +contract MestERC1155Tests is BaseTest { address private mockFactory = address(1); address private mockUser = address(2); @@ -12,7 +12,7 @@ contract MestERC1155Tests is TestContext { createFactory(); } - function testSetURI() public { + function test_setURI() public { vm.expectRevert(bytes("Ownable: caller is not the owner")); sharesNFT.setURI(BASE_URI); @@ -23,7 +23,7 @@ contract MestERC1155Tests is TestContext { assertEq(shareUri, "https://mest.io/shares/0"); } - function testSetFactory() public { + function test_setFactory() public { vm.expectRevert(bytes("Ownable: caller is not the owner")); sharesNFT.setFactory(mockFactory); @@ -37,7 +37,7 @@ contract MestERC1155Tests is TestContext { assertEq(mockUserBal, 10); } - function testShareMint() public { + function test_shareMint() public { vm.prank(address(sharesFactory)); sharesNFT.shareMint(mockUser, 0, 10); @@ -45,7 +45,7 @@ contract MestERC1155Tests is TestContext { assertEq(mockUserBal, 10); } - function testShareBurn() public { + function test_shareBurn() public { vm.startPrank(address(sharesFactory)); sharesNFT.shareMint(mockUser, 0, 10); sharesNFT.shareBurn(mockUser, 0, 10); diff --git a/test/unit/MestSharesFactory.t.sol b/test/unit/MestSharesFactory.t.sol index ccff1d2..11b59c2 100644 --- a/test/unit/MestSharesFactory.t.sol +++ b/test/unit/MestSharesFactory.t.sol @@ -2,11 +2,11 @@ pragma solidity 0.8.25; import { console } from "forge-std/console.sol"; -import { TestContext } from "../TestContext.t.sol"; +import {BaseTest} from "../BaseTest.t.sol"; import { IYieldAggregator } from "contracts/interface/IYieldAggregator.sol"; import { IMestShare } from "contracts/interface/IMestShare.sol"; -contract MestSharesFactoryTests is TestContext { +contract MestSharesFactoryTests is BaseTest { uint8 public curveType = 0; address public addrAlice = address(2); address public addrBob = address(3); @@ -38,7 +38,7 @@ contract MestSharesFactoryTests is TestContext { _buyShare(addrBob, 0, 1, referralReceiver); } - function testMintShares() public { + function test_mintShares() public { vm.skip(true); // vm.prank(addrAlice); @@ -51,7 +51,7 @@ contract MestSharesFactoryTests is TestContext { // assertEq(creator, addrAlice); } - function testBuyShares() public { + function test_buyShares() public { uint256 aliceBalBefore = addrAlice.balance; uint256 bobBalBefore = addrBob.balance; uint256 referrerBalBefore = referralReceiver.balance; @@ -76,7 +76,7 @@ contract MestSharesFactoryTests is TestContext { assertEq(bobShareBal, 2); } - function testSellShares() public { + function test_sellShares() public { uint256 aliceBalBefore = addrAlice.balance; uint256 bobBalBefore = addrBob.balance; uint256 referrerBalBefore = referralReceiver.balance; @@ -101,7 +101,7 @@ contract MestSharesFactoryTests is TestContext { assertEq(aliceShareBal, 0); } - function testClaimYield() public { + function test_claimYield() public { uint256 aliceBalBefore = addrAlice.balance; // check MaxClaimableYield < yieldBuffer @@ -135,7 +135,7 @@ contract MestSharesFactoryTests is TestContext { assertEq(aliceBalAfter - aliceBalBefore, yieldMaxClaimableAfter); } - function testClaimYieldGreaterMaxAmount() public { + function test_claimYieldGreaterMaxAmount() public { uint256 maxAmount = aaveYieldAggregator.yieldMaxClaimable(1); vm.prank(owner); @@ -143,12 +143,12 @@ contract MestSharesFactoryTests is TestContext { sharesFactory.claimYield(maxAmount + 1, receiver); } - function testInternalSafeTransferETHWithZeroAmount() public { + function test_internalSafeTransferETHWithZeroAmount() public { vm.prank(owner); sharesFactory.claimYield(0, receiver); } - function testMigrate() public { + function test_migrate() public { uint256 factoryaWETHBalBefore = aWETH.balanceOf(address(sharesFactory)); uint256 factoryETHBalBefore = address(sharesFactory).balance; @@ -166,7 +166,7 @@ contract MestSharesFactoryTests is TestContext { // switch back } - function testSetReferralFeePercent() public { + function test_setReferralFeePercent() public { vm.prank(owner); sharesFactory.setReferralFeePercent(2 * 1e16); @@ -174,7 +174,7 @@ contract MestSharesFactoryTests is TestContext { assertEq(referralFeePercent, 2 * 1e16); } - function testSetCreatorFeePercent() public { + function test_setCreatorFeePercent() public { vm.prank(owner); sharesFactory.setCreatorFeePercent(3 * 1e16); @@ -183,7 +183,7 @@ contract MestSharesFactoryTests is TestContext { } // Negative test cases - function testBuySharesRefund() public { + function test_buySharesRefund() public { uint256 aliceBalBefore = addrAlice.balance; (uint256 buyPriceAfterFee,,,) = sharesFactory.getBuyPriceAfterFee(0, 0, 1, referralReceiver); @@ -200,7 +200,7 @@ contract MestSharesFactoryTests is TestContext { assertEq(aliceBalBefore - aliceBalAfter, buyPriceAfterFee); } - function testBuySharesFailed() public { + function test_buySharesFailed() public { // invalid shareId, when id >= shareIndex uint256 shareIndex = sharesFactory.shareIndex(); @@ -223,7 +223,7 @@ contract MestSharesFactoryTests is TestContext { sharesFactory.buyShare{ value: buyPriceAfterFee / 2 }(0, 1, referralReceiver); } - function testSellSharesFailed() public { + function test_sellSharesFailed() public { (uint256 sellPriceAfterFee,,,) = sharesFactory.getSellPriceAfterFee(0, 0, 1, referralReceiver); uint256 minETHAmount = (sellPriceAfterFee * 95) / 100; uint256 overETHAmount = (sellPriceAfterFee * 120) / 100; @@ -244,12 +244,12 @@ contract MestSharesFactoryTests is TestContext { sharesFactory.sellShare(0, 1, overETHAmount, referralReceiver); } - function testSellSharesReferralToZeroAddress() public view { + function test_sellSharesReferralToZeroAddress() public view { (,, uint256 referralFee,) = sharesFactory.getSellPriceAfterFee(0, 0, 1, address(0)); assertEq(referralFee, 0); } - function testExceedsSupply() public { + function test_exceedsSupply() public { uint256 testShareId = 0; uint256 fromSupply = IMestShare(sharesNFT).shareFromSupply(testShareId); uint256 requiredQuantity = fromSupply + 1; diff --git a/test/unit/MestSharesFactoryWithBlankAggr.t.sol b/test/unit/MestSharesFactoryWithBlankAggr.t.sol deleted file mode 100644 index 32822ca..0000000 --- a/test/unit/MestSharesFactoryWithBlankAggr.t.sol +++ /dev/null @@ -1,182 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import { TestContext } from "../TestContext.t.sol"; -import { console } from "forge-std/console.sol"; - -// TODO: fix this test -contract MestSharesFactoryWithBlankAggr is TestContext { - address public user1 = address(4); - address public user2 = address(5); - - function testBalanceAfterMigrate() public { - vm.skip(true); - - vm.warp(YIELD_CLAIM_TIME); // need to fill a number gt current block.timestamp - uint256 allEthAmount = aWETH.balanceOf(address(sharesFactory)); - - // vm.prank(owner); - // sharesFactory.migrate(address(blankYieldAggregator)); - // - // uint256 factoryEthBal = address(sharesFactory).balance; - // console.log("all factory eth amount:", factoryEthBal); - // assertEq(factoryEthBal, allEthAmount); - } - - // function testBlankYieldToolBuyAndSell() public { - // { - // uint256 user1BalBefore = user1.balance; - // uint256 receiverBalBefore = receiver.balance; - // uint256 factoryBalBefore = address(sharesFactory).balance; - // uint256 user2BalBefore = user2.balance; - // vm.prank(user2); - // sharesFactory.sellShare(0, 1, 0, receiver); - // uint256 user2ShareBal = sharesNFT.balanceOf(user2, 0); - // uint256 shareSupply = sharesNFT.totalSupply(0); - // assertEq(user2ShareBal, 0); - // assertEq(shareSupply, 1); - // uint256 user1BalAfter = user1.balance; - // uint256 receiverBalAfter = receiver.balance; - // uint256 factoryBalAfter = address(sharesFactory).balance; - // uint256 user2BalAfter = user2.balance; - // - // assertEq(factoryBalBefore - factoryBalAfter, 5000182222222220); - // assertEq(user2BalAfter - user2BalBefore, 4500163999999998); - // assertEq(user1BalAfter - user1BalBefore, 250009111111111); // creatorFee - // assertEq(receiverBalAfter - receiverBalBefore, 250009111111111); // referralFee - // } - // - // { - // uint256 user1BalBefore = user1.balance; - // uint256 receiverBalBefore = receiver.balance; - // uint256 factoryBalBefore = address(sharesFactory).balance; - // uint256 user2BalBefore = user2.balance; - // vm.prank(user2); - // sharesFactory.buyShare{ value: 5501050111111109 }(0, 1, receiver); - // uint256 user2ShareBal = sharesNFT.balanceOf(user2, 0); - // uint256 shareSupply = sharesNFT.totalSupply(0); - // assertEq(user2ShareBal, 1); - // assertEq(shareSupply, 2); - // uint256 user1BalAfter = user1.balance; - // uint256 receiverBalAfter = receiver.balance; - // uint256 factoryBalAfter = address(sharesFactory).balance; - // uint256 user2BalAfter = user2.balance; - // - // //console.log(factoryBalAfter - factoryBalBefore); - // //console.log(user2BalBefore - user2BalAfter); - // //console.log(user1BalAfter - user1BalBefore); // creatorFee - // //console.log(receiverBalAfter - receiverBalBefore); // referralFee - // - // assertEq(factoryBalAfter - factoryBalBefore, 5000182222222220); - // assertEq(user2BalBefore - user2BalAfter, 5500200444444442); // totalPrice + gasFee - // assertEq(user1BalAfter - user1BalBefore, 250009111111111); // creatorFee - // assertEq(receiverBalAfter - receiverBalBefore, 250009111111111); // referralFee - // } - // - // vm.prank(owner); - // vm.expectRevert(bytes("Insufficient yield")); - // sharesFactory.claimYield(1, receiver); - // - // vm.prank(owner); - // sharesFactory.claimYield(0, receiver); - // - // uint256 amount = blankYieldAggregator.yieldBalanceOf(address(this)); - // assertEq(amount, 0); - // amount = blankYieldAggregator.yieldMaxClaimable(0); - // assertEq(amount, 0); - // } - // - // function testBlankYieldAggregator2AaveYieldAggregator() public { - // testBlankYieldToolBuyAndSell(); - // uint256 allEthAmount = address(sharesFactory).balance; - // - // vm.prank(owner); - // sharesFactory.migrate(address(aaveYieldAggregator)); - // uint256 allEthAmountAfter = aWETH.balanceOf(address(sharesFactory)); - // assertEq(allEthAmount, allEthAmountAfter); - // - // { - // uint256 user1BalBefore = user1.balance; - // uint256 receiverBalBefore = receiver.balance; - // uint256 factoryBalBefore = aWETH.balanceOf(address(sharesFactory)); - // uint256 user2BalBefore = user2.balance; - // vm.prank(user2); - // sharesFactory.sellShare(0, 1, 0, receiver); - // uint256 user2ShareBal = sharesNFT.balanceOf(user2, 0); - // uint256 shareSupply = sharesNFT.totalSupply(0); - // assertEq(user2ShareBal, 0); - // assertEq(shareSupply, 1); - // uint256 user1BalAfter = user1.balance; - // uint256 receiverBalAfter = receiver.balance; - // uint256 factoryBalAfter = aWETH.balanceOf(address(sharesFactory)); - // uint256 user2BalAfter = user2.balance; - // - // console.log("factory bal:", factoryBalBefore - factoryBalAfter); - // console.log("standardAmount:", 5000182222222220); - // assertEq(user2BalAfter - user2BalBefore, 4500163999999998); - // assertEq(user1BalAfter - user1BalBefore, 250009111111111); // creatorFee - // assertEq(receiverBalAfter - receiverBalBefore, 250009111111111); // referralFee - // } - // - // { - // uint256 user1BalBefore = user1.balance; - // uint256 receiverBalBefore = receiver.balance; - // uint256 factoryBalBefore = aWETH.balanceOf(address(sharesFactory)); - // uint256 user2BalBefore = user2.balance; - // vm.prank(user2); - // sharesFactory.buyShare{ value: 5501050111111109 }(0, 1, receiver); - // uint256 user2ShareBal = sharesNFT.balanceOf(user2, 0); - // uint256 shareSupply = sharesNFT.totalSupply(0); - // assertEq(user2ShareBal, 1); - // assertEq(shareSupply, 2); - // uint256 user1BalAfter = user1.balance; - // uint256 receiverBalAfter = receiver.balance; - // uint256 factoryBalAfter = aWETH.balanceOf(address(sharesFactory)); - // uint256 user2BalAfter = user2.balance; - // - // //console.log(factoryBalAfter - factoryBalBefore); - // //console.log(user2BalBefore - user2BalAfter); - // //console.log(user1BalAfter - user1BalBefore); // creatorFee - // //console.log(receiverBalAfter - receiverBalBefore); // referralFee - // - // console.log("factory bal:", factoryBalAfter - factoryBalBefore); - // console.log("standardAmount:", 5000182222222220); - // assertEq(user2BalBefore - user2BalAfter, 5500200444444442); // totalPrice + gasFee - // assertEq(user1BalAfter - user1BalBefore, 250009111111111); // creatorFee - // assertEq(receiverBalAfter - receiverBalBefore, 250009111111111); // referralFee - // } - // } - // - // function testBlankYieldAggregatorOnlyFactory() public { - // vm.prank(owner); - // sharesFactory.migrate(address(blankYieldAggregator)); - // - // vm.prank(user1); - // vm.expectRevert(bytes("Only factory")); - // blankYieldAggregator.yieldDeposit(); - // - // vm.prank(user1); - // vm.expectRevert(bytes("Only factory")); - // blankYieldAggregator.yieldWithdraw(1); - // } - // - // function testBlankYieldAggregatorYieldBalanceOf() public { - // uint256 amount = blankYieldAggregator.yieldBalanceOf(address(this)); - // assertEq(amount, 0); - // } - // - // function testBlankYieldAggregatorYieldMaxClaimable() public { - // uint256 amount = blankYieldAggregator.yieldMaxClaimable(0); - // assertEq(amount, 0); - // } - // - // function testBlankYieldAggregatorCommonFunctions() public { - // // user1 send eth to blankYieldAggregator and trigger `receive` - // vm.deal(user1, 10 ether); - // vm.prank(user1); - // payable(address(blankYieldAggregator)).transfer(1 ether); - // - // uint256 balanceOfBlankYieldAggregator = address(blankYieldAggregator).balance; - // assertEq(balanceOfBlankYieldAggregator, 1 ether); - // } -} diff --git a/test/unit/YieldAggregator.t.sol b/test/unit/YieldAggregator.t.sol index 2dd623b..1d8dab7 100644 --- a/test/unit/YieldAggregator.t.sol +++ b/test/unit/YieldAggregator.t.sol @@ -2,60 +2,96 @@ pragma solidity 0.8.25; -import { TestContext } from "../TestContext.t.sol"; -import { IYieldAggregator } from "contracts/interface/IYieldAggregator.sol"; +import { MestSharesFactoryV1 } from "contracts/core/MestSharesFactoryV1.sol"; +import {BaseTest} from "../BaseTest.t.sol"; +import { console } from "forge-std/console.sol"; -contract YieldAggregatorTests is TestContext { +contract YieldAggregatorTests is BaseTest { + uint8 public curveType = 0; + address public addrAlice = address(1); + address public addrBob = address(2); + address public referralReceiver = address(3); uint256 public defaultYieldBuffer = 1e12; - IYieldAggregator public yieldAggregator; function setUp() public { createFactory(); - yieldAggregator = sharesFactory.yieldAggregator(); } - // Specific for aaveYieldAggregator - function testSetYieldBuffer() public { - assertEq(aaveYieldAggregator.yieldBuffer(), defaultYieldBuffer); - - vm.expectRevert(bytes("Ownable: caller is not the owner")); - aaveYieldAggregator.setYieldBuffer(1e11); + function test_migrateNewYieldAggregator() public { + vm.prank(owner); + vm.expectRevert(bytes("Invalid yieldAggregator")); + sharesFactory.migrate(address(0)); vm.prank(owner); - aaveYieldAggregator.setYieldBuffer(1e11); - assertEq(aaveYieldAggregator.yieldBuffer(), 1e11); + sharesFactory.migrate(address(aaveYieldAggregator)); } - // Specific for aaveYieldAggregator - function testYieldMaxClaimable() public view { - uint256 depositedETHAmount = sharesFactory.depositedETHAmount(); - assertEq(aaveYieldAggregator.yieldMaxClaimable(depositedETHAmount), 0); + function test_withoutInitialYieldAggregator() public { + // test before set yieldAggregator buy fail + MestSharesFactoryV1 newSharesFactory = new MestSharesFactoryV1( + address(sharesNFT), + BASE_PRICE, // basePrice, + INFLECTION_POINT, // inflectionPoint, + INFLECTION_PRICE, // inflectionPrice + LINEAR_PRICE_SLOPE // linearPriceSlope, + ); + + vm.deal(addrAlice, 10 ether); + vm.prank(addrAlice); + vm.expectRevert(bytes("Invalid yieldAggregator")); + newSharesFactory.buyShare{ value: 5500050111111109 }(0, 1, referralReceiver); } - function testYieldDeposit() public { - vm.prank(owner); - vm.expectRevert(bytes("Only factory")); - yieldAggregator.yieldDeposit(); + function test_setYieldBuffer() public { + vm.skip(true); - vm.prank(address(sharesFactory)); - yieldAggregator.yieldDeposit(); - } + // TODO: fix below + _testBuyShares(); - function testYieldWithdraw() public { - vm.prank(owner); - vm.expectRevert(bytes("Only factory")); - yieldAggregator.yieldWithdraw(0); + vm.warp(YIELD_CLAIM_TIME); // need to fill a number gt current block.timestamp + uint256 depositedETHAmount = sharesFactory.depositedETHAmount(); + uint256 maxYield = aaveYieldAggregator.yieldMaxClaimable(depositedETHAmount); + assertEq(depositedETHAmount, 10000227777777775); - vm.prank(address(sharesFactory)); - yieldAggregator.yieldWithdraw(0); - } + uint256 withdrawableETHAmount = aWETH.balanceOf(address(sharesFactory)); + uint256 yieldBuffer = withdrawableETHAmount - depositedETHAmount - maxYield; + assertEq(yieldBuffer, defaultYieldBuffer); - function testMigrateNewYieldAggregator() public { vm.prank(owner); - vm.expectRevert(bytes("Invalid yieldAggregator")); - sharesFactory.migrate(address(0)); + aaveYieldAggregator.setYieldBuffer(1e11); + uint256 maxYieldAfter = aaveYieldAggregator.yieldMaxClaimable(depositedETHAmount); + assertEq(maxYieldAfter - maxYield, 1e12 - 1e11); + } - vm.prank(owner); - sharesFactory.migrate(address(blankYieldAggregator)); + function _testBuyShares() public { + uint256 aliceBalBefore = addrAlice.balance; + uint256 bobBalBefore = addrBob.balance; + uint256 referrerBalBefore = referralReceiver.balance; + // uint256 factoryBalBefore = aWETH.balanceOf(address(sharesFactory)); + uint256 depositedETHAmountBefore = sharesFactory.depositedETHAmount(); + + vm.deal(addrBob, 10 ether); + _buyShare(addrBob, 0, 2, referralReceiver); + + uint256 aliceBalAfter = addrAlice.balance; + uint256 bobBalAfter = addrBob.balance; + uint256 referrerBalAfter = referralReceiver.balance; + // uint256 factoryBalAfter = aWETH.balanceOf(address(sharesFactory)); + uint256 depositedETHAmountAfter = sharesFactory.depositedETHAmount(); + + assertEq(bobBalBefore - bobBalAfter, 5500450999999993); // Bob buy 1 share + assertEq(aliceBalAfter - aliceBalBefore, 250020499999999); // Alice receive creator fee + assertEq(referrerBalAfter - referrerBalBefore, 250020499999999); // referral receive fee + // assertEq(factoryBalAfter - factoryBalBefore, 5000409999999995); // Factory aWETH balance with rounding error + assertEq(depositedETHAmountAfter - depositedETHAmountBefore, 5000409999999995); // Factory records ETH Amount + + uint256 bobShareBal = sharesNFT.balanceOf(addrBob, 0); + assertEq(bobShareBal, 2); + } + + function _buyShare(address sender, uint256 shareId, uint256 quantity, address referral) internal { + (uint256 buyPriceAfterFee,,,) = sharesFactory.getBuyPriceAfterFee(shareId, curveType, quantity, referral); + vm.prank(address(sender)); + sharesFactory.buyShare{ value: buyPriceAfterFee }(shareId, quantity, referral); } } From fc0d1485dde76fb1d98254b12a9237c1205f5d7a Mon Sep 17 00:00:00 2001 From: aquariuslt Date: Tue, 14 May 2024 13:51:10 +0800 Subject: [PATCH 2/2] chore: fix solhint issue --- contracts/core/aggregator/AaveYieldAggregator.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/core/aggregator/AaveYieldAggregator.sol b/contracts/core/aggregator/AaveYieldAggregator.sol index 7c2f6bd..ac996b9 100644 --- a/contracts/core/aggregator/AaveYieldAggregator.sol +++ b/contracts/core/aggregator/AaveYieldAggregator.sol @@ -100,7 +100,8 @@ contract AaveYieldAggregator is Ownable, IYieldAggregator { /** * @notice Check Aave pool state * @return bool true if Aave pool is active, false otherwise - * @dev For more information, see: https://github.com/aave/aave-v3-core/blob/master/contracts/protocol/libraries/configuration/ReserveConfiguration.sol + * @dev For more information, see: + * https://github.com/aave/aave-v3-core/blob/master/contracts/protocol/libraries/configuration/ReserveConfiguration.sol */ function _checkAavePoolState() internal view returns (bool) { uint256 configData = AAVE_POOL.getReserveData(WETH).configuration.data;