Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lock utilised capacity #39

Merged
merged 15 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/facets/ITokenizedVaultFacet.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,23 @@ Transfer dividends to the entity
|`guid` | bytes32 | Globally unique identifier of a dividend distribution.
|`amount` | uint256 | the mamount of the dividend token to be distributed to NAYMS token holders.|
<br></br>
### getLockedBalance
Get the amount of tokens that an entity has for sale in the marketplace.
```solidity
function getLockedBalance(
bytes32 _entityId,
bytes32 _tokenId
) external returns (uint256 amount)
```
#### Arguments:
| Argument | Type | Description |
| --- | --- | --- |
|`_entityId` | bytes32 | Unique platform ID of the entity.
|`_tokenId` | bytes32 | The ID assigned to an external token.
|
<br></br>
#### Returns:
| Type | Description |
| --- | --- |
|`amount` | of tokens that the entity has for sale in the marketplace.|
<br></br>
20 changes: 0 additions & 20 deletions docs/facets/IUserFacet.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,3 @@ Gets the entity related to the user
| --- | --- |
|`entityId` | Unique platform ID of the entity|
<br></br>
### getBalanceOfTokensForSale
Get the amount of tokens that an entity has for sale in the marketplace.
```solidity
function getBalanceOfTokensForSale(
bytes32 _entityId,
bytes32 _tokenId
) external returns (uint256 amount)
```
#### Arguments:
| Argument | Type | Description |
| --- | --- | --- |
|`_entityId` | bytes32 | Unique platform ID of the entity.
|`_tokenId` | bytes32 | The ID assigned to an external token.
|
<br></br>
#### Returns:
| Type | Description |
| --- | --- |
|`amount` | of tokens that the entity has for sale in the marketplace.|
<br></br>
2 changes: 1 addition & 1 deletion src/diamonds/nayms/AppStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ struct AppStorage {
uint16 premiumCommissionSTMBP;
// A policy can pay out additional commissions on premiums to entities having a variety of roles on the policy

mapping(bytes32 => mapping(bytes32 => uint256)) marketLockedBalances; // to keep track of an owner's tokens that are on sale in the marketplace, ownerId => lockedTokenId => amount
mapping(bytes32 => mapping(bytes32 => uint256)) lockedBalances; // keep track of token balance that is locked, ownerId => tokenId => lockedAmount
}

library LibAppStorage {
Expand Down
12 changes: 12 additions & 0 deletions src/diamonds/nayms/facets/TokenizedVaultFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Modifiers } from "../Modifiers.sol";
import { LibConstants } from "../libs/LibConstants.sol";
import { LibHelpers } from "../libs/LibHelpers.sol";
import { LibTokenizedVault } from "../libs/LibTokenizedVault.sol";
import { LibACL } from "../libs/LibACL.sol";
import { LibObject } from "../libs/LibObject.sol";
import { LibEntity } from "../libs/LibEntity.sol";
import { ReentrancyGuard } from "../../../utils/ReentrancyGuard.sol";
Expand Down Expand Up @@ -124,8 +125,19 @@ contract TokenizedVaultFacet is Modifiers, ReentrancyGuard {
bytes32 entityId = LibObject._getParent(senderId);
bytes32 dividendTokenId = LibEntity._getEntityInfo(entityId).assetId;

require(LibACL._isInGroup(senderId, entityId, LibHelpers._stringToBytes32(LibConstants.GROUP_ENTITY_ADMINS)), "payDividendFromEntity: not the entity's admin");
require(LibTokenizedVault._internalBalanceOf(entityId, dividendTokenId) >= amount, "payDividendFromEntity: insufficient balance");

LibTokenizedVault._payDividend(guid, entityId, entityId, dividendTokenId, amount);
}

