From bab6a625893856a2ec59e646ca7f08b071b37088 Mon Sep 17 00:00:00 2001 From: Marc Doerflinger Date: Wed, 3 Jul 2024 13:00:51 +0000 Subject: [PATCH] add happy day testcase for bundle staking (#418) --- contracts/instance/module/IComponents.sol | 1 + contracts/pool/BasicPoolAuthorization.sol | 3 + contracts/pool/BundleService.sol | 6 +- contracts/pool/PoolService.sol | 8 +- test/TestBundle.t.sol | 89 ++++++++++++++++++++++ test/base/GifTest.sol | 12 ++- test/component/distribution/Referral.t.sol | 9 ++- 7 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 test/TestBundle.t.sol diff --git a/contracts/instance/module/IComponents.sol b/contracts/instance/module/IComponents.sol index 2c0b86294..b81cc14b0 100644 --- a/contracts/instance/module/IComponents.sol +++ b/contracts/instance/module/IComponents.sol @@ -14,6 +14,7 @@ interface IComponents { struct ComponentInfo { string name; // component name (needs to be unique per instance) + // TODO: why here **AND** in ProductInfo/PoolInfo? NftId productNftId; IERC20Metadata token; TokenHandler tokenHandler; diff --git a/contracts/pool/BasicPoolAuthorization.sol b/contracts/pool/BasicPoolAuthorization.sol index e8e839c17..6be1e665b 100644 --- a/contracts/pool/BasicPoolAuthorization.sol +++ b/contracts/pool/BasicPoolAuthorization.sol @@ -46,6 +46,9 @@ contract BasicPoolAuthorization _authorize(functions, BasicPool.setMaxCapitalAmount.selector, "setMaxCapitalAmount"); _authorize(functions, BasicPool.setBundleOwnerRole.selector, "setBundleOwnerRole"); _authorize(functions, BasicPool.setFees.selector, "setFees"); + _authorize(functions, BasicPool.stake.selector, "stake"); + _authorize(functions, BasicPool.unstake.selector, "unstake"); + _authorize(functions, BasicPool.extend.selector, "extend"); _authorize(functions, IInstanceLinkedComponent.withdrawFees.selector, "withdrawFees"); diff --git a/contracts/pool/BundleService.sol b/contracts/pool/BundleService.sol index 864b3313e..27454a0ff 100644 --- a/contracts/pool/BundleService.sol +++ b/contracts/pool/BundleService.sol @@ -241,7 +241,7 @@ contract BundleService is ) external virtual - restricted() + // TODO: restricted() (once #462 is done) { IBundle.BundleInfo memory bundleInfo = instance.getInstanceReader().getBundleInfo(bundleNftId); StateId bundleState = instance.getInstanceReader().getMetadata(bundleNftId.toKey32(BUNDLE())).state; @@ -265,7 +265,7 @@ contract BundleService is ) external virtual - restricted() + // TODO: restricted() (once #462 is done) { InstanceStore instanceStore = instance.getInstanceStore(); ( @@ -291,7 +291,7 @@ contract BundleService is function extend(NftId bundleNftId, Seconds lifetimeExtension) external virtual - restricted + // TODO: restricted() (once #462 is done) returns (Timestamp extendedExpiredAt) { (NftId poolNftId,, IInstance instance) = _getAndVerifyActiveComponent(POOL()); diff --git a/contracts/pool/PoolService.sol b/contracts/pool/PoolService.sol index 20e0d61d4..f6309b53d 100644 --- a/contracts/pool/PoolService.sol +++ b/contracts/pool/PoolService.sol @@ -186,8 +186,8 @@ contract PoolService is view returns (Fee memory stakingFee) { - NftId productNftId = instanceReader.getPoolInfo(poolNftId).productNftId; - return instanceReader.getPoolInfo(productNftId).stakingFee; + NftId productNftId = instanceReader.getComponentInfo(poolNftId).productNftId; + return instanceReader.getProductInfo(productNftId).stakingFee; } function closeBundle(NftId bundleNftId) @@ -210,7 +210,7 @@ contract PoolService is function stake(NftId bundleNftId, Amount amount) external virtual - restricted() + // TODO: restricted() (once #462 is done) returns(Amount netAmount) { (NftId poolNftId,, IInstance instance) = _getAndVerifyActiveComponent(POOL()); @@ -259,7 +259,7 @@ contract PoolService is function unstake(NftId bundleNftId, Amount amount) external virtual - restricted() + // TODO: restricted() (once #462 is done) returns(Amount netAmount) { (NftId poolNftId,, IInstance instance) = _getAndVerifyActiveComponent(POOL()); diff --git a/test/TestBundle.t.sol b/test/TestBundle.t.sol new file mode 100644 index 000000000..25a468e66 --- /dev/null +++ b/test/TestBundle.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {console} from "../lib/forge-std/src/Test.sol"; + +import {Amount, AmountLib} from "../contracts/type/Amount.sol"; +import {BasicPoolAuthorization} from "../contracts/pool/BasicPoolAuthorization.sol"; +import {Fee, FeeLib} from "../contracts/type/Fee.sol"; +import {IBundle} from "../contracts/instance/module/IBundle.sol"; +import {IComponents} from "../contracts/instance/module/IComponents.sol"; +import {IKeyValueStore} from "../contracts/shared/IKeyValueStore.sol"; +import {ILifecycle} from "../contracts/shared/ILifecycle.sol"; +import {Key32} from "../contracts/type/Key32.sol"; +import {NftId, NftIdLib} from "../contracts/type/NftId.sol"; +import {ObjectType, BUNDLE} from "../contracts/type/ObjectType.sol"; +import {Pool} from "../contracts/pool/Pool.sol"; +import {IPoolService} from "../contracts/pool/IPoolService.sol"; +import {POOL_OWNER_ROLE, PUBLIC_ROLE} from "../contracts/type/RoleId.sol"; +import {Seconds, SecondsLib} from "../contracts/type/Seconds.sol"; +import {SimplePool} from "./mock/SimplePool.sol"; +import {StateId, ACTIVE, PAUSED, CLOSED} from "../contracts/type/StateId.sol"; +import {TimestampLib} from "../contracts/type/Timestamp.sol"; +import {GifTest} from "./base/GifTest.sol"; +import {UFixedLib} from "../contracts/type/UFixed.sol"; + +contract TestBundle is GifTest { + + + /// @dev test staking of an existing bundle + function test_Bundle_stakeBundle() public { + // GIVEN + initialStakingFee = FeeLib.percentageFee(4); + _prepareProduct(false); + + IComponents.ComponentInfo memory poolComponentInfo = instanceReader.getComponentInfo(poolNftId); + + vm.startPrank(investor); + token.approve(address(pool.getTokenHandler()), 2000); + + Seconds lifetime = SecondsLib.toSeconds(604800); + bundleNftId = pool.createBundle( + FeeLib.zero(), + 1000, + lifetime, + "" + ); + vm.stopPrank(); + + assertTrue(!bundleNftId.eqz(), "bundle nft id is zero"); + + assertEq(token.balanceOf(poolComponentInfo.wallet), 1000, "pool wallet token balance not 1000"); + uint256 investorBalanceBefore = token.balanceOf(investor); + + assertEq(instanceReader.getBalanceAmount(poolNftId).toInt(), 1000, "pool balance not 1000"); + assertEq(instanceReader.getFeeAmount(poolNftId).toInt(), 40, "pool fees not 40"); + + assertEq(instanceReader.getBalanceAmount(bundleNftId).toInt(), 960, "bundle balance not 960"); + assertEq(instanceReader.getFeeAmount(bundleNftId).toInt(), 0, "bundle fees 0"); + + uint256 stakeAmount = 1000; + Amount stakeAmt = AmountLib.toAmount(stakeAmount); + Amount stakeNetAmt = AmountLib.toAmount(960); + vm.startPrank(investor); + + // THEN - expect log event + vm.expectEmit(); + emit IPoolService.LogPoolServiceBundleStaked(instanceNftId, poolNftId, bundleNftId, stakeAmt, stakeNetAmt); + + // WHEN - pool is staked with another 1000 tokens + pool.stake(bundleNftId, stakeAmt); + + // THEN - assert all counters are updated + assertEq(token.balanceOf(poolComponentInfo.wallet), 2000, "pool wallet token balance not 2000"); + assertEq(token.balanceOf(investor), investorBalanceBefore - stakeAmount, "investor token balance not 0"); + + assertEq(instanceReader.getBalanceAmount(poolNftId).toInt(), 2000, "pool balance not 2000"); + assertEq(instanceReader.getFeeAmount(poolNftId).toInt(), 80, "pool fees not 80"); + + assertEq(instanceReader.getBalanceAmount(bundleNftId).toInt(), 1920, "bundle balance not 1920"); + assertEq(instanceReader.getFeeAmount(bundleNftId).toInt(), 0, "bundle fees 0"); + } + + function _fundInvestor(uint256 amount) internal { + vm.startPrank(registryOwner); + token.transfer(investor, amount); + vm.stopPrank(); + } + +} diff --git a/test/base/GifTest.sol b/test/base/GifTest.sol index 31efbf32e..3678710e0 100644 --- a/test/base/GifTest.sol +++ b/test/base/GifTest.sol @@ -14,6 +14,7 @@ import { // GIF_MANAGER_ROLE, // GIF_ADMIN_ROLE, // ADMIN_ROLE, + PUBLIC_ROLE, PRODUCT_OWNER_ROLE, ORACLE_OWNER_ROLE, POOL_OWNER_ROLE, @@ -171,12 +172,14 @@ contract GifTest is GifDeployer { uint8 initialProductFeePercentage = 2; uint8 initialPoolFeePercentage = 3; + uint8 initialStakingFeePercentage = 0; uint8 initialBundleFeePercentage = 4; uint8 initialDistributionFeePercentage = 20; uint8 initialMinDistributionOwnerFeePercentage = 2; Fee public initialProductFee = FeeLib.percentageFee(initialProductFeePercentage); Fee public initialPoolFee = FeeLib.percentageFee(initialPoolFeePercentage); + Fee public initialStakingFee = FeeLib.percentageFee(initialStakingFeePercentage); Fee public initialBundleFee = FeeLib.percentageFee(initialBundleFeePercentage); Fee public initialDistributionFee = FeeLib.percentageFee(initialDistributionFeePercentage); Fee public initialMinDistributionOwnerFee = FeeLib.percentageFee(initialMinDistributionOwnerFeePercentage); @@ -460,7 +463,14 @@ contract GifTest is GifDeployer { initialDistributionFee, initialMinDistributionOwnerFee); vm.stopPrank(); - + + vm.startPrank(poolOwner); + pool.setFees( + initialPoolFee, + initialStakingFee, + FeeLib.zero()); + vm.stopPrank(); + // solhint-disable console.log("product nft id", productNftId.toInt()); console.log("product component at", address(product)); diff --git a/test/component/distribution/Referral.t.sol b/test/component/distribution/Referral.t.sol index 99fb6b026..f0a75abb4 100644 --- a/test/component/distribution/Referral.t.sol +++ b/test/component/distribution/Referral.t.sol @@ -126,8 +126,8 @@ contract ReferralTest is ReferralTestBase { assertTrue(instanceReader.getPolicyState(policyNftId) == ACTIVE(), "policy state not ACTIVE"); uint256 netPremium = 100; - uint256 expectedPremium = netPremium + 14; // 100 (net premium) + 14 (distribution fee 3 + 11 distributor commission) - assertEq(token.balanceOf(address(customer)), initialCustomerBalance - expectedPremium, "customer balance not 886"); + uint256 expectedPremium = netPremium + 17; // 100 (net premium) + 14 (distribution fee 3 + pool fee 3 + 11 distributor commission) + assertEq(token.balanceOf(address(customer)), initialCustomerBalance - expectedPremium, "customer balance not 883"); { IPolicy.PolicyInfo memory policyInfo = instanceReader.getPolicyInfo(policyNftId); @@ -146,8 +146,9 @@ contract ReferralTest is ReferralTestBase { assertEq(instanceReader.getFeeAmount(distributorNftId).toInt(), 3, "sumCommisions not 3"); // check pool financials and balance - assertEq(instanceReader.getBalanceAmount(poolNftId).toInt(), initialPoolBalance + netPremium, "unexpected pool balance (1)"); - assertEq(token.balanceOf(pool.getWallet()), initialPoolBalance + netPremium, "unexpected pool balance (2)"); + uint256 expectedPoolFee = 3; + assertEq(instanceReader.getBalanceAmount(poolNftId).toInt(), initialPoolBalance + netPremium + expectedPoolFee, "unexpected pool balance (1)"); + assertEq(token.balanceOf(pool.getWallet()), initialPoolBalance + netPremium + expectedPoolFee, "unexpected pool balance (2)"); assertEq(instanceBundleSet.activePolicies(bundleNftId), 1, "expected one active policy"); assertTrue(instanceBundleSet.getActivePolicy(bundleNftId, 0).eq(policyNftId), "active policy nft id in bundle manager not equal to policy nft id");