diff --git a/README.md b/README.md index 234532d..a7564b0 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,25 @@ -
-  
-  
-  
-  A NEW WAY TO PAY.
-  Payment via the bonding curve and yield farming.
-  
-
- -Mest, **a payment protocol designed for creators**. And it's a bridge between creators and fans, where fans can **donate, sponsor, subscribe**, etc. The contract provides a new way to pay where you can stake ETH by a **sigmoid bonding curve** and **yield farming**, buy what you need, and withdraw whenever you want. - -- 🐦 For fans, pay early and save more; -- πŸ’΅ For creators, long-term income from fees and yield; -- ⚑ Lightweight with flexible curve and yield strategies; -- 🌟 Made for creators like startups, indie hackers and KOLs. + + Value4Value -
- -| Features | Mest | FriendtechV1 | Patreon | Coinbase Commerce | -| ------------------ | ----- | ------------ | ------- | ----------------- | -| User Capacity | > 10K | < 100 | N/A | N/A | -| Capital efficiency | βœ… | ❌ | ❌ | ❌ | -| Permissionless | βœ… | βœ… | ❌ | ❌ | -| Tokenization | βœ… | ❌ | ❌ | ❌ | +
+
+ A NEW WAY TO PAY.
+ Payment via the bonding curve and yield farming. +
+
+ + +## + +**A payment protocol designed for creators**, it bridges the gap between creators and fans, where fans can **donate, sponsor, subscribe**, and more, while creators receive **fees and yield**. + +- For fans, pay early and earn more; +- For creators, long-term income from fees and yield; +- Lightweight with flexible curve and yield strategies; +- No protocol fee, any client can be charged via referral fee; +- Stake what you need, and withdraw when you want; +- Made for creators like startups, indie hackers and KOLs. -
- ## How it works? The contract uses a sigmoid bonding curve for dynamic pricing. When you buy, it mints tokens and drives prices up, then when you sell, it burns tokens and drives prices down. The staked ETH is allocated in an interest-rate market to generate sustainable rewards, which are then redistributed to the creators. @@ -33,19 +28,47 @@ The contract uses a sigmoid bonding curve for dynamic pricing. When you buy, it -## Contracts +## Why it is better? +
+ +| Features | V4V | Friendtech | Patreon | Coinbase Commerce | +|--------------------|-------|------------|---------|-------------------| +| Flexible strategy | β˜…β˜…β˜…β˜…β˜… | β˜…β˜…β˜…β˜†β˜† | β˜…β˜…β˜…β˜†β˜† | β˜…β˜†β˜†β˜†β˜† | +| Capital efficiency | β˜…β˜…β˜…β˜†β˜† | β˜…β˜†β˜†β˜†β˜† | β˜…β˜†β˜†β˜†β˜† | β˜…β˜†β˜†β˜†β˜† | +| Permissionless | β˜…β˜…β˜…β˜…β˜… | β˜…β˜…β˜…β˜…β˜… | β˜†β˜†β˜†β˜†β˜† | β˜…β˜…β˜…β˜†β˜† | +| Tokenization | β˜…β˜…β˜…β˜…β˜… | β˜…β˜…β˜…β˜†β˜† | β˜†β˜†β˜†β˜†β˜† | β˜†β˜†β˜†β˜†β˜† | +| Protocol Fee | β˜…β˜…β˜…β˜…β˜† | β˜…β˜…β˜†β˜†β˜† | β˜…β˜†β˜†β˜†β˜† | β˜…β˜…β˜…β˜†β˜† | + +
+ +## Smart Contracts ### NFT -The token is a standard ERC1155 contract, with NFTs acting as shares in the bonding curve. When you trade shares, NFTs are minted or burned. +> The token is a standard ERC1155 contract, with NFTs acting as shares in the bonding curve. When you trade shares, NFTs are minted or burned. + +| Network | Address | +|------------------|--------------------------------------------| +| Optimism Mainnet | N/A | +| Optimism Sepolia | 0x07cB2490DfBFd63613318F87156D935ddAcb62F4 | ### Shares -SharesFactory is the core contract that contains the bonding curve and yield aggregator logic where you can mint, buy, and sell shares, as well as change yield strategies and claim yields. +> SharesFactory is the core contract that contains the bonding curve and yield aggregator logic where you can mint, buy, and sell shares, as well as change yield strategies and claim yields. + +| Network | Address | +|------------------|--------------------------------------------| +| Optimism Mainnet | N/A | +| Optimism Sepolia | 0x5F31921A68eA5b350baF141536933Cc7d70EBAEa | ### Yield -YieldAggregator is a yield strategy contract that provides a common interface for SharesFactory to use, such as deposit, withdraw, and claimable. However, the underlying logic can be any yield strategy, such as Aave, Pendle and LRT, or nothing at all. +> YieldAggregator is a yield strategy contract that provides a common interface for SharesFactory to use, such as deposit, withdraw, and claimable. However, the underlying logic can be any yield strategy, such as Aave, Pendle and LRT, or nothing at all. + +| Network | Address | +|------------------|--------------------------------------------| +| Optimism Mainnet | N/A | +| Optimism Sepolia | 0x2c1414c3F442AA7a4E531E2891009Dd1a8744b59 | ## Test and Deploy @@ -65,4 +88,4 @@ deploy ## Acknowledgement -Thanks to [Simon de la Rouviere](https://docs.google.com/document/d/1VNkBjjGhcZUV9CyC0ccWYbqeOoVKT2maqX0rK3yXB20), whose ideas inspired Mest to combine curated market with bonding curves, and to the ideal S-curve model from [sound protocol](https://github.com/soundxyz/sound-protocol), we’ve also learned the principle of minimalism from [friend tech](https://www.friend.tech) and [bodhi](https://bodhi.wtf). +Thanks to [Simon de la Rouviere](https://docs.google.com/document/d/1VNkBjjGhcZUV9CyC0ccWYbqeOoVKT2maqX0rK3yXB20), whose ideas inspired *V4V* to combine curated market with bonding curves, and to the ideal S-curve model from [sound protocol](https://github.com/soundxyz/sound-protocol), we’ve also learned the principle of minimalism from [friend tech](https://www.friend.tech) and [bodhi](https://bodhi.wtf). diff --git a/contracts/core/SharesFactoryV1.sol b/contracts/core/SharesFactoryV1.sol index db3c954..1d1a697 100644 --- a/contracts/core/SharesFactoryV1.sol +++ b/contracts/core/SharesFactoryV1.sol @@ -40,8 +40,8 @@ contract SharesFactoryV1 is Ownable { event SetCurve(uint8 indexed curveType); event SetFee(uint256 indexed feePercent, string feeType); event Mint(uint256 indexed id, address indexed creator, uint8 indexed curveType); - event Buy(uint256 indexed id, address indexed buyer, uint256 quantity, uint256 totalPrice); - event Sell(uint256 indexed id, address indexed seller, uint256 quantity, uint256 totalPrice); + event Buy(uint256 indexed id, address indexed buyer, uint32 quantity, uint256 totalPrice); + event Sell(uint256 indexed id, address indexed seller, uint32 quantity, uint256 totalPrice); constructor( address _ERC1155, @@ -73,26 +73,20 @@ contract SharesFactoryV1 is Ownable { return (share.creator, share.curveType); } - function getCurve(uint8 curveType) - public - view - returns ( - uint96 basePrice, - uint32 inflectionPoint, - uint128 inflectionPrice, - uint128 linearPriceSlope, - bool exists - ) - { + function getCurve(uint8 curveType) public view returns (uint96, uint32, uint128, uint128, bool) { require(curvesMap[curveType].exists, "Invalid curveType"); Curve memory curve = curvesMap[curveType]; - return ( - curve.basePrice, - curve.inflectionPoint, - curve.inflectionPrice, - curve.linearPriceSlope, - curve.exists - ); + uint96 basePrice = curve.basePrice; + uint32 g = curve.inflectionPoint; + uint128 h = curve.inflectionPrice; + uint128 m = curve.linearPriceSlope; + bool exists = curve.exists; + return (basePrice, g, h, m, exists); + } + + function getSubTotal(uint32 fromSupply, uint32 quantity, uint8 curveType) public view returns (uint256) { + (uint96 basePrice, uint32 g, uint128 h, uint128 m,) = getCurve(curveType); + return _subTotal(fromSupply, quantity, basePrice, g, h, m); } function setReferralFeePercent(uint256 _feePercent) external onlyOwner { @@ -305,7 +299,7 @@ contract SharesFactoryV1 is Ownable { uint256 fromSupply = IShare(ERC1155).shareFromSupply(shareId); uint256 actualReferralFeePercent = referral != address(0) ? referralFeePercent : 0; - buyPrice = _subTotal(uint32(fromSupply), quantity, curveType); + buyPrice = getSubTotal(uint32(fromSupply), quantity, curveType); referralFee = (buyPrice * actualReferralFeePercent) / 1 ether; creatorFee = (buyPrice * creatorFeePercent) / 1 ether; buyPriceAfterFee = buyPrice + referralFee + creatorFee; @@ -337,7 +331,7 @@ contract SharesFactoryV1 is Ownable { uint256 actualReferralFeePercent = referral != address(0) ? referralFeePercent : 0; require(fromSupply >= quantity, "Exceeds supply"); - sellPrice = _subTotal(uint32(fromSupply) - quantity, quantity, curveType); + sellPrice = getSubTotal(uint32(fromSupply) - quantity, quantity, curveType); referralFee = (sellPrice * actualReferralFeePercent) / 1 ether; creatorFee = (sellPrice * creatorFeePercent) / 1 ether; sellPriceAfterFee = sellPrice - referralFee - creatorFee; @@ -380,15 +374,11 @@ contract SharesFactoryV1 is Ownable { function _subTotal( uint32 fromSupply, uint32 quantity, - uint8 curveType - ) public view returns (uint256 subTotal) { - ( - uint96 basePrice, - uint32 inflectionPoint, - uint128 inflectionPrice, - uint128 linearPriceSlope, - ) = getCurve(curveType); - + uint96 basePrice, + uint32 inflectionPoint, + uint128 inflectionPrice, + uint128 linearPriceSlope + ) public pure returns (uint256 subTotal) { unchecked { subTotal = basePrice * quantity; subTotal += BondingCurveLib.linearSum(linearPriceSlope, fromSupply, quantity); diff --git a/images/banner.png b/images/banner.png deleted file mode 100644 index e773b64..0000000 Binary files a/images/banner.png and /dev/null differ diff --git a/images/curve.gif b/images/curve.gif index 3a75c09..0daa939 100644 Binary files a/images/curve.gif and b/images/curve.gif differ diff --git a/test/BaseTest.t.sol b/test/BaseTest.t.sol index 2b3838d..45e5dcc 100644 --- a/test/BaseTest.t.sol +++ b/test/BaseTest.t.sol @@ -17,8 +17,12 @@ contract BaseTest is Test { AaveYieldAggregator public aaveYieldAggregator; BlankYieldAggregator public blankYieldAggregator; - address public receiver = address(999); - address public owner = address(1); + uint8 public defaultCurveType = 0; + address public owner = makeAddr("owner"); + address public addrAlice = makeAddr("addrAlice"); + address public addrBob = makeAddr("addrBob"); + address public referralReceiver = makeAddr("referralReceiver"); + address public yieldReceiver = makeAddr("yieldReceiver"); /** * @dev Below are the related token/contract addresses on the Optimism mainnet. @@ -64,9 +68,6 @@ contract BaseTest is Test { sharesNFT.transferOwnership(owner); sharesFactory.transferOwnership(owner); aaveYieldAggregator.transferOwnership(owner); - - // vm.prank(owner); - // sharesFactory.migrate(address(aaveYieldAggregator)); } function testSuccess() public { } diff --git a/test/unit/SharesFactory.t.sol b/test/unit/SharesFactory.t.sol index 1c02baf..f5f1d32 100644 --- a/test/unit/SharesFactory.t.sol +++ b/test/unit/SharesFactory.t.sol @@ -2,15 +2,11 @@ pragma solidity 0.8.25; import { console } from "forge-std/console.sol"; -import { BaseTest } from "../BaseTest.t.sol"; +import { SharesFactoryV1 } from "contracts/core/SharesFactoryV1.sol"; import { IYieldAggregator } from "contracts/interface/IYieldAggregator.sol"; +import { BaseTest } from "../BaseTest.t.sol"; contract SharesFactoryTests is BaseTest { - uint8 public defaultCurveType = 0; - address public addrAlice = address(2); - address public addrBob = address(3); - address public referralReceiver = address(4); - function setUp() public { createFactory(); _setUpShare(); @@ -34,6 +30,10 @@ contract SharesFactoryTests is BaseTest { // Bob buy 1 share with 0 id _buyShare(addrBob, 0, 1, referralReceiver); + + // Mock accumulated yield + uint256 timestamp = block.timestamp; + vm.warp(timestamp + 1 minutes); } function test_mintShare() public { @@ -45,6 +45,9 @@ contract SharesFactoryTests is BaseTest { assertEq(creator, addrAlice); assertEq(curveType, defaultCurveType); + + vm.expectRevert(bytes("Invalid curveType")); + sharesFactory.mintShare(99); } function test_minAndBuyShare() public { @@ -121,11 +124,12 @@ contract SharesFactoryTests is BaseTest { uint256 yieldMaxClaimableBefore, uint256 yieldBufferBefore ) = _getYield(); - assertTrue((yieldBalanceBefore - depositedETHAmountBefore) < yieldBufferBefore); + assertTrue(yieldBalanceBefore < (yieldBufferBefore + depositedETHAmountBefore)); assertEq(yieldMaxClaimableBefore, 0); // Speed up time to claim yield vm.warp(YIELD_CLAIM_TIME); + ( uint256 depositedETHAmountAfter, uint256 yieldBalanceAfter, @@ -176,8 +180,8 @@ contract SharesFactoryTests is BaseTest { // default curveType ( uint256 basePrice, - uint256 inflectionPoint, - uint256 inflectionPrice, + uint256 inflectionPoint, + uint256 inflectionPrice, uint256 linearPriceSlope, bool exists ) = sharesFactory.getCurve(0); @@ -215,9 +219,9 @@ contract SharesFactoryTests is BaseTest { ( uint256 basePrice, - uint256 inflectionPoint, - uint256 inflectionPrice, - uint256 linearPriceSlope, + uint256 inflectionPoint, + uint256 inflectionPrice, + uint256 linearPriceSlope, bool exists ) = sharesFactory.getCurve(1); @@ -252,26 +256,32 @@ contract SharesFactoryTests is BaseTest { */ function test_buySharesFailed() public { + // invalid yieldAggregator, when sharesFactory not set yieldAggregator + SharesFactoryV1 newSharesFactory = new SharesFactoryV1( + address(sharesNFT), + BASE_PRICE, + INFLECTION_POINT, + INFLECTION_PRICE, + LINEAR_PRICE_SLOPE + ); + vm.prank(addrAlice); + vm.expectRevert(bytes("Invalid yieldAggregator")); + newSharesFactory.buyShare{ value: 1 ether }(0, 1, referralReceiver); + // invalid shareId, when id >= shareIndex uint256 shareIndex = sharesFactory.shareIndex(); - - vm.prank(addrAlice); + vm.startPrank(addrAlice); vm.expectRevert(bytes("Invalid shareId")); sharesFactory.buyShare{ value: 1 ether }(shareIndex, 1, referralReceiver); - - vm.prank(addrAlice); vm.expectRevert(bytes("Invalid shareId")); sharesFactory.buyShare{ value: 1 ether }(shareIndex * 999, 1, referralReceiver); - - // invalid buyer, when alice create shares, only alice can buy it first - vm.prank(addrAlice); - sharesFactory.mintShare(defaultCurveType); + vm.stopPrank(); // invalid value, when value < buyPriceAfterFee (uint256 buyPriceAfterFee,,,) = sharesFactory.getBuyPriceAfterFee(0, 1, referralReceiver); vm.prank(addrAlice); vm.expectRevert(bytes("Insufficient payment")); - sharesFactory.buyShare{ value: buyPriceAfterFee / 2 }(0, 1, referralReceiver); + sharesFactory.buyShare{ value: buyPriceAfterFee }(0, 2, referralReceiver); } function test_sellSharesFailed() public { @@ -319,11 +329,11 @@ contract SharesFactoryTests is BaseTest { vm.prank(addrAlice); vm.expectRevert(bytes("Ownable: caller is not the owner")); - sharesFactory.claimYield(maxAmount, receiver); + sharesFactory.claimYield(maxAmount, yieldReceiver); vm.prank(owner); vm.expectRevert(bytes("Insufficient yield")); - sharesFactory.claimYield(maxAmount + 1, receiver); + sharesFactory.claimYield(maxAmount + 1, yieldReceiver); } function test_getBuyPriceAfterFeeFailed() public { @@ -365,7 +375,7 @@ contract SharesFactoryTests is BaseTest { function test_safeTransferETHWithZero() public { vm.prank(owner); - sharesFactory.claimYield(0, receiver); + sharesFactory.claimYield(0, yieldReceiver); } /* @@ -375,30 +385,32 @@ contract SharesFactoryTests is BaseTest { */ function testFuzz_setCurveTypeAndSubTotal( - uint8 curveType, - uint16 basePrice, - uint16 inflectionPoint, - uint32 inflectionPrice, + uint8 curveType, + uint16 basePrice, + uint16 inflectionPoint, + uint32 inflectionPrice, uint32 linearPriceSlope, - uint16 fromSupply, + uint16 fromSupply, uint16 quantity ) public { vm.prank(owner); - try sharesFactory.setCurveType(curveType, basePrice, inflectionPoint, inflectionPrice, linearPriceSlope) { + try sharesFactory.setCurveType(curveType, basePrice, inflectionPoint, inflectionPrice, linearPriceSlope) + { (, , , , bool exists) = sharesFactory.getCurve(curveType); assertEq(exists, true); } catch Error(string memory reason) { assertEq(reason, "Curve already initialized"); } - try sharesFactory._subTotal(fromSupply, quantity, curveType) returns (uint256 total) { + try sharesFactory.getSubTotal(fromSupply, quantity, curveType) returns (uint256 total) + { if (quantity == 0) { assertEq(total, 0); } else { assertGe(total, 0); } } catch Error(string memory reason) { - console.log("testFuzz_setCurveTypeAndSubTotal:_subTotal", reason); + console.log("testFuzz_setCurveTypeAndSubTotal:getSubTotal", reason); } } @@ -424,7 +436,7 @@ contract SharesFactoryTests is BaseTest { */ function _mintAndBuyShare(address sender, uint8 curveType, uint32 quantity, address referral) internal { - uint256 buyPrice = sharesFactory._subTotal(0, quantity, curveType); + uint256 buyPrice = sharesFactory.getSubTotal(0, quantity, curveType); vm.prank(address(sender)); sharesFactory.mintAndBuyShare{ value: buyPrice * 110 / 100 }(curveType, quantity, referral); @@ -450,10 +462,6 @@ contract SharesFactoryTests is BaseTest { uint256 yieldBalance = yieldAggregator.yieldBalanceOf(address(sharesFactory)); uint256 yieldMaxClaimable = yieldAggregator.yieldMaxClaimable(depositedETHAmount); uint256 yieldBuffer = 1e12; - // uint256 yieldBuffer = yieldAggregator.yieldBuffer(); - console.log("depositedETHAmount: ", depositedETHAmount); - console.log("yieldBalance: ", yieldBalance); - console.log("yieldMaxClaimable: ", yieldMaxClaimable); return (depositedETHAmount, yieldBalance, yieldMaxClaimable, yieldBuffer); } } diff --git a/test/unit/YieldAggregator.t.sol b/test/unit/YieldAggregator.t.sol index 3bd9489..079a2b7 100644 --- a/test/unit/YieldAggregator.t.sol +++ b/test/unit/YieldAggregator.t.sol @@ -2,95 +2,127 @@ pragma solidity 0.8.25; -import { SharesFactoryV1 } from "contracts/core/SharesFactoryV1.sol"; +import { AaveYieldAggregator } from "contracts/core/aggregator/AaveYieldAggregator.sol"; +import { BlankYieldAggregator } from "contracts/core/aggregator/BlankYieldAggregator.sol"; import { BaseTest } from "../BaseTest.t.sol"; 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; function setUp() public { createFactory(); } - function test_migrateNewYieldAggregator() public { - vm.prank(owner); - vm.expectRevert(bytes("Invalid yieldAggregator")); - sharesFactory.migrate(address(0)); + function test_newAggregator() public { + AaveYieldAggregator aave = new AaveYieldAggregator(address(sharesFactory), WETH, AAVE_POOL, AAVE_WETH_GATEWAY); + assertEq(aave.yieldBuffer(), defaultYieldBuffer); + assertEq(aave.FACTORY(), address(sharesFactory)); + assertEq(aave.WETH(), WETH); + assertEq(address(aave.AAVE_POOL()), AAVE_POOL); + assertEq(address(aave.AAVE_WETH_GATEWAY()), AAVE_WETH_GATEWAY); + assertEq(address(aave.aWETH()), address(aWETH)); + + BlankYieldAggregator blank = new BlankYieldAggregator(address(sharesFactory), WETH); + assertEq(blank.FACTORY(), address(sharesFactory)); + assertEq(blank.WETH(), WETH); + } + function test_setYieldBuffer() public { vm.prank(owner); - sharesFactory.migrate(address(aaveYieldAggregator)); - } + aaveYieldAggregator.setYieldBuffer(1e11); + assertEq(aaveYieldAggregator.yieldBuffer(), 1e11); - function test_withoutInitialYieldAggregator() public { - // test before set yieldAggregator buy fail - SharesFactoryV1 newSharesFactory = new SharesFactoryV1( - 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); + vm.expectRevert(bytes("Ownable: caller is not the owner")); + aaveYieldAggregator.setYieldBuffer(1e11); } - function test_setYieldBuffer() public { - vm.skip(true); + function test_yieldDepost() public { + _depositETH2AaveYieldAggregator(10 ether); + assertTrue(aWETH.balanceOf(address(sharesFactory)) == 10 ether); + assertTrue(address(aaveYieldAggregator).balance == 0); - // TODO: fix below - _testBuyShares(); + _migrate2BlankYieldAggregator(); + vm.deal(address(blankYieldAggregator), 10 ether); + vm.prank(address(sharesFactory)); + blankYieldAggregator.yieldDeposit(); + assertTrue(address(sharesFactory).balance == 20 ether); + assertTrue(address(blankYieldAggregator).balance == 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.expectRevert(bytes("Only factory")); + aaveYieldAggregator.yieldDeposit(); + vm.expectRevert(bytes("Only factory")); + blankYieldAggregator.yieldDeposit(); + } + + function test_yieldWithdraw() public { + _depositETH2AaveYieldAggregator(10 ether); + + vm.prank(address(sharesFactory)); + aaveYieldAggregator.yieldWithdraw(10 ether); + assertTrue(aWETH.balanceOf(address(sharesFactory)) == 0); + assertTrue(address(sharesFactory).balance == 10 ether); + + _migrate2BlankYieldAggregator(); + vm.prank(address(sharesFactory)); + blankYieldAggregator.yieldWithdraw(10 ether); + assertTrue(address(blankYieldAggregator).balance == 0); + assertTrue(address(sharesFactory).balance == 10 ether); + + vm.expectRevert(bytes("Only factory")); + aaveYieldAggregator.yieldWithdraw(10 ether); + + vm.expectRevert(bytes("Only factory")); + blankYieldAggregator.yieldWithdraw(0); + } + + function test_yieldBalanceOf() public { + _depositETH2AaveYieldAggregator(10 ether); + + uint256 aaveYieldBalance = aaveYieldAggregator.yieldBalanceOf(address(sharesFactory)); + assertEq(aaveYieldBalance, 10 ether); + + uint256 blankYieldBalance = blankYieldAggregator.yieldBalanceOf(address(sharesFactory)); + assertEq(blankYieldBalance, 0); + } + + function test_yieldToken() public view { + address aaveYieldToken = aaveYieldAggregator.yieldToken(); + assertEq(aaveYieldToken, address(aWETH)); + + address blankYieldToken = blankYieldAggregator.yieldToken(); + assertEq(blankYieldToken, address(WETH)); + } + + function test_yieldMaxClaimable() public { + _depositETH2AaveYieldAggregator(10 ether); + uint256 yieldBuffer = aaveYieldAggregator.yieldBuffer(); + + // Initial state, return 0 + uint256 expectedClaimableBefore = 0; + uint256 actualClaimableBefore = aaveYieldAggregator.yieldMaxClaimable(10 ether); + assertEq(actualClaimableBefore, expectedClaimableBefore); + + // Mock time to 2029-11-01 00:00:30, return yield + vm.warp(YIELD_CLAIM_TIME); uint256 withdrawableETHAmount = aWETH.balanceOf(address(sharesFactory)); - uint256 yieldBuffer = withdrawableETHAmount - depositedETHAmount - maxYield; - assertEq(yieldBuffer, defaultYieldBuffer); + uint256 expectedClaimableAfter = withdrawableETHAmount - 10 ether - yieldBuffer; + uint256 actualClaimableAfter = aaveYieldAggregator.yieldMaxClaimable(10 ether); + assertEq(actualClaimableAfter, expectedClaimableAfter); - vm.prank(owner); - aaveYieldAggregator.setYieldBuffer(1e11); - uint256 maxYieldAfter = aaveYieldAggregator.yieldMaxClaimable(depositedETHAmount); - assertEq(maxYieldAfter - maxYield, 1e12 - 1e11); + // BlankYieldAggregator always return 0 + assertEq(blankYieldAggregator.yieldMaxClaimable(0), 0); } - 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 _depositETH2AaveYieldAggregator(uint256 amount) internal { + vm.deal(address(aaveYieldAggregator), amount); + vm.prank(address(sharesFactory)); + aaveYieldAggregator.yieldDeposit(); } - function _buyShare(address sender, uint256 shareId, uint32 quantity, address referral) internal { - (uint256 buyPriceAfterFee,,,) = sharesFactory.getBuyPriceAfterFee(shareId, quantity, referral); - vm.prank(address(sender)); - sharesFactory.buyShare{ value: buyPriceAfterFee }(shareId, quantity, referral); + function _migrate2BlankYieldAggregator() internal { + vm.prank(owner); + sharesFactory.migrate(address(blankYieldAggregator)); } }