/**
* @notice Get the amount of tokens that an entity has for sale in the marketplace.
* @param _entityId Unique platform ID of the entity.
* @param _tokenId The ID assigned to an external token.
* @return amount of tokens that the entity has for sale in the marketplace.
*/
function getLockedBalance(bytes32 _entityId, bytes32 _tokenId) external view returns (uint256 amount) {
amount = LibTokenizedVault._getLockedBalance(_entityId, _tokenId);
}
}
10 changes: 0 additions & 10 deletions src/diamonds/nayms/facets/UserFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,4 @@ contract UserFacet is Modifiers {
function getEntity(bytes32 _userId) external view returns (bytes32 entityId) {
entityId = LibObject._getParent(_userId);
}

/**
* @notice Get the amount of tokens that an entity has for sale in the marketplace.
* @param _entityId Unique platform ID of the entity.
* @param _tokenId The ID assigned to an external token.
* @return amount of tokens that the entity has for sale in the marketplace.
*/
function getBalanceOfTokensForSale(bytes32 _entityId, bytes32 _tokenId) external view returns (uint256 amount) {
amount = LibMarket._getBalanceOfTokensForSale(_entityId, _tokenId);
}
}
8 changes: 8 additions & 0 deletions src/diamonds/nayms/interfaces/ITokenizedVaultFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,12 @@ interface ITokenizedVaultFacet {
* @param amount the mamount of the dividend token to be distributed to NAYMS token holders.
*/
function payDividendFromEntity(bytes32 guid, uint256 amount) external;

/**
* @notice Get the amount of tokens that an entity has for sale in the marketplace.
* @param _entityId Unique platform ID of the entity.
* @param _tokenId The ID assigned to an external token.
* @return amount of tokens that the entity has for sale in the marketplace.
*/
function getLockedBalance(bytes32 _entityId, bytes32 _tokenId) external view returns (uint256 amount);
}
8 changes: 0 additions & 8 deletions src/diamonds/nayms/interfaces/IUserFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,4 @@ interface IUserFacet {
* @return entityId Unique platform ID of the entity
*/
function getEntity(bytes32 _userId) external view returns (bytes32 entityId);

/**
* @notice Get the amount of tokens that an entity has for sale in the marketplace.
* @param _entityId Unique platform ID of the entity.
* @param _tokenId The ID assigned to an external token.
* @return amount of tokens that the entity has for sale in the marketplace.
*/
function getBalanceOfTokensForSale(bytes32 _entityId, bytes32 _tokenId) external view returns (uint256 amount);
}
37 changes: 25 additions & 12 deletions src/diamonds/nayms/libs/LibEntity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ library LibEntity {
/**
* @dev If an entity passes their checks to create a policy, ensure that the entity's capacity is appropriately decreased by the amount of capital that will be tied to the new policy being created.
*/
function _validateSimplePolicyCreation(bytes32 _entityId, SimplePolicy calldata simplePolicy) internal view returns (uint256 updatedUtilizedCapacity) {
function _validateSimplePolicyCreation(bytes32 _entityId, SimplePolicy calldata simplePolicy) internal view {
// The policy's limit cannot be 0. If a policy's limit is zero, this essentially means the policy doesn't require any capital, which doesn't make business sense.
require(simplePolicy.limit > 0, "limit not > 0");
require(LibAdmin._isSupportedExternalToken(simplePolicy.asset), "external token is not supported");

bool isEntityAdmin = LibACL._isInGroup(LibHelpers._getSenderId(), _entityId, LibHelpers._stringToBytes32(LibConstants.GROUP_ENTITY_ADMINS));
require(isEntityAdmin, "must be entity admin");
Expand All @@ -45,20 +46,15 @@ library LibEntity {
// require(entity.collateralRatio > 0 && entity.maxCapacity > 0, "currency disabled");

// Calculate the entity's utilized capacity after it writes this policy.
updatedUtilizedCapacity = entity.utilizedCapacity + simplePolicy.limit;
uint256 updatedUtilizedCapacity = entity.utilizedCapacity + ((simplePolicy.limit * entity.collateralRatio) / LibConstants.BP_FACTOR);

// The entity must have enough capacity available to write this policy.
// An entity is not able to write an additional policy that will utilize its capacity beyond its assigned max capacity.
require(entity.maxCapacity >= updatedUtilizedCapacity, "not enough available capacity");

// Calculate the entity's required capital for its capacity utilization based on its collateral requirements.
uint256 capitalRequirementForUpdatedUtilizedCapacity = (updatedUtilizedCapacity * entity.collateralRatio) / LibConstants.BP_FACTOR;

require(LibAdmin._isSupportedExternalToken(simplePolicy.asset), "external token is not supported");

// The entity's balance must be >= to the updated capacity requirement
// todo: business only wants to count the entity's balance that was raised from the participation token sale and not its total balance
require(LibTokenizedVault._internalBalanceOf(_entityId, simplePolicy.asset) >= capitalRequirementForUpdatedUtilizedCapacity, "not enough capital");
require(LibTokenizedVault._internalBalanceOf(_entityId, simplePolicy.asset) >= updatedUtilizedCapacity, "not enough capital");

require(simplePolicy.startDate >= block.timestamp, "start date < block.timestamp");
require(simplePolicy.maturationDate > simplePolicy.startDate, "start date > maturation date");
Expand Down Expand Up @@ -88,9 +84,13 @@ library LibEntity {

require(_stakeholders.entityIds.length == _stakeholders.signatures.length, "incorrect number of signatures");

// note: An entity's updated utilized capacity <= max capitalization check is done in _validateSimplePolicyCreation().
// Update state with the entity's updated utilized capacity.
s.entities[_entityId].utilizedCapacity = _validateSimplePolicyCreation(_entityId, _simplePolicy);
_validateSimplePolicyCreation(_entityId, _simplePolicy);

Entity storage entity = s.entities[_entityId];
uint256 factoredLimit = (_simplePolicy.limit * entity.collateralRatio) / LibConstants.BP_FACTOR;

entity.utilizedCapacity += factoredLimit;
s.lockedBalances[_entityId][entity.assetId] += factoredLimit;

LibObject._createObject(_policyId, _entityId, _dataHash);
s.simplePolicies[_policyId] = _simplePolicy;
Expand Down Expand Up @@ -163,8 +163,21 @@ library LibEntity {
AppStorage storage s = LibAppStorage.diamondStorage();
validateEntity(_entity);

uint256 oldCollateralRatio = s.entities[_entityId].collateralRatio;
uint256 oldUtilizedCapacity = s.entities[_entityId].utilizedCapacity;

s.entities[_entityId] = _entity;

// if it's a cell, and collateral ratio changed
if (_entity.assetId != 0 && _entity.collateralRatio != oldCollateralRatio) {
// unlock current utilized capacity
s.lockedBalances[_entityId][_entity.assetId] -= oldUtilizedCapacity;
// recalculate utilized capacity
s.entities[_entityId].utilizedCapacity = (oldUtilizedCapacity * _entity.collateralRatio) / oldCollateralRatio;
// lock updated utilized capacity
s.lockedBalances[_entityId][_entity.assetId] += s.entities[_entityId].utilizedCapacity;
}

emit EntityUpdated(_entityId);
amarinkovic marked this conversation as resolved.
Show resolved Hide resolved
}

Expand All @@ -183,7 +196,7 @@ library LibEntity {
// note: We do not directly use the value maxCapacity to determine if the entity can or cannot write a policy.
// First, we use the bool simplePolicyEnabled to control and dictate whether an entity can or cannot write a policy.
// If an entity has this set to true, then we check if an entity has enough capacity to write the policy.
require(!_entity.simplePolicyEnabled || (_entity.maxCapacity > 0), "max capacity should be greater than 0 for policy creation");
require(!_entity.simplePolicyEnabled || _entity.maxCapacity > 0, "max capacity should be greater than 0 for policy creation");
} else {
// non-cell entity
require(_entity.collateralRatio == 0, "only cell has collateral ratio");
Expand Down
13 changes: 4 additions & 9 deletions src/diamonds/nayms/libs/LibMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ library LibMarket {
marketInfo.state = LibConstants.OFFER_STATE_ACTIVE;

// lock tokens!
s.marketLockedBalances[_creator][_sellToken] += _sellAmount;
s.lockedBalances[_creator][_sellToken] += _sellAmount;
}

s.offers[lastOfferId] = marketInfo;
Expand Down Expand Up @@ -283,7 +283,7 @@ library LibMarket {
}
}

s.marketLockedBalances[s.offers[_offerId].creator][s.offers[_offerId].sellToken] -= _buyAmount;
s.lockedBalances[s.offers[_offerId].creator][s.offers[_offerId].sellToken] -= _buyAmount;

LibTokenizedVault._internalTransfer(s.offers[_offerId].creator, _takerId, s.offers[_offerId].sellToken, _buyAmount);
LibTokenizedVault._internalTransfer(_takerId, s.offers[_offerId].creator, s.offers[_offerId].buyToken, _sellAmount);
Expand Down Expand Up @@ -336,7 +336,7 @@ library LibMarket {
// unlock the remaining sell amount back to creator
if (marketInfo.sellAmount > 0) {
// note nothing is transferred since tokens for sale are UN-escrowed. Just unlock!
s.marketLockedBalances[s.offers[_offerId].creator][s.offers[_offerId].sellToken] -= marketInfo.sellAmount;
s.lockedBalances[s.offers[_offerId].creator][s.offers[_offerId].sellToken] -= marketInfo.sellAmount;
}

// don't emit event stating market order is canceled if the market order was executed and fulfilled
Expand Down Expand Up @@ -379,7 +379,7 @@ library LibMarket {

// note: add restriction to not be able to sell tokens that are already for sale
// maker must own sell amount and it must not be locked
require(s.tokenBalances[_sellToken][_entityId] - s.marketLockedBalances[_entityId][_sellToken] >= _sellAmount, "tokens locked in market");
require(s.tokenBalances[_sellToken][_entityId] - s.lockedBalances[_entityId][_sellToken] >= _sellAmount, "tokens locked");

// must have a valid fee schedule
require(_feeSchedule == LibConstants.FEE_SCHEDULE_PLATFORM_ACTION || _feeSchedule == LibConstants.FEE_SCHEDULE_STANDARD, "fee schedule invalid");
Expand Down Expand Up @@ -438,9 +438,4 @@ library LibMarket {
AppStorage storage s = LibAppStorage.diamondStorage();
return s.offers[_offerId].state == LibConstants.OFFER_STATE_ACTIVE;
}

function _getBalanceOfTokensForSale(bytes32 _entityId, bytes32 _tokenId) internal view returns (uint256 amount) {
AppStorage storage s = LibAppStorage.diamondStorage();
return s.marketLockedBalances[_entityId][_tokenId];
}
}
9 changes: 7 additions & 2 deletions src/diamonds/nayms/libs/LibSimplePolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,15 @@ library LibSimplePolicy {

function releaseFunds(bytes32 _policyId) private {
AppStorage storage s = LibAppStorage.diamondStorage();
bytes32 entityId = LibObject._getParent(_policyId);

SimplePolicy storage simplePolicy = s.simplePolicies[_policyId];
Entity storage entity = s.entities[LibObject._getParent(_policyId)];
Entity storage entity = s.entities[entityId];

uint256 policyLockedAmount = (simplePolicy.limit * entity.collateralRatio) / LibConstants.BP_FACTOR;
entity.utilizedCapacity -= policyLockedAmount;
s.lockedBalances[entityId][entity.assetId] -= policyLockedAmount;

entity.utilizedCapacity -= simplePolicy.limit;
simplePolicy.fundsLocked = false;
}
}
13 changes: 9 additions & 4 deletions src/diamonds/nayms/libs/LibTokenizedVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ library LibTokenizedVault {
) internal returns (bool success) {
AppStorage storage s = LibAppStorage.diamondStorage();

if (s.marketLockedBalances[_from][_tokenId] > 0) {
require(s.tokenBalances[_tokenId][_from] - s.marketLockedBalances[_from][_tokenId] >= _amount, "_internalTransferFrom: tokens for sale in mkt");
if (s.lockedBalances[_from][_tokenId] > 0) {
require(s.tokenBalances[_tokenId][_from] - s.lockedBalances[_from][_tokenId] >= _amount, "_internalTransferFrom: tokens locked");
} else {
require(s.tokenBalances[_tokenId][_from] >= _amount, "_internalTransferFrom: must own the funds");
}
Expand Down Expand Up @@ -147,8 +147,8 @@ library LibTokenizedVault {
) internal {
AppStorage storage s = LibAppStorage.diamondStorage();

if (s.marketLockedBalances[_from][_tokenId] > 0) {
require(s.tokenBalances[_tokenId][_from] - s.marketLockedBalances[_from][_tokenId] >= _amount, "_internalBurn: tokens for sale in mkt");
if (s.lockedBalances[_from][_tokenId] > 0) {
require(s.tokenBalances[_tokenId][_from] - s.lockedBalances[_from][_tokenId] >= _amount, "_internalBurn: tokens locked");
} else {
require(s.tokenBalances[_tokenId][_from] >= _amount, "_internalBurn: must own the funds");
}
Expand Down Expand Up @@ -277,4 +277,9 @@ library LibTokenizedVault {
_dividendDeduction += 1;
}
}

function _getLockedBalance(bytes32 _accountId, bytes32 _tokenId) internal view returns (uint256 amount) {
AppStorage storage s = LibAppStorage.diamondStorage();
return s.lockedBalances[_accountId][_tokenId];
}
}
13 changes: 0 additions & 13 deletions test/T02User.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,4 @@ contract T02UserTest is D03ProtocolDefaults, MockAccounts {
nayms.setEntity(signer1Id, entityId);
assertEq(nayms.getEntity(signer1Id), entityId);
}

function testGetBalanceOfTokensForSale() public {
bytes32 entityId = createTestEntity(account0Id);

// nothing at first
assertEq(nayms.getBalanceOfTokensForSale(entityId, entityId), 0);

nayms.enableEntityTokenization(entityId, "e1token", "e1token");

// now start token sale to create an offer
nayms.startTokenSale(entityId, 100, 100);
assertEq(nayms.getBalanceOfTokensForSale(entityId, entityId), 100);
}
}
26 changes: 24 additions & 2 deletions test/T03TokenizedVault.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,23 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults {
address token,
uint256 amount
) internal {
(bool success, bytes memory result) = address(nayms).call(abi.encodeWithSelector(tokenizedVaultFixture.externalDepositDirect.selector, to, token, amount));
(bool success, ) = address(nayms).call(abi.encodeWithSelector(tokenizedVaultFixture.externalDepositDirect.selector, to, token, amount));
require(success, "Should get commissions from app storage");
}

function testGetLockedBalance() public {
bytes32 entityId = createTestEntity(account0Id);

// nothing at first
assertEq(nayms.getLockedBalance(entityId, entityId), 0);

// now start token sale to create an offer
nayms.enableEntityTokenization(entityId, "Entity1", "Entity1 Token");
nayms.startTokenSale(entityId, 100, 100);

assertEq(nayms.getLockedBalance(entityId, entityId), 100);
}

function testBasisPoints() public {
TradingCommissionsBasisPoints memory bp = nayms.getTradingCommissionsBasisPoints();

Expand Down Expand Up @@ -278,6 +291,15 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults {

bytes32 randomGuid = bytes32("0x1");

address nonAdminAddress = vm.addr(0xACC9);
bytes32 nonAdminId = LibHelpers._getIdForAddress(nonAdminAddress);
nayms.setEntity(nonAdminId, acc0EntityId);

vm.startPrank(nonAdminAddress);
vm.expectRevert("payDividendFromEntity: not the entity's admin");
nayms.payDividendFromEntity(randomGuid, 10 ether);
vm.stopPrank();

vm.expectRevert("payDividendFromEntity: insufficient balance");
nayms.payDividendFromEntity(randomGuid, 10 ether);

Expand Down Expand Up @@ -409,7 +431,7 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults {
);

uint256 takerBuyAmount = 1e18;
console2.log(nayms.getBalanceOfTokensForSale(eAlice, eAlice));
console2.log(nayms.getLockedBalance(eAlice, eAlice));

TradingCommissions memory tc = nayms.calculateTradingCommissions(takerBuyAmount);

Expand Down
Loading