Skip to content

Commit

Permalink
Merge branch 'dev' into NAY4_code_conciseness
Browse files Browse the repository at this point in the history
  • Loading branch information
amarinkovic authored Oct 3, 2024
2 parents 956dd1b + 93fe561 commit 5f86a9f
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 64 deletions.
7 changes: 0 additions & 7 deletions docs/src/src/libs/LibAdmin.sol/library.LibAdmin.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,6 @@
function _getSystemId() internal pure returns (bytes32);
```

### _getEmptyId


```solidity
function _getEmptyId() internal pure returns (bytes32);
```

### _updateMaxDividendDenominations


Expand Down
14 changes: 0 additions & 14 deletions docs/src/src/libs/LibHelpers.sol/library.LibHelpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ Pure functions


## Functions
### _getIdForObjectAtIndex


```solidity
function _getIdForObjectAtIndex(uint256 _index) internal pure returns (bytes32);
```

### _getIdForAddress


Expand All @@ -33,13 +26,6 @@ function _getSenderId() internal view returns (bytes32);
function _checkBottom12BytesAreEmpty(bytes32 value) internal pure returns (bool);
```

### _checkUpper12BytesAreEmpty


```solidity
function _checkUpper12BytesAreEmpty(bytes32 value) internal pure returns (bool);
```

### _getAddressFromId


Expand Down
13 changes: 0 additions & 13 deletions docs/src/src/shared/FreeStructs.sol/struct.StakingCheckpoint.md

This file was deleted.

11 changes: 11 additions & 0 deletions src/facets/StakingFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ contract StakingFacet is Modifiers {
LibTokenizedVaultStaking._collectRewards(parentId, _entityId, lastPaid);
}

/**
* @notice Collect rewards for a staker
* @param _entityId staking entity ID
* @param _interval interval to collect rewards up to
*/
function collectRewardsToInterval(bytes32 _entityId, uint64 _interval) external notLocked {
bytes32 parentId = LibObject._getParent(msg.sender._getIdForAddress());

LibTokenizedVaultStaking._collectRewards(parentId, _entityId, _interval);
}

