From 804c24f08cdcba9d1edc9f75a9bf6d4f1912521b Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Sun, 8 Sep 2024 23:11:41 +0000 Subject: [PATCH] fix tests --- contracts/instance/Instance.sol | 2 +- contracts/registry/RegistryAuthorization.sol | 19 +- contracts/registry/ReleaseRegistry.sol | 7 +- contracts/shared/ComponentService.sol | 20 +- contracts/shared/ContractLib.sol | 5 +- contracts/staking/IStaking.sol | 6 + contracts/staking/Staking.sol | 82 +++++-- contracts/staking/StakingReader.sol | 2 +- contracts/staking/StakingService.sol | 2 +- contracts/staking/StakingStore.sol | 81 +++++-- contracts/type/UFixed.sol | 1 + .../distribution/Distributor.Cluster.t.sol | 5 +- test/registry/RegistryTokenWhitelisting.t.sol | 3 +- test/staking/Staking.t.sol | 229 +++++++++++------- test/staking/StakingOwner.t.sol | 28 ++- test/staking/TvlCalculation.t.sol | 74 +++--- 16 files changed, 384 insertions(+), 182 deletions(-) diff --git a/contracts/instance/Instance.sol b/contracts/instance/Instance.sol index 9f6f3fc1d..9379555b1 100644 --- a/contracts/instance/Instance.sol +++ b/contracts/instance/Instance.sol @@ -64,7 +64,7 @@ contract Instance is IRegistry registry, VersionPart release, address initialOwner, - bool tokenRegistryDisabled + bool tokenRegistryDisabled // only disable for testing ) external initializer() diff --git a/contracts/registry/RegistryAuthorization.sol b/contracts/registry/RegistryAuthorization.sol index 4bb5550ea..ee6f54055 100644 --- a/contracts/registry/RegistryAuthorization.sol +++ b/contracts/registry/RegistryAuthorization.sol @@ -6,7 +6,7 @@ import {IRegistry} from "../registry/IRegistry.sol"; import {IStaking} from "../staking/IStaking.sol"; import {Authorization} from "../authorization/Authorization.sol"; -import {POOL, REGISTRY, STAKING} from "../../contracts/type/ObjectType.sol"; +import {COMPONENT, POOL, REGISTRY, STAKING} from "../../contracts/type/ObjectType.sol"; import {PUBLIC_ROLE} from "../type/RoleId.sol"; import {ReleaseRegistry} from "./ReleaseRegistry.sol"; import {RegistryAdmin} from "./RegistryAdmin.sol"; @@ -21,7 +21,6 @@ import {VersionPartLib} from "../type/Version.sol"; contract RegistryAuthorization is Authorization { - /// @dev gif core roles string public constant GIF_ADMIN_ROLE_NAME = "GifAdminRole"; string public constant GIF_MANAGER_ROLE_NAME = "GifManagerRole"; @@ -77,6 +76,14 @@ contract RegistryAuthorization maxMemberCount: maxReleases, name: STAKING_SERVICE_ROLE_NAME})); + _addRole( + RoleIdLib.toGenericServiceRoleId(COMPONENT()), + _toRoleInfo({ + adminRoleId: ADMIN_ROLE(), + roleType: RoleType.Core, + maxMemberCount: maxReleases, + name: COMPONENT_SERVICE_ROLE_NAME})); + _addRole( RoleIdLib.toGenericServiceRoleId(POOL()), _toRoleInfo({ @@ -235,6 +242,13 @@ contract RegistryAuthorization _authorize(functions, IStaking.updateRewards.selector, "updateRewards"); _authorize(functions, IStaking.claimRewards.selector, "claimRewards"); + // pool service role + functions = _authorizeForTarget( + STAKING_TARGET_NAME, + RoleIdLib.toGenericServiceRoleId(COMPONENT())); + + _authorize(functions, IStaking.addTargetToken.selector, "addTargetToken"); + // pool service role functions = _authorizeForTarget( STAKING_TARGET_NAME, @@ -276,6 +290,7 @@ contract RegistryAuthorization STAKING_STORE_TARGET_NAME, getTargetRole(getTarget(STAKING_TARGET_NAME))); + _authorize(functions, StakingStore.addTargetToken.selector, "addTargetToken"); _authorize(functions, StakingStore.addToken.selector, "addToken"); _authorize(functions, StakingStore.setStakingRate.selector, "setStakingRate"); _authorize(functions, StakingStore.createTarget.selector, "createTarget"); diff --git a/contracts/registry/ReleaseRegistry.sol b/contracts/registry/ReleaseRegistry.sol index 37df219b6..94bd50ab5 100644 --- a/contracts/registry/ReleaseRegistry.sol +++ b/contracts/registry/ReleaseRegistry.sol @@ -14,7 +14,7 @@ import {IServiceAuthorization} from "../authorization/IServiceAuthorization.sol" import {ContractLib} from "../shared/ContractLib.sol"; import {NftId} from "../type/NftId.sol"; -import {ObjectType, ObjectTypeLib, POOL, RELEASE, REGISTRY, SERVICE, STAKING} from "../type/ObjectType.sol"; +import {ObjectType, ObjectTypeLib, COMPONENT, POOL, RELEASE, REGISTRY, SERVICE, STAKING} from "../type/ObjectType.sol"; import {RegistryAdmin} from "./RegistryAdmin.sol"; import {Registry} from "./Registry.sol"; import {ReleaseAdmin} from "./ReleaseAdmin.sol"; @@ -258,6 +258,11 @@ contract ReleaseRegistry is _registryAdmin.grantServiceRoleForAllVersions(IService(service), STAKING()); } + service = _registry.getServiceAddress(COMPONENT(), release); + if(service != address(0)) { + _registryAdmin.grantServiceRoleForAllVersions(IService(service), COMPONENT()); + } + service = _registry.getServiceAddress(POOL(), release); if(service != address(0)) { _registryAdmin.grantServiceRoleForAllVersions(IService(service), POOL()); diff --git a/contracts/shared/ComponentService.sol b/contracts/shared/ComponentService.sol index 5e31f8348..5d62e8e3d 100644 --- a/contracts/shared/ComponentService.sol +++ b/contracts/shared/ComponentService.sol @@ -11,19 +11,21 @@ import {IInstanceLinkedComponent} from "./IInstanceLinkedComponent.sol"; import {InstanceAdmin} from "../instance/InstanceAdmin.sol"; import {InstanceReader} from "../instance/InstanceReader.sol"; import {InstanceStore} from "../instance/InstanceStore.sol"; -import {IInstanceService} from "../instance/IInstanceService.sol"; import {IPoolComponent} from "../pool/IPoolComponent.sol"; import {IProductComponent} from "../product/IProductComponent.sol"; import {IRegistry} from "../registry/IRegistry.sol"; import {IRegistryService} from "../registry/IRegistryService.sol"; +import {IStaking} from "../staking/IStaking.sol"; +import {IStakingService} from "../staking/IStakingService.sol"; import {AccessManagerCloneable} from "../authorization/AccessManagerCloneable.sol"; import {Amount, AmountLib} from "../type/Amount.sol"; +import {ChainIdLib} from "../type/ChainId.sol"; import {ContractLib} from "../shared/ContractLib.sol"; import {Fee, FeeLib} from "../type/Fee.sol"; import {KEEP_STATE} from "../type/StateId.sol"; import {NftId, NftIdLib} from "../type/NftId.sol"; -import {ObjectType, ACCOUNTING, REGISTRY, COMPONENT, DISTRIBUTION, INSTANCE, ORACLE, POOL, PRODUCT} from "../type/ObjectType.sol"; +import {ObjectType, ACCOUNTING, REGISTRY, COMPONENT, DISTRIBUTION, INSTANCE, ORACLE, POOL, PRODUCT, STAKING} from "../type/ObjectType.sol"; import {Service} from "../shared/Service.sol"; import {TokenHandler} from "../shared/TokenHandler.sol"; import {TokenHandlerDeployerLib} from "../shared/TokenHandlerDeployerLib.sol"; @@ -38,7 +40,7 @@ contract ComponentService is IAccountingService private _accountingService; IRegistryService private _registryService; - IInstanceService private _instanceService; + IStaking private _staking; function _initialize( address owner, @@ -57,7 +59,7 @@ contract ComponentService is _accountingService = IAccountingService(_getServiceAddress(ACCOUNTING())); _registryService = IRegistryService(_getServiceAddress(REGISTRY())); - _instanceService = IInstanceService(_getServiceAddress(INSTANCE())); + _staking = IStakingService(_getServiceAddress(STAKING())).getStaking(); _registerInterface(type(IComponentService).interfaceId); } @@ -219,10 +221,16 @@ contract ComponentService is } IInstance instance = IInstance(msg.sender); + NftId instanceNftId = registry.getNftIdForAddress(msg.sender); productNftId = _verifyAndRegister( instance, productAddress, - instance.getNftId(), // instance is parent of product to be registered + instanceNftId, // instance is parent of product to be registered + token); + + // add product specific token for product to staking + _staking.addTargetToken( + instanceNftId, token); } @@ -560,8 +568,8 @@ contract ComponentService is // check if provided token is whitelisted and active if (!ContractLib.isActiveToken( getRegistry().getTokenRegistryAddress(), + ChainIdLib.current(), token, - block.chainid, AccessManagerCloneable(authority()).getRelease()) ) { revert ErrorComponentServiceTokenInvalid(token); diff --git a/contracts/shared/ContractLib.sol b/contracts/shared/ContractLib.sol index 69eea4353..2e2fc0d6e 100644 --- a/contracts/shared/ContractLib.sol +++ b/contracts/shared/ContractLib.sol @@ -9,6 +9,7 @@ import {IPolicyHolder} from "../shared/IPolicyHolder.sol"; import {IRegistry} from "../registry/IRegistry.sol"; import {IService} from "../shared/IService.sol"; +import {ChainId} from "../type/ChainId.sol"; import {NftId} from "../type/NftId.sol"; import {ObjectType, INSTANCE, PRODUCT, DISTRIBUTION, ORACLE, POOL} from "../type/ObjectType.sol"; import {VersionPart} from "../type/Version.sol"; @@ -22,7 +23,7 @@ interface IInstanceAdminHelper { } interface ITokenRegistryHelper { - function isActive(uint256 chainId, address token, VersionPart release) external view returns (bool); + function isActive(ChainId chainId, address token, VersionPart release) external view returns (bool); } library ContractLib { @@ -114,8 +115,8 @@ library ContractLib { function isActiveToken( address tokenRegistryAddress, + ChainId chainId, address token, - uint256 chainId, VersionPart release ) external diff --git a/contracts/staking/IStaking.sol b/contracts/staking/IStaking.sol index a6ba759f3..0f18931fd 100644 --- a/contracts/staking/IStaking.sol +++ b/contracts/staking/IStaking.sol @@ -34,6 +34,11 @@ interface IStaking is // token event LogStakingTokenAdded(ChainId chainId, address token); + event LogStakingTargetTokenAdded(NftId targetNftId, ChainId chainId, address token); + + // total value locked + event LogStakingTotalValueLockedIncreased(NftId targetNftId, address token, Amount amount, Amount newBalance); + event LogStakingTotalValueLockedDecreased(NftId targetNftId, address token, Amount amount, Amount newBalance); // targets event LogStakingTargetCreated(NftId targetNftId, ObjectType objectType, Seconds lockingPeriod, UFixed rewardRate, Amount maxStakedAmount); @@ -45,6 +50,7 @@ interface IStaking is // stakes event LogStakingStakeRegistered(NftId stakeNftId, NftId targetNftId, Amount stakeAmount); + event LogStakingStakeRewardsUpdated(NftId stakeNftId, Amount rewardIncrementAmount, Amount rewardBalanceAmount); event LogStakingStakeRestaked(NftId stakeNftId, NftId targetNftId, Amount stakeAmount, address owner, NftId oldStakeNftId); // modifiers diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index 38eb2b16e..7d8dddd4b 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -11,7 +11,7 @@ import {IVersionable} from "../upgradeability/IVersionable.sol"; import {Amount, AmountLib} from "../type/Amount.sol"; import {Blocknumber} from "../type/Blocknumber.sol"; -import {ChainId} from "../type/ChainId.sol"; +import {ChainId, ChainIdLib} from "../type/ChainId.sol"; import {Component} from "../shared/Component.sol"; import {IComponent} from "../shared/IComponent.sol"; import {IComponentService} from "../shared/IComponentService.sol"; @@ -189,6 +189,7 @@ contract Staking is function setStakingReader(StakingReader stakingReader) external virtual + restricted() onlyOwner() { if(stakingReader.getStaking() != IStaking(this)) { @@ -324,8 +325,17 @@ contract Staking is restricted() onlyTarget(targetNftId) { - _getStakingStorage()._store.addTargetToken(targetNftId, token); - // TODO add logging + StakingStorage storage $ = _getStakingStorage(); + ChainId chainId = ChainIdLib.fromNftId(targetNftId); + if ($._store.getTokenInfo(chainId, token).lastUpdatedIn.eqz()) { + $._store.addToken(chainId, token); + + emit LogStakingTokenAdded(chainId, token); + } + + $._store.addTargetToken(targetNftId, token); + + emit LogStakingTargetTokenAdded(targetNftId, chainId, token); } @@ -339,7 +349,7 @@ contract Staking is StakingStorage storage $ = _getStakingStorage(); newBalance = $._store.increaseTotalValueLocked(targetNftId, token, amount); - // TODO add logging + emit LogStakingTotalValueLockedIncreased(targetNftId, token, amount, newBalance); } @@ -353,7 +363,7 @@ contract Staking is StakingStorage storage $ = _getStakingStorage(); newBalance = $._store.decreaseTotalValueLocked(targetNftId, token, amount); - // TODO add logging + emit LogStakingTotalValueLockedDecreased(targetNftId, token, amount, newBalance); } @@ -404,14 +414,29 @@ contract Staking is virtual restricted() // only staking service onlyStake(stakeNftId) - returns (Amount stakeBalance) + returns (Amount newStakeBalance) { StakingStorage storage $ = _getStakingStorage(); + + // update rewards since last update + NftId targetNftId = $._reader.getTargetNftId(stakeNftId); + TargetInfo memory targetInfo = $._reader.getTargetInfo(targetNftId); + (Amount rewardIncrement, ) = StakingLib.calculateRewardIncrease( + $._reader, + stakeNftId, + targetInfo.rewardRate); + + // calculate additional locking period if applicable + Seconds additionalLockingPeriod = SecondsLib.zero(); + if (stakeAmount.gtz()) { + additionalLockingPeriod = targetInfo.lockingPeriod; + } + $._store.increaseStakeBalances( stakeNftId, stakeAmount, - AmountLib.zero(), - SecondsLib.zero()); // TODO define effect on locking period + rewardIncrement, + additionalLockingPeriod); // TODO define effect on locking period // TODO add logging } @@ -432,13 +457,20 @@ contract Staking is ( unstakedAmount, rewardsClaimedAmount - ) = _unstakeAll($, stakeNftId); + ) = _unstakeAll( + $, + stakeNftId, + true); // claim rewards // TODO add logging } - function _unstakeAll(StakingStorage storage $, NftId stakeNftId) + function _unstakeAll( + StakingStorage storage $, + NftId stakeNftId, + bool claimRewards + ) internal virtual returns ( @@ -452,7 +484,7 @@ contract Staking is } // update rewards since last update - NftId targetNftId = _updateRewards($._reader, $._store, stakeNftId); + _updateRewards($._reader, $._store, stakeNftId); ( unstakedAmount, @@ -460,7 +492,8 @@ contract Staking is ) = $._store.decreaseStakeBalances( stakeNftId, AmountLib.max(), // unstake all stakes - AmountLib.max()); // claim all rewards + AmountLib.max(), // claim all rewards + claimRewards); } @@ -472,6 +505,7 @@ contract Staking is virtual restricted() // only staking service onlyStakeOwner(stakeNftId) + onlyTarget(newTargetNftId) returns ( NftId newStakeNftId, Amount newStakedAmount @@ -483,11 +517,17 @@ contract Staking is ( Amount unstakedAmount, Amount rewardsClaimedAmount - ) = _unstakeAll($, stakeNftId); + ) = _unstakeAll( + $, + stakeNftId, + false); // do not claim rewards, as rewards are restaked newStakeNftId = $._stakingService.createStakeObject(newTargetNftId, stakeOwner); newStakedAmount = unstakedAmount + rewardsClaimedAmount; - $._store.createStake(newStakeNftId, newTargetNftId, newStakedAmount); + $._store.createStake( + newStakeNftId, + newTargetNftId, + newStakedAmount); emit LogStakingStakeRestaked(newStakeNftId, newTargetNftId, newStakedAmount, stakeOwner, stakeNftId); } @@ -515,13 +555,14 @@ contract Staking is StakingStorage storage $ = _getStakingStorage(); // update rewards since last update - NftId targetNftId = _updateRewards($._reader, $._store, stakeNftId); + _updateRewards($._reader, $._store, stakeNftId); // unstake all available rewards (, rewardsClaimedAmount) = $._store.decreaseStakeBalances( stakeNftId, AmountLib.zero(), // unstake dip amount - AmountLib.max()); // unstake reward amount + AmountLib.max(), // unstake reward amount + true); // claim rewards // TODO add logging } @@ -568,7 +609,6 @@ contract Staking is //--- internal functions ------------------------------------------------// - function _updateRewards( StakingReader reader, StakingStore store, @@ -576,21 +616,21 @@ contract Staking is ) internal virtual - returns (NftId targetNftId) { - UFixed rewardRate; + (, UFixed rewardRate) = reader.getTargetRewardRate(stakeNftId); - (targetNftId, rewardRate) = reader.getTargetRewardRate(stakeNftId); (Amount rewardIncrement, ) = StakingLib.calculateRewardIncrease( reader, stakeNftId, rewardRate); - store.increaseStakeBalances( + (, Amount newRewardAmount,) = store.increaseStakeBalances( stakeNftId, AmountLib.zero(), rewardIncrement, SecondsLib.zero()); + + emit LogStakingStakeRewardsUpdated(stakeNftId, rewardIncrement, newRewardAmount); } diff --git a/contracts/staking/StakingReader.sol b/contracts/staking/StakingReader.sol index a79eff530..a0f8436df 100644 --- a/contracts/staking/StakingReader.sol +++ b/contracts/staking/StakingReader.sol @@ -147,7 +147,7 @@ contract StakingReader is function getRequiredStakeBalance(NftId targetNftId) external view returns (Amount requiredStakedAmount) { // TODO implement - return AmountLib.zero(); + return _store.getRequiredStakeBalance(targetNftId); } // TODO cleanup diff --git a/contracts/staking/StakingService.sol b/contracts/staking/StakingService.sol index 6f7befe5d..bbbd562f6 100644 --- a/contracts/staking/StakingService.sol +++ b/contracts/staking/StakingService.sol @@ -252,7 +252,7 @@ contract StakingService is // collect staked dip by staking if (dipAmount.gtz()) { emit LogStakingServiceStakeIncreased(stakeNftId, stakeOwner, dipAmount, stakeBalance); - $._tokenHandler.pullToken( + $._tokenHandler.pullToken( stakeOwner, dipAmount); } diff --git a/contracts/staking/StakingStore.sol b/contracts/staking/StakingStore.sol index b4e9c353d..511b2afa5 100644 --- a/contracts/staking/StakingStore.sol +++ b/contracts/staking/StakingStore.sol @@ -41,7 +41,7 @@ contract StakingStore is // in/decreasing reward reserves error ErrorStakingStoreNotTarget(NftId targetNftId); - error ErrorStakingStoreRewardReservesInsufficient(NftId targetNftId, Amount reservesBalanceAmount, Amount dipAmount); + error ErrorStakingStoreRewardReservesInsufficient(NftId targetNftId, Amount reserveAmount, Amount claimedAmount); // stakes error ErrorStakingStoreStakesExceedingTargetMaxAmount(NftId targetNftId, Amount maxStakedAmount, Amount newIStaking); @@ -111,11 +111,12 @@ contract StakingStore is revert ErrorStakingStoreTokenAlreadyAdded(chainId, token); } - // check if token is registered with token registry - TokenRegistry tokenRegistry = TokenRegistry(_registry.getTokenRegistryAddress()); - if (!tokenRegistry.isRegistered(chainId, token)) { - revert ErrorStakingStoreTokenNotRegistered(chainId, token); - } + // TODO cleanup + // // check if token is registered with token registry + // TokenRegistry tokenRegistry = TokenRegistry(_registry.getTokenRegistryAddress()); + // if (!tokenRegistry.isRegistered(chainId, token)) { + // revert ErrorStakingStoreTokenNotRegistered(chainId, token); + // } info.stakingRate = UFixedLib.zero(); info.lastUpdatedIn = BlocknumberLib.current(); @@ -421,7 +422,7 @@ contract StakingStore is // effects // update target - targetInfo.stakedAmount = stakedAmount; + targetInfo.stakedAmount = targetInfo.stakedAmount + stakedAmount; targetInfo.lastUpdatedIn = BlocknumberLib.current(); // update stake @@ -442,6 +443,11 @@ contract StakingStore is ) external restricted() + returns ( + Amount newStakedAmount, + Amount newRewardAmount, + Timestamp newLockedUntil + ) { // checks IStaking.StakeInfo storage stakeInfo = _getAndVerifyStake(stakeNftId); @@ -455,14 +461,18 @@ contract StakingStore is targetInfo.lastUpdatedIn = BlocknumberLib.current(); // update stake - stakeInfo.stakedAmount = stakeInfo.stakedAmount + stakedAmount; - stakeInfo.rewardAmount = stakeInfo.rewardAmount + rewardAmount; + newStakedAmount = stakeInfo.stakedAmount + stakedAmount; + newRewardAmount = stakeInfo.rewardAmount + rewardAmount; + newLockedUntil = stakeInfo.lockedUntil; + stakeInfo.stakedAmount = newStakedAmount; + stakeInfo.rewardAmount = newRewardAmount; stakeInfo.lastUpdateAt = TimestampLib.current(); stakeInfo.lastUpdatedIn = BlocknumberLib.current(); // increase locked until if applicable if (additionalLockingPeriod.gtz()) { - stakeInfo.lockedUntil.addSeconds(additionalLockingPeriod); + newLockedUntil = stakeInfo.lockedUntil.addSeconds(additionalLockingPeriod); + stakeInfo.lockedUntil = newLockedUntil; } } @@ -470,7 +480,8 @@ contract StakingStore is function decreaseStakeBalances( NftId stakeNftId, Amount maxUnstakedAmount, - Amount maxClaimAmount + Amount maxClaimAmount, + bool claimRewards ) external restricted() @@ -490,7 +501,19 @@ contract StakingStore is // update target targetInfo.stakedAmount = targetInfo.stakedAmount - unstakedAmount; targetInfo.rewardAmount = targetInfo.rewardAmount - claimedAmount; - targetInfo.reserveAmount = targetInfo.reserveAmount - claimedAmount; + + // update reserves if rewards are claimed + if (claimRewards) { + if (claimedAmount > targetInfo.reserveAmount) { + revert ErrorStakingStoreRewardReservesInsufficient( + stakeInfo.targetNftId, + targetInfo.reserveAmount, + claimedAmount); + } + + targetInfo.reserveAmount = targetInfo.reserveAmount - claimedAmount; + } + targetInfo.lastUpdatedIn = BlocknumberLib.current(); // update stake @@ -546,11 +569,38 @@ contract StakingStore is return _reader; } - function exists(NftId stakeNftId) external view returns (bool) { return exists(stakeNftId.toKey32(STAKE())); } + + function exists(NftId stakeNftId) external view returns (bool) { + return _stakeInfo[stakeNftId].lastUpdatedIn.gtz(); + } - function getRequiredStakeBalance(NftId nftId) external view returns (Amount requiredAmount) { - // TODO implement + function getRequiredStakeBalance(NftId targetNftId) + external + view + returns (Amount requiredStakedAmount) + { + address [] memory tokens = _targetToken[targetNftId]; + if (tokens.length == 0) { + return AmountLib.zero(); + } + + requiredStakedAmount = AmountLib.zero(); + ChainId targetChainId = _targetInfo[targetNftId].chainId; + address token; + Amount tvlAmount; + UFixed stakingRate; + + for (uint256 i = 0; i < tokens.length; i++) { + token = tokens[i]; + tvlAmount = _tvlInfo[targetNftId][token].tvlAmount; + if (tvlAmount.eqz()) { continue; } + + stakingRate = _tokenInfo[targetChainId][token].stakingRate; + if (stakingRate.eqz()) { continue; } + + requiredStakedAmount = requiredStakedAmount + tvlAmount.multiplyWith(stakingRate); + } } @@ -570,6 +620,7 @@ contract StakingStore is stakeInfo.lastUpdateAt); } + /// @dev Returns true iff current stake amount is still locked function isStakeLocked(NftId stakeNftId) external view returns (bool) { return _stakeInfo[stakeNftId].lockedUntil > TimestampLib.current(); diff --git a/contracts/type/UFixed.sol b/contracts/type/UFixed.sol index 47de66546..c151c9c05 100644 --- a/contracts/type/UFixed.sol +++ b/contracts/type/UFixed.sol @@ -18,6 +18,7 @@ using { eqUFixed as ==, neUFixed as !=, UFixedLib.gt, + UFixedLib.eqz, UFixedLib.gtz, UFixedLib.toInt, UFixedLib.toInt1000 diff --git a/test/component/distribution/Distributor.Cluster.t.sol b/test/component/distribution/Distributor.Cluster.t.sol index b23e2df15..1dee141d2 100644 --- a/test/component/distribution/Distributor.Cluster.t.sol +++ b/test/component/distribution/Distributor.Cluster.t.sol @@ -48,6 +48,8 @@ contract DistributorClusterTest is GifClusterTest { DistributorType type2 = _createDistributorType(myDistribution2, instanceOwner); // THEN + address distribution1Owner = myDistribution1.getOwner(); + address newDistributor = makeAddr("newDistributor"); vm.expectRevert(abi.encodeWithSelector( IDistributionService.ErrorDistributionServiceDistributorTypeDistributionMismatch.selector, type2, @@ -55,7 +57,8 @@ contract DistributorClusterTest is GifClusterTest { myDistributionNftId1)); // WHEN - myDistribution1.createDistributor(makeAddr("distributor"), type2, ""); + vm.prank(distribution1Owner); + myDistribution1.createDistributor(newDistributor, type2, ""); } function test_changeDistributorType_typeFromOtherProductCluster() public { diff --git a/test/registry/RegistryTokenWhitelisting.t.sol b/test/registry/RegistryTokenWhitelisting.t.sol index f66ac01ff..9c4c0c474 100644 --- a/test/registry/RegistryTokenWhitelisting.t.sol +++ b/test/registry/RegistryTokenWhitelisting.t.sol @@ -198,8 +198,9 @@ contract RegistryTokenWhitelisting is GifTest { tokenRegistry.registerRemoteToken(quakChainId, quakAddress, quakDecimals, quakSymbol); // attempt to register quak token for currentChainId 0 + ChainId zeroChainId = ChainIdLib.zero(); vm.expectRevert(TokenRegistry.ErrorTokenRegistryChainIdZero.selector); - tokenRegistry.registerRemoteToken(ChainIdLib.zero(), quakAddress, quakDecimals, quakSymbol); + tokenRegistry.registerRemoteToken(zeroChainId, quakAddress, quakDecimals, quakSymbol); // attempt to register quak token for currentChainId 0 vm.expectRevert(TokenRegistry.ErrorTokenRegistryTokenAddressZero.selector); diff --git a/test/staking/Staking.t.sol b/test/staking/Staking.t.sol index e8973c236..4fe98d251 100644 --- a/test/staking/Staking.t.sol +++ b/test/staking/Staking.t.sol @@ -10,6 +10,7 @@ import {IStaking} from "../../contracts/staking/IStaking.sol"; import {IStakingService} from "../../contracts/staking/IStakingService.sol"; import {Amount, AmountLib} from "../../contracts/type/Amount.sol"; +import {BlocknumberLib} from "../../contracts/type/Blocknumber.sol"; import {GifTest} from "../base/GifTest.sol"; import {NftId, NftIdLib} from "../../contracts/type/NftId.sol"; import {STAKE} from "../../contracts/type/ObjectType.sol"; @@ -154,11 +155,12 @@ contract StakingTest is GifTest { assertEq(stakeInfo.lockedUntil.toInt(), TimestampLib.current().toInt() + lockingPeriod.toInt(), "unexpected locked until"); // check accumulated stakes/rewards on instance - assertEq(stakingReader.getStakeInfo(instanceNftId).stakedAmount.toInt(), dipAmount.toInt(), "unexpected instance stake amount"); - assertEq(stakingReader.getStakeInfo(instanceNftId).rewardAmount.toInt(), 0, "unexpected instance reward amount"); - assertEq(stakingReader.getStakeInfo(instanceNftId).lastUpdatedIn.toInt(), block.number, "unexpected instance last updated in"); + assertEq(stakingReader.getTargetInfo(instanceNftId).stakedAmount.toInt(), dipAmount.toInt(), "unexpected instance stake amount"); + assertEq(stakingReader.getTargetInfo(instanceNftId).rewardAmount.toInt(), 0, "unexpected instance reward amount"); + assertEq(stakingReader.getTargetInfo(instanceNftId).lastUpdatedIn.toInt(), block.number, "unexpected instance last updated in"); } + function test_stakingExceedsMaxStakedAmount() public { // GIVEN (, Amount dipAmount) = _prepareAccount(staker2, 3000); @@ -266,9 +268,9 @@ contract StakingTest is GifTest { assertEq(stakingReader.getStakeInfo(stakeNftId).lastUpdatedIn.toInt(), lastUpdateAt, "unexpected last updated at (before)"); // check accumulated stakes/rewards on instance - assertEq(stakingReader.getStakeInfo(instanceNftId).stakedAmount.toInt(), dipAmount.toInt(), "unexpected instance stake amount (before)"); - assertEq(stakingReader.getStakeInfo(instanceNftId).rewardAmount.toInt(), 0, "unexpected instance reward amount (before)"); - assertEq(stakingReader.getStakeInfo(instanceNftId).lastUpdatedIn.toInt(), lastUpdateIn, "unexpected instance last updated at (before)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).stakedAmount.toInt(), dipAmount.toInt(), "unexpected instance stake amount (before)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).rewardAmount.toInt(), 0, "unexpected instance reward amount (before)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).lastUpdatedIn.toInt(), lastUpdateIn, "unexpected instance last updated at (before)"); // WHEN // update rewards (unpermissioned) @@ -281,13 +283,13 @@ contract StakingTest is GifTest { assertEq(stakingReader.getStakeInfo(stakeNftId).lastUpdatedIn.toInt(), block.number, "unexpected last updated at (after)"); // re-check accumulated stakes/rewards on instance - assertEq(stakingReader.getStakeInfo(instanceNftId).stakedAmount.toInt(), dipAmount.toInt(), "unexpected instance stake amount (after)"); - assertEq(stakingReader.getStakeInfo(instanceNftId).rewardAmount.toInt(), expectedRewardIncrease.toInt(), "unexpected instance reward amount (after)"); - assertEq(stakingReader.getStakeInfo(instanceNftId).lastUpdatedIn.toInt(), block.number, "unexpected instance last updated at (after)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).stakedAmount.toInt(), dipAmount.toInt(), "unexpected instance stake amount (after)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).rewardAmount.toInt(), expectedRewardIncrease.toInt(), "unexpected instance reward amount (after)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).lastUpdatedIn.toInt(), block.number, "unexpected instance last updated at (after)"); } - function test_stakingStakeIncreaseAfterOneYear() public { + function test_stakingStakeIncreaseByZeroAfterOneYear() public { ( , @@ -297,6 +299,7 @@ contract StakingTest is GifTest { // record time at stake creation uint256 lastUpdateAt = block.timestamp; + uint256 lastUpdateIn = block.number; // wait a year _wait(SecondsLib.oneYear()); @@ -326,27 +329,89 @@ contract StakingTest is GifTest { // check stake/rewards balance (before calling restake) assertEq(stakingReader.getStakeInfo(stakeNftId).stakedAmount.toInt(), dipAmount.toInt(), "unexpected stake amount (before)"); assertEq(stakingReader.getStakeInfo(stakeNftId).rewardAmount.toInt(), 0, "unexpected reward amount (before)"); - assertEq(stakingReader.getStakeInfo(stakeNftId).lastUpdatedIn.toInt(), lastUpdateAt, "unexpected last updated at (before)"); + assertEq(stakingReader.getStakeInfo(stakeNftId).lastUpdatedIn.toInt(), lastUpdateIn, "unexpected last updated at (before)"); // time now Timestamp timestampNow = TimestampLib.current(); - // restake rewards + // increase stake by 0 vm.startPrank(staker); stakingService.stake(stakeNftId, AmountLib.zero()); vm.stopPrank(); // check stake/rewards balance (after calling restake) Amount expectedRestakedAmount = dipAmount + expectedRewardIncrease; - assertEq(stakingReader.getStakeInfo(stakeNftId).stakedAmount.toInt(), expectedRestakedAmount.toInt(), "unexpected stake amount (after restake)"); - assertEq(stakingReader.getStakeInfo(stakeNftId).rewardAmount.toInt(), 0, "unexpected reward amount (after restake)"); - assertEq(stakingReader.getStakeInfo(stakeNftId).lastUpdatedIn.toInt(), block.number, "unexpected last updated at (after)"); + assertEq(stakingReader.getStakeInfo(stakeNftId).stakedAmount.toInt(), dipAmount.toInt(), "unexpected stake amount (after stake 0)"); + assertEq(stakingReader.getStakeInfo(stakeNftId).rewardAmount.toInt(), expectedRewardIncrease.toInt(), "unexpected reward amount (after stake 0)"); + assertEq(stakingReader.getStakeInfo(stakeNftId).lastUpdatedIn.toInt(), block.number, "unexpected last updated at (after stake 0)"); + } - // check locked until + + function test_stakingStakeIncreaseByHundredAfterOneYear() public { + + ( + , + Amount dipAmount, + NftId stakeNftId + ) = _prepareStake(staker, instanceNftId, 1000); + + // record time at stake creation + uint256 lastUpdateAt = block.timestamp; + uint256 lastUpdateIn = block.number; + + // wait a year + _wait(SecondsLib.oneYear()); + + // check one year passed + assertEq(block.timestamp - SecondsLib.oneYear().toInt(), lastUpdateAt, "unexpected year duration"); + + // check reward calculations after one year + UFixed rewardRate = stakingReader.getTargetInfo(instanceNftId).rewardRate; + ( + Amount rewardIncrease, + Amount totalDipAmount + ) = StakingLib.calculateRewardIncrease( + stakingReader, + stakeNftId, + rewardRate); + + Amount expectedRewardIncrease = StakingLib.calculateRewardAmount( + rewardRate, + SecondsLib.oneYear(), + dipAmount); + + assertEq(expectedRewardIncrease.toInt(), 50 * 10**dip.decimals(), "unexpected 'expected' reward increase"); + assertTrue(rewardIncrease.gtz(), "reward increase zero"); + assertEq(rewardIncrease.toInt(), expectedRewardIncrease.toInt(), "unexpected rewared increase"); + + // check stake/rewards balance (before calling restake) + assertEq(stakingReader.getStakeInfo(stakeNftId).stakedAmount.toInt(), dipAmount.toInt(), "unexpected stake amount (before)"); + assertEq(stakingReader.getStakeInfo(stakeNftId).rewardAmount.toInt(), 0, "unexpected reward amount (before)"); + assertEq(stakingReader.getStakeInfo(stakeNftId).lastUpdatedIn.toInt(), lastUpdateIn, "unexpected last updated at (before)"); + + // time now Seconds lockingPeriod = stakingReader.getTargetInfo(instanceNftId).lockingPeriod; - Timestamp lockedUntilAfter = stakingReader.getStakeInfo(stakeNftId).lockedUntil; - Timestamp expectedLockedUntil = timestampNow.addSeconds(lockingPeriod); - assertEq(lockedUntilAfter.toInt(), expectedLockedUntil.toInt(), "unexpected updated lockedUntil"); + Timestamp lockedUntilBefore = stakingReader.getStakeInfo(stakeNftId).lockedUntil; + assertEq(lockedUntilBefore.toInt(), lastUpdateAt + lockingPeriod.toInt(), "unexpected updated lockedUntil (before)"); + + // increase stake by 0 + (, Amount dipAmount2) = _prepareAccount(staker, 100); + + vm.startPrank(staker); + stakingService.stake(stakeNftId, dipAmount2); + vm.stopPrank(); + + // check stake/rewards balance (after calling restake) + Amount expectedNewAmount = dipAmount + dipAmount2; + IStaking.StakeInfo memory stakeInfo = stakingReader.getStakeInfo(stakeNftId); + assertEq(stakeInfo.stakedAmount.toInt(), expectedNewAmount.toInt(), "unexpected stake amount (after stake 100)"); + assertEq(stakeInfo.rewardAmount.toInt(), expectedRewardIncrease.toInt(), "unexpected reward amount (after stake 100)"); + assertEq(stakeInfo.lastUpdatedIn.toInt(), block.number, "unexpected last updated at (after stake 100)"); + + // check locked until + Timestamp lockedUntilAfter = stakeInfo.lockedUntil; + Timestamp expectedLockedUntil = lockedUntilBefore.addSeconds(lockingPeriod); + assertEq(lockedUntilAfter.toInt(), expectedLockedUntil.toInt(), "unexpected updated lockedUntil (after stake 100)"); } @@ -368,7 +433,7 @@ contract StakingTest is GifTest { // THEN vm.expectRevert(abi.encodeWithSelector( - IStaking.ErrorStakingTargetMaxStakedAmountExceeded.selector, + StakingStore.ErrorStakingStoreStakesExceedingTargetMaxAmount.selector, instanceNftId, dipAmount, dipAmount + stakeIncreaseAmount)); @@ -436,9 +501,9 @@ contract StakingTest is GifTest { assertEq(stakingReader.getStakeInfo(stakeNftId).lastUpdatedIn.toInt(), block.number, "unexpected last updated at (after unstake)"); // check accumulated stakes/rewards on instance(after unstake) - assertEq(stakingReader.getStakeInfo(instanceNftId).stakedAmount.toInt(), 0, "unexpected instance stake amount (after unstake)"); - assertEq(stakingReader.getStakeInfo(instanceNftId).rewardAmount.toInt(), 0, "unexpected instance reward amount (after unstake)"); - assertEq(stakingReader.getStakeInfo(instanceNftId).lastUpdatedIn.toInt(), block.number, "unexpected instance last updated in (after unstake)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).stakedAmount.toInt(), 0, "unexpected instance stake amount (after unstake)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).rewardAmount.toInt(), 0, "unexpected instance reward amount (after unstake)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).lastUpdatedIn.toInt(), block.number, "unexpected instance last updated in (after unstake)"); } function test_stakingStakeUnstakeStakeLocked() public { @@ -496,9 +561,8 @@ contract StakingTest is GifTest { assertEq(stakingReader.getStakeInfo(stakeNftId).lastUpdatedIn.toInt(), lastUpdateAt, "unexpected last updated at (before unstake)"); // WHEN - vm.startPrank(staker); + vm.prank(staker); stakingService.claimRewards(stakeNftId); - vm.stopPrank(); // THEN // get and check instance reward rate @@ -522,9 +586,9 @@ contract StakingTest is GifTest { assertEq(stakingReader.getStakeInfo(stakeNftId).lastUpdatedIn.toInt(), block.number, "unexpected last updated at (after claim rewards)"); // check accumulated stakes/rewards on instance(after claim rewards) - assertEq(stakingReader.getStakeInfo(instanceNftId).stakedAmount.toInt(), stakeAmount.toInt(), "unexpected instance stake amount (after claim rewards)"); - assertEq(stakingReader.getStakeInfo(instanceNftId).rewardAmount.toInt(), 0, "unexpected instance reward amount (after claim rewards)"); - assertEq(stakingReader.getStakeInfo(instanceNftId).lastUpdatedIn.toInt(), block.number, "unexpected instance last updated in (after claim rewards)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).stakedAmount.toInt(), stakeAmount.toInt(), "unexpected instance stake amount (after claim rewards)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).rewardAmount.toInt(), 0, "unexpected instance reward amount (after claim rewards)"); + assertEq(stakingReader.getTargetInfo(instanceNftId).lastUpdatedIn.toInt(), block.number, "unexpected instance last updated in (after claim rewards)"); } @@ -559,18 +623,16 @@ contract StakingTest is GifTest { UFixed rewardRate = stakingReader.getRewardRate(instanceNftId); Amount expectedRewards = stakeAmount.multiplyWith(rewardRate); - // WHEN - + // WHEN + THEN vm.expectRevert( abi.encodeWithSelector( StakingStore.ErrorStakingStoreRewardReservesInsufficient.selector, instanceNftId, - expectedRewards, - reserveAmount)); + reserveAmount, + expectedRewards)); - vm.startPrank(staker); + vm.prank(staker); stakingService.claimRewards(stakeNftId); - vm.stopPrank(); } // solhint-disable-next-line func-name-mixedcase @@ -642,31 +704,28 @@ contract StakingTest is GifTest { // solhint-disable-next-line func-name-mixedcase function test_stakingRestakeWithRewards() public { // GIVEN - - // create a second instance - restake target - vm.startPrank(instanceOwner2); - (instance2, instanceNftId2) = instanceService.createInstance(false); - Seconds lockingPeriod2 = stakingReader.getTargetInfo(instanceNftId2).lockingPeriod; - vm.stopPrank(); - - (, Amount dipAmount) = _prepareAccount(staker, 3000); - // set reward rate vm.startPrank(instanceOwner); instance.setStakingRewardRate(UFixedLib.toUFixed(1, -1)); // 10% reward rate vm.stopPrank(); - vm.startPrank(staker); - - // create initial instance stake - NftId stakeNftId = stakingService.create( - instanceNftId, - dipAmount); + ( + , + Amount dipAmount, + NftId stakeNftId + ) = _prepareStake(staker, instanceNftId, 1000); // wait some time _wait(SecondsLib.oneYear()); - + + // create a second instance - restake target + vm.startPrank(instanceOwner2); + (instance2, instanceNftId2) = instanceService.createInstance(false); + Seconds lockingPeriod2 = stakingReader.getTargetInfo(instanceNftId2).lockingPeriod; + vm.stopPrank(); + // WHEN - restake to new target + vm.prank(staker); (NftId stakeNftId2, Amount restakedAmount) = staking.restake(stakeNftId, instanceNftId2); // THEN - check restake only created new staked and changed counters nothing else, especially no tokens moved @@ -737,15 +796,15 @@ contract StakingTest is GifTest { // WHEN - restake to target (NftId stakeNftId3, Amount restakedAmount) = staking.restake(stakeNftId, instanceNftId2); - // THEN - check restake only created new staked and changed counters nothing else, especially no tokens moved - assertTrue(stakeNftId2.gtz()); + // THEN - check restake only created new stake and changed counters nothing else, especially no tokens moved + assertTrue(stakeNftId3.gtz()); assertEq(oneThousand.toInt(), restakedAmount.toInt()); // check balances after staking - no tokens mived assertEq(dip.balanceOf(staker), 0, "staker: unexpected dip balance (after staking)"); assertEq(dip.balanceOf(staking.getWallet()), dipAmount.toInt(), "staking wallet: unexpected dip balance (after staking)"); - // check stake balance after restake + // check stake balances of individual stakes after restake assertEq(stakingReader.getStakeInfo(stakeNftId).stakedAmount.toInt(), 0, "unexpected stake amount"); assertEq(stakingReader.getStakeInfo(stakeNftId).rewardAmount.toInt(), 0, "unexpected reward amount"); assertEq(stakingReader.getStakeInfo(stakeNftId2).stakedAmount.toInt(), twoThousand.toInt(), "unexpected stake amount (2)"); @@ -773,48 +832,46 @@ contract StakingTest is GifTest { // solhint-disable-next-line func-name-mixedcase function test_stakingRestakeInvalidNftType() public { // GIVEN + (, Amount dipAmount) = _prepareAccount(staker, 3000); - // create a second instance - restake target - vm.startPrank(instanceOwner2); - (instance2, instanceNftId2) = instanceService.createInstance(false); - Seconds lockingPeriod2 = stakingReader.getTargetInfo(instanceNftId2).lockingPeriod; - vm.stopPrank(); - - _prepareAccount(staker, 3000); - - vm.startPrank(instanceOwner); - - // THEN + // WHEN + THEN - attempt to stake against registry vm.expectRevert(abi.encodeWithSelector( - INftOwnable.ErrorNftOwnableInvalidType.selector, - instanceNftId, - 30)); + IRegistry.ErrorRegistryTypeCombinationInvalid.selector, + address(0), 30, 2)); // stake must not have registry as its parent - // WHEN - restake to new target - staking.restake(instanceNftId, instanceNftId2); + vm.startPrank(staker); + stakingService.create( + registryNftId, + dipAmount); } + // solhint-disable-next-line func-name-mixedcase function test_stakingRestakeNotOwner() public { // GIVEN + ( + TokenHandler tokenHandler, + Amount dipAmount, + NftId stakeNftId + ) = _prepareStake(staker, instanceNftId, 1000); + // create a second instance - restake target vm.startPrank(instanceOwner2); (instance2, instanceNftId2) = instanceService.createInstance(false); - Seconds lockingPeriod2 = stakingReader.getTargetInfo(instanceNftId2).lockingPeriod; + Seconds lockingPeriod = stakingReader.getTargetInfo(instanceNftId).lockingPeriod; vm.stopPrank(); - _prepareAccount(staker, 3000); - - vm.startPrank(staker); - // THEN vm.expectRevert(abi.encodeWithSelector( - INftOwnable.ErrorNftOwnableNotOwner.selector, - staker)); + IStaking.ErrorStakingNotStakeOwner.selector, + stakeNftId, + staker, + outsider)); // WHEN - restake to new target - staking.restake(instanceNftId, instanceNftId2); + vm.prank(outsider); + staking.restake(stakeNftId, instanceNftId2); } @@ -845,7 +902,6 @@ contract StakingTest is GifTest { // WHEN - restake to new target staking.restake(stakeNftId, instanceNftId2); -console.log("f"); } /// @dev test restaking and exceeding the max staked amount @@ -906,17 +962,20 @@ console.log("f"); NftId stakeNftId = stakingService.create( instanceNftId, dipAmount); - NftId targetNftId = NftIdLib.zero(); - // THEN + // and wait until restaking is allowed + vm.warp(block.timestamp + lockingPeriod.toInt()); + + // WHEN + THEN - restake to invalid target + NftId invalidTargetNftId = NftIdLib.toNftId(123); vm.expectRevert(abi.encodeWithSelector( - IStakingService.ErrorStakingServiceTargetUnknown.selector, - targetNftId)); + IStaking.ErrorStakingNotTarget.selector, + invalidTargetNftId)); - // WHEN - restake to new target - staking.restake(stakeNftId, targetNftId); + staking.restake(stakeNftId, invalidTargetNftId); } - + + function _addRewardReserves( NftId, address instanceOwner, diff --git a/test/staking/StakingOwner.t.sol b/test/staking/StakingOwner.t.sol index f16588d4b..6a6f0cd57 100644 --- a/test/staking/StakingOwner.t.sol +++ b/test/staking/StakingOwner.t.sol @@ -12,8 +12,8 @@ import {IStaking} from "../../contracts/staking/IStaking.sol"; import {IStakingService} from "../../contracts/staking/IStakingService.sol"; import {Amount, AmountLib} from "../../contracts/type/Amount.sol"; -import {BlocknumberLib} from "../../contracts/type/Blocknumber.sol"; -import {ChainIdLib} from "../../contracts/type/ChainId.sol"; +import {Blocknumber, BlocknumberLib} from "../../contracts/type/Blocknumber.sol"; +import {ChainId, ChainIdLib} from "../../contracts/type/ChainId.sol"; import {GifTest} from "../base/GifTest.sol"; import {NftId, NftIdLib} from "../../contracts/type/NftId.sol"; import {PROTOCOL, STAKE, STAKING} from "../../contracts/type/ObjectType.sol"; @@ -25,6 +25,7 @@ import {TargetManagerLib} from "../../contracts/staking/TargetManagerLib.sol"; import {Timestamp, TimestampLib} from "../../contracts/type/Timestamp.sol"; import {TokenHandler} from "../../contracts/shared/TokenHandler.sol"; import {UFixed, UFixedLib} from "../../contracts/type/UFixed.sol"; +import {VersionPart} from "../../contracts/type/Version.sol"; contract StakingOwnerTest is GifTest { @@ -160,12 +161,14 @@ contract StakingOwnerTest is GifTest { assertTrue(newProtocolLockingPeriod > protocolLockingPeriod, "new locking period not greater than default locking period"); // WHEN + NftId protocolNftId = registry.getProtocolNftId(); + Blocknumber currentBlock = BlocknumberLib.current(); vm.expectEmit(address(staking)); emit IStaking.LogStakingProtocolLockingPeriodSet( - registry.getProtocolNftId(), + protocolNftId, newProtocolLockingPeriod, protocolLockingPeriod, - BlocknumberLib.current()); + currentBlock); vm.startPrank(stakingOwner); staking.setProtocolLockingPeriod(newProtocolLockingPeriod); @@ -182,12 +185,14 @@ contract StakingOwnerTest is GifTest { assertTrue(newProtocolRewardRate > protocolRewardRate, "new reward rate not greater than default reward rate"); // WHEN + NftId protocolNftId = registry.getProtocolNftId(); + Blocknumber currentBlock = BlocknumberLib.current(); vm.expectEmit(address(staking)); emit IStaking.LogStakingProtocolRewardRateSet( - registry.getProtocolNftId(), + protocolNftId, newProtocolRewardRate, protocolRewardRate, - BlocknumberLib.current()); + currentBlock); vm.startPrank(stakingOwner); staking.setProtocolRewardRate(newProtocolRewardRate); @@ -225,11 +230,12 @@ contract StakingOwnerTest is GifTest { staking.setProtocolRewardRate(newProtocolRewardRate); // WHEN + THEN attempt to set staking rate + ChainId currentChainId = ChainIdLib.current(); vm.expectRevert( abi.encodeWithSelector( INftOwnable.ErrorNftOwnableNotOwner.selector, outsider)); - staking.setStakingRate(ChainIdLib.current(), address(token), newTokenStakingRate); + staking.setStakingRate(currentChainId, address(token), newTokenStakingRate); // WHEN + THEN attempt to set staking reader StakingReader newStakingReader = new StakingReader(registry); @@ -239,6 +245,14 @@ contract StakingOwnerTest is GifTest { staking.setStakingReader(newStakingReader); + // WHEN + THEN attempt to set staking servie + VersionPart currentRelase = registry.getLatestRelease(); + vm.expectRevert( + abi.encodeWithSelector( + INftOwnable.ErrorNftOwnableNotOwner.selector, outsider)); + + staking.setStakingService(currentRelase); + // WHEN + THEN attempt to approve token handler Amount newApproveAmount = AmountLib.toAmount(100); vm.expectRevert( diff --git a/test/staking/TvlCalculation.t.sol b/test/staking/TvlCalculation.t.sol index 8bf9244b1..8a5e1ad28 100644 --- a/test/staking/TvlCalculation.t.sol +++ b/test/staking/TvlCalculation.t.sol @@ -21,8 +21,8 @@ import {UFixed, UFixedLib} from "../../contracts/type/UFixed.sol";import {Versio contract TvlCalculation is GifTest { // TODO find better ways than to copy paste events from StakingStore contract - event LogStakingStoreTotalValueLockedIncreased(NftId targetNftId, address token, Amount amount, Amount newBalance, Blocknumber lastUpdatedIn); - event LogStakingStoreTotalValueLockedDecreased(NftId targetNftId, address token, Amount amount, Amount newBalance, Blocknumber lastUpdatedIn); + event LogStakingTotalValueLockedIncreased(NftId targetNftId, address token, Amount amount, Amount newBalance); + event LogStakingTotalValueLockedDecreased(NftId targetNftId, address token, Amount amount, Amount newBalance); uint256 public constant BUNDLE_CAPITAL = 20000; uint256 public constant SUM_INSURED = 1000; @@ -36,6 +36,31 @@ contract TvlCalculation is GifTest { UFixed public stakingRate; + function setUp() public override { + super.setUp(); + + _prepareProduct(); + + // create risk + vm.startPrank(productOwner); + riskId = product.createRisk("Risk_1", ""); + vm.stopPrank(); + + // fund customer + _fundAccount(customer, CUSTOMER_FUNDS * 10 ** token.decimals()); + + tokenAddress = address(token); + stakingStoreAddress = address(staking.getStakingStore()); + + // for every usdc token 10 dip tokens must be staked + stakingRate = UFixedLib.toUFixed(1, int8(dip.decimals() - token.decimals() + 1)); + + vm.startPrank(stakingOwner); + staking.setStakingRate(ChainIdLib.current(), tokenAddress, stakingRate); + vm.stopPrank(); + } + + function test_stakingTvlCalculationInitialState() public { // check instance is active target @@ -99,13 +124,12 @@ contract TvlCalculation is GifTest { // check tvl log entry from staking Blocknumber currentBlocknumber = BlocknumberLib.current(); Timestamp currentTimestamp = TimestampLib.current(); - vm.expectEmit(stakingStoreAddress); - emit LogStakingStoreTotalValueLockedIncreased( + vm.expectEmit(address(staking)); + emit LogStakingTotalValueLockedIncreased( instanceNftId, tokenAddress, sumInsuredAmount, // amount - sumInsuredAmount, // new balance - currentBlocknumber); + sumInsuredAmount); // new balance // collateralize application _collateralize(policyNftId, false, currentTimestamp); @@ -169,13 +193,12 @@ contract TvlCalculation is GifTest { // check tvl decrease log emission Blocknumber currentBlocknumber = BlocknumberLib.current(); Amount zeroAmount = AmountLib.zero(); - vm.expectEmit(stakingStoreAddress); - emit LogStakingStoreTotalValueLockedDecreased( + vm.expectEmit(address(staking)); + emit LogStakingTotalValueLockedDecreased( instanceNftId, tokenAddress, sumInsuredAmount, // amount - zeroAmount, // new balance - currentBlocknumber); + zeroAmount); // new balance _closePolicy(policyNftId); @@ -304,13 +327,12 @@ contract TvlCalculation is GifTest { // WHEN processing the payout (includes actual payout token flow) Blocknumber currentBlocknumber = BlocknumberLib.current(); Amount newBalanceAmount = sumInsuredAmount - claimAmount; - vm.expectEmit(stakingStoreAddress); - emit LogStakingStoreTotalValueLockedDecreased( + vm.expectEmit(address(staking)); + emit LogStakingTotalValueLockedDecreased( instanceNftId, tokenAddress, claimAmount, // amount - newBalanceAmount, // new balance - currentBlocknumber); + newBalanceAmount); // new balance product.processPayout(policyNftId, payoutId); @@ -331,30 +353,6 @@ contract TvlCalculation is GifTest { } - function setUp() public override { - super.setUp(); - - _prepareProduct(); - - // create risk - vm.startPrank(productOwner); - riskId = product.createRisk("Risk_1", ""); - vm.stopPrank(); - - // fund customer - _fundAccount(customer, CUSTOMER_FUNDS * 10 ** token.decimals()); - - tokenAddress = address(token); - stakingStoreAddress = address(staking.getStakingStore()); - - // for every usdc token 10 dip tokens must be staked - stakingRate = UFixedLib.toUFixed(1, int8(dip.decimals() - token.decimals() + 1)); - - vm.startPrank(stakingOwner); - staking.setStakingRate(ChainIdLib.current(), tokenAddress, stakingRate); - vm.stopPrank(); - } - function _createPayout( NftId nftId, // policy nft id