function payReward(bytes32 _stakingRewardId, bytes32 _entityId, bytes32 _rewardTokenId, uint256 _amount) external notLocked assertPrivilege(_entityId, LC.GROUP_ENTITY_ADMINS) {
LibTokenizedVaultStaking._payReward(_stakingRewardId, _entityId, _rewardTokenId, _amount);
}
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/IERC1271.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC1271 standard signature validation method for
* contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
* @dev We use this interface to generate java bindings for solidity contracts.
*/
interface IERC1271 {
/**
Expand Down
8 changes: 6 additions & 2 deletions src/libs/LibAdmin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,13 @@ library LibAdmin {
s.locked[IDiamondProxy.stake.selector] = true;
s.locked[IDiamondProxy.unstake.selector] = true;
s.locked[IDiamondProxy.collectRewards.selector] = true;
s.locked[IDiamondProxy.collectRewardsToInterval.selector] = true;
s.locked[IDiamondProxy.payReward.selector] = true;
s.locked[IDiamondProxy.cancelSimplePolicy.selector] = true;
s.locked[IDiamondProxy.createSimplePolicy.selector] = true;
s.locked[IDiamondProxy.createEntity.selector] = true;

bytes4[] memory lockedFunctions = new bytes4[](21);
bytes4[] memory lockedFunctions = new bytes4[](22);
lockedFunctions[0] = IDiamondProxy.startTokenSale.selector;
lockedFunctions[1] = IDiamondProxy.paySimpleClaim.selector;
lockedFunctions[2] = IDiamondProxy.paySimplePremium.selector;
Expand All @@ -168,6 +169,7 @@ library LibAdmin {
lockedFunctions[18] = IDiamondProxy.cancelSimplePolicy.selector;
lockedFunctions[19] = IDiamondProxy.createSimplePolicy.selector;
lockedFunctions[20] = IDiamondProxy.createEntity.selector;
lockedFunctions[21] = IDiamondProxy.collectRewardsToInterval.selector;

emit FunctionsLocked(lockedFunctions);
}
Expand Down Expand Up @@ -195,8 +197,9 @@ library LibAdmin {
s.locked[IDiamondProxy.cancelSimplePolicy.selector] = false;
s.locked[IDiamondProxy.createSimplePolicy.selector] = false;
s.locked[IDiamondProxy.createEntity.selector] = false;
s.locked[IDiamondProxy.collectRewardsToInterval.selector] = false;

bytes4[] memory lockedFunctions = new bytes4[](21);
bytes4[] memory lockedFunctions = new bytes4[](22);
lockedFunctions[0] = IDiamondProxy.startTokenSale.selector;
lockedFunctions[1] = IDiamondProxy.paySimpleClaim.selector;
lockedFunctions[2] = IDiamondProxy.paySimplePremium.selector;
Expand All @@ -218,6 +221,7 @@ library LibAdmin {
lockedFunctions[18] = IDiamondProxy.cancelSimplePolicy.selector;
lockedFunctions[19] = IDiamondProxy.createSimplePolicy.selector;
lockedFunctions[20] = IDiamondProxy.createEntity.selector;
lockedFunctions[21] = IDiamondProxy.collectRewardsToInterval.selector;

emit FunctionsUnlocked(lockedFunctions);
}
Expand Down
64 changes: 38 additions & 26 deletions src/libs/LibTokenizedVaultStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ library LibTokenizedVaultStaking {

/**
* @dev First 4 bytes: "VTOK", next 8 bytes: interval, next 20 bytes: right 20 bytes of tokenId
* @param _entityId The ID of the entity.
* @param _tokenId The internal ID of the token.
* @param _interval The interval of staking.
*/
Expand Down Expand Up @@ -49,9 +50,16 @@ library LibTokenizedVaultStaking {
emit TokenStakingStarted(_entityId, _config.tokenId, _config.initDate, _config.a, _config.r, _config.divider, _config.interval);
}

/**
* @notice Checks if staking has been initialized for the given entity.
* @dev Staking is considered initialized if the initDate is set and the current timestamp is
* equal to or after the initDate.
* @param _entityId The ID of the entity to check staking initialization.
* @return bool indicating whether staking is initialized.
*/
function _isStakingInitialized(bytes32 _entityId) internal view returns (bool) {
AppStorage storage s = LibAppStorage.diamondStorage();
return (s.stakingConfigs[_entityId].initDate > 0 && s.stakingConfigs[_entityId].initDate < block.timestamp);
return (s.stakingConfigs[_entityId].initDate > 0 && s.stakingConfigs[_entityId].initDate <= block.timestamp);
}

function _stakingConfig(bytes32 _entityId) internal view returns (StakingConfig memory) {
Expand All @@ -78,7 +86,14 @@ library LibTokenizedVaultStaking {
AppStorage storage s = LibAppStorage.diamondStorage();
return s.stakeCollected[_entityId][_entityId];
}

/**
* @notice Pays rewards to a staker.
* @dev Rewards can be paid if the current timestamp is equal to or after the staking initDate.
* @param _stakingRewardId The ID for the staking reward.
* @param _entityId The ID of the entity whose rewards are being paid.
* @param _rewardTokenId The ID of the reward token.
* @param _rewardAmount The amount of reward to be paid.
*/
function _payReward(bytes32 _stakingRewardId, bytes32 _entityId, bytes32 _rewardTokenId, uint256 _rewardAmount) internal {
AppStorage storage s = LibAppStorage.diamondStorage();

Expand Down Expand Up @@ -110,7 +125,7 @@ library LibTokenizedVaultStaking {
s.stakeBalance[vTokenId][_entityId] = stakingState.balance;
s.stakeBoost[vTokenId][_entityId] = stakingState.boost;

// Update last colleted interval for the token itself
// Update last collected interval for the token itself
s.stakeCollected[_entityId][_entityId] = interval;

// Transfer the funds
Expand All @@ -128,7 +143,8 @@ library LibTokenizedVaultStaking {

bytes32 tokenId = s.stakingConfigs[_entityId].tokenId;

if (_amount < s.objectMinimumSell[tokenId]) revert InvalidStakingAmount();
// Prevent staking below or equal to the minimum required
if (_amount <= s.objectMinimumSell[tokenId]) revert InvalidStakingAmount();

uint64 currentInterval = _currentInterval(_entityId);
bytes32 vTokenIdMax = _vTokenIdBucket(_entityId, tokenId);
Expand All @@ -152,18 +168,25 @@ library LibTokenizedVaultStaking {

uint256 boost1 = ((((_getD(_entityId) - ratio) * _amount) / _getD(_entityId)) * _getA(_entityId)) / _getD(_entityId);
uint256 boost2 = (((ratio * _amount) / _getD(_entityId)) * _getA(_entityId)) / _getD(_entityId);

uint256 balance1 = _amount - (ratio * _amount) / _getD(_entityId);
uint256 balance2 = (ratio * _amount) / _getD(_entityId);

s.stakeBalance[_vTokenId(_entityId, tokenId, currentInterval + 1)][_stakerId] += balance1 + boost1;
s.stakeBalance[_vTokenId(_entityId, tokenId, currentInterval + 1)][_entityId] += balance1 + boost1;

s.stakeBalanceAdded[_vTokenId(_entityId, tokenId, currentInterval + 1)][_stakerId] += balance1;
s.stakeBalanceAdded[_vTokenId(_entityId, tokenId, currentInterval + 1)][_entityId] += balance1;

s.stakeBoost[_vTokenId(_entityId, tokenId, currentInterval + 1)][_stakerId] += (boost1 * _getR(_entityId)) / _getD(_entityId) + boost2;
s.stakeBoost[_vTokenId(_entityId, tokenId, currentInterval + 1)][_entityId] += (boost1 * _getR(_entityId)) / _getD(_entityId) + boost2;

s.stakeBalance[_vTokenId(_entityId, tokenId, currentInterval + 2)][_stakerId] += balance2;
s.stakeBalance[_vTokenId(_entityId, tokenId, currentInterval + 2)][_entityId] += balance2;

s.stakeBalanceAdded[_vTokenId(_entityId, tokenId, currentInterval + 2)][_stakerId] += balance2;
s.stakeBalanceAdded[_vTokenId(_entityId, tokenId, currentInterval + 2)][_entityId] += balance2;

emit TokenStaked(_stakerId, _entityId, tokenId, _amount);
}

Expand Down Expand Up @@ -257,7 +280,7 @@ library LibTokenizedVaultStaking {
uint256 totalDistributionAmount = s.stakingDistributionAmount[vTokenId_i];
if (totalDistributionAmount > 0) {
uint256 currencyIndex;
(rewards, currencyIndex) = addUniqueValue(rewards, s.stakingDistributionDenomination[vTokenId_i]);
(rewards, currencyIndex) = _addUniqueValue(rewards, s.stakingDistributionDenomination[vTokenId_i]);

// Use the same math as dividend distributions, assuming zero has already been collected
uint256 userDistributionAmount = LibTokenizedVault._getWithdrawableDividendAndDeductionMath(
Expand Down Expand Up @@ -348,7 +371,7 @@ library LibTokenizedVaultStaking {
return s.stakingConfigs[_entityId].divider;
}

function addUniqueValue(RewardsBalances memory rewards, bytes32 newValue) internal pure returns (RewardsBalances memory, uint256) {
function _addUniqueValue(RewardsBalances memory rewards, bytes32 newValue) internal pure returns (RewardsBalances memory, uint256) {
require(rewards.currencies.length == rewards.amounts.length, "Different array lengths!");

uint256 length = rewards.currencies.length;
Expand All @@ -361,7 +384,7 @@ library LibTokenizedVaultStaking {
// prettier-ignore
RewardsBalances memory rewards_ = RewardsBalances({
currencies: new bytes32[](length + 1),
amounts: new uint256[](rewards.amounts.length + 1),
amounts: new uint256[](length + 1),
lastPaidInterval: 0
});

Expand All @@ -378,7 +401,7 @@ library LibTokenizedVaultStaking {

/**
* @dev Get the starting time of a given interval
* @param _entityId The internal ID of the token
* @param _entityId The internal ID of the entity
* @param _interval The interval to get the time for
*/
function _calculateStartTimeOfInterval(bytes32 _entityId, uint64 _interval) internal view returns (uint64 intervalTime_) {
Expand All @@ -390,36 +413,25 @@ library LibTokenizedVaultStaking {
intervalTime_ = _calculateStartTimeOfInterval(_entityId, _currentInterval(_entityId));
}

function _stakedAmount(bytes32 _stakerId, bytes32 _entityId) internal view returns (uint256) {
function _getStakingAmounts(bytes32 _stakerId, bytes32 _entityId) internal view returns (uint256 stakedBalance_, uint256 boostedBalance_) {
AppStorage storage s = LibAppStorage.diamondStorage();

bytes32 tokenId = s.stakingConfigs[_entityId].tokenId;
bytes32 vTokenIdMax = _vTokenIdBucket(_entityId, tokenId);

return s.stakeBalance[vTokenIdMax][_stakerId];
}

function _getStakingAmounts(bytes32 _stakerId, bytes32 _entityId) internal view returns (uint256 stakedBalance_, uint256 boostedBalance_) {
uint64 currentInterval = _currentInterval(_entityId);
bytes32 tokenId = s.stakingConfigs[_entityId].tokenId;

stakedBalance_ = _stakedAmount(_stakerId, _entityId);
stakedBalance_ = s.stakeBalance[_vTokenIdBucket(_entityId, tokenId)][_stakerId];

if (!_isStakingInitialized(_entityId)) {
// boost is always 1 before init
return (stakedBalance_, boostedBalance_);
}

(StakingState memory state, ) = _getStakingStateWithRewardsBalances(_stakerId, _entityId, currentInterval + 2);
(StakingState memory state, ) = _getStakingStateWithRewardsBalances(_stakerId, _entityId, currentInterval);

uint256 boostPrevious = state.boost;
uint256 balancePrevious = state.balance;

for (uint i = 0; i < 2; i++) {
boostPrevious = (boostPrevious * _getD(_entityId)) / _getR(_entityId);
balancePrevious = balancePrevious - boostPrevious;
}
uint256 balance1 = s.stakeBalanceAdded[_vTokenId(_entityId, tokenId, currentInterval + 1)][_stakerId];
uint256 balance2 = s.stakeBalanceAdded[_vTokenId(_entityId, tokenId, currentInterval + 2)][_stakerId];

boostedBalance_ = balancePrevious;
boostedBalance_ = state.balance + balance1 + balance2;

if (boostedBalance_ < stakedBalance_) {
boostedBalance_ = stakedBalance_;
Expand Down
17 changes: 16 additions & 1 deletion src/shared/AppStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,29 @@ struct AppStorage {
mapping(address userAddress => EntityApproval) selfOnboarding; // map address => { entityId, roleId }
/// Staking
mapping(bytes32 entityId => StakingConfig) stakingConfigs; // StakingConfig for an entity
mapping(bytes32 vTokenId => mapping(bytes32 stakerId => uint256 balance)) stakeBalance; // [vTokenId][ownerId] boost at interval
mapping(bytes32 vTokenId => mapping(bytes32 stakerId => uint256 balance)) stakeBalance; // [vTokenId][ownerId] balance at interval
mapping(bytes32 vTokenId => mapping(bytes32 stakerId => uint256 boost)) stakeBoost; // [vTokenId][ownerId] boost at interval
mapping(bytes32 entityId => mapping(bytes32 stakerId => uint64 interval)) stakeCollected; // last interval reward was collected or pain for a staker in staking entity
mapping(bytes32 vTokenId => uint256 amount) stakingDistributionAmount; // [vTokenId] Reward at interval
mapping(bytes32 vTokenId => bytes32 denomination) stakingDistributionDenomination; // [vTokenId] Reward currency
mapping(bytes32 entityId => mapping(bytes32 stakerId => uint64 interval)) stakingSynced; // last interval when data was synced into storage for staker
mapping(bytes32 vTokenId => mapping(bytes32 stakerId => uint256 balance)) stakeBalanceAdded; // raw balance staked at an interval, withouth any boost included, only for reading future intervals (to calculate the total boosted balance)
}

/// Staking-Related Mappings

/// | Mapping Name | Key Structure | Value Type | Description |
/// |-----------------------------------|----------------------------------------|------------|-------------------------------------------------------------------------------------------------------------|
/// | `stakeCollected` | `[Entity ID][Staker ID]` | `uint64` | Records the last timestamp a staker collected their stake. |
/// | `stakeCollected` | `[Entity ID][Entity ID]` | `uint64` | Records the last timestamp an entity paid out rewards. |
/// | `stakeBalance` | `[vTokenId][Account ID]` | `uint256` | Tracks staked balances for accounts across different intervals. |
/// | `stakeBoost` | `[vTokenId][Account ID]` | `uint256` | Tracks boosted staked balances for accounts. |
/// | `stakeBalanceAdded` | `[vTokenId][Staker ID]` | `uint256` | Raw balance staked at an interval without any boost, used for future interval calculations. |
/// | `stakingDistributionAmount` | `[vTokenId]` | `uint256` | Stores the reward amount for each `vTokenId` at each interval. |
/// | `stakingDistributionDenomination` | `[vTokenId]` | `bytes32` | Stores the reward currency (`denomination`) for each `vTokenId` at each interval. |
/// | `stakingSynced` | `[Entity ID][Staker ID]` | `uint64` | Records the last interval when data was synced into storage for a staker. |
/// | `objectMinimumSell` | `[Token ID][Entity ID]` | `uint256` | Sets minimum staking and reward amounts for tokens per entity. |

struct FunctionLockedStorage {
mapping(bytes4 => bool) locked; // function selector => is locked?
}
Expand Down
7 changes: 6 additions & 1 deletion test/T02Admin.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ contract T02AdminTest is D03ProtocolDefaults, MockAccounts {
assertEq(entries[0].topics[0], keccak256("FunctionsLocked(bytes4[])"));
(s_functionSelectors) = abi.decode(entries[0].data, (bytes4[]));

bytes4[] memory lockedFunctions = new bytes4[](21);
bytes4[] memory lockedFunctions = new bytes4[](22);
lockedFunctions[0] = IDiamondProxy.startTokenSale.selector;
lockedFunctions[1] = IDiamondProxy.paySimpleClaim.selector;
lockedFunctions[2] = IDiamondProxy.paySimplePremium.selector;
Expand All @@ -263,6 +263,7 @@ contract T02AdminTest is D03ProtocolDefaults, MockAccounts {
lockedFunctions[18] = IDiamondProxy.cancelSimplePolicy.selector;
lockedFunctions[19] = IDiamondProxy.createSimplePolicy.selector;
lockedFunctions[20] = IDiamondProxy.createEntity.selector;
lockedFunctions[21] = IDiamondProxy.collectRewardsToInterval.selector;

for (uint256 i = 0; i < lockedFunctions.length; i++) {
assertTrue(nayms.isFunctionLocked(lockedFunctions[i]));
Expand Down Expand Up @@ -322,6 +323,9 @@ contract T02AdminTest is D03ProtocolDefaults, MockAccounts {
vm.expectRevert("function is locked");
nayms.collectRewards(bytes32(0));

vm.expectRevert("function is locked");
nayms.collectRewardsToInterval(bytes32(0), 5);

vm.expectRevert("function is locked");
nayms.cancelSimplePolicy(bytes32(0));

Expand Down Expand Up @@ -354,6 +358,7 @@ contract T02AdminTest is D03ProtocolDefaults, MockAccounts {
assertFalse(nayms.isFunctionLocked(IDiamondProxy.unstake.selector), "function unstake locked");
assertFalse(nayms.isFunctionLocked(IDiamondProxy.payReward.selector), "function payReward locked");
assertFalse(nayms.isFunctionLocked(IDiamondProxy.collectRewards.selector), "function collectRewards locked");
assertFalse(nayms.isFunctionLocked(IDiamondProxy.collectRewardsToInterval.selector), "function collectRewardsToInterval locked");
assertFalse(nayms.isFunctionLocked(IDiamondProxy.cancelSimplePolicy.selector), "function cancelSimplePolicy locked");
assertFalse(nayms.isFunctionLocked(IDiamondProxy.createSimplePolicy.selector), "function createSimplePolicy locked");
assertFalse(nayms.isFunctionLocked(IDiamondProxy.createEntity.selector), "function createEntity locked");
Expand Down
Loading

0 comments on commit 5f86a9f

Please sign in to comment.