From f8b2d598a083a6dcec6cc5d4be0185d78d60ff57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandar=20Marinkovi=C4=87?= Date: Tue, 6 Dec 2022 12:48:05 +0100 Subject: [PATCH] Lock utilised capacity (#39) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: give more general name to locked up funds mapping * refactor: use more generic require messages for locked tokens * feat: update locked funds with utilised capacity * refactor: general locked balance getter; test defaults improvements * test: not able to trade with locked funds * feat: only entity admin can pay dividend * feat: locked funds and utilised capacity are factored by collateral ratio * 📝 docs: update natspec generated markdowns * test: harden collateral ratio test case * feat: add collateral ratio updated event * test: fix cell update test * feat: ensure enough balance available when increasing collateral ratio --- docs/facets/ITokenizedVaultFacet.md | 20 ++ docs/facets/IUserFacet.md | 20 -- src/diamonds/nayms/AppStorage.sol | 2 +- .../nayms/facets/TokenizedVaultFacet.sol | 16 +- src/diamonds/nayms/facets/UserFacet.sol | 10 - .../nayms/interfaces/ITokenizedVaultFacet.sol | 8 + src/diamonds/nayms/interfaces/IUserFacet.sol | 8 - src/diamonds/nayms/libs/LibEntity.sol | 42 +++- src/diamonds/nayms/libs/LibMarket.sol | 13 +- src/diamonds/nayms/libs/LibSimplePolicy.sol | 9 +- src/diamonds/nayms/libs/LibTokenizedVault.sol | 13 +- test/T02User.t.sol | 13 -- test/T03TokenizedVault.t.sol | 30 ++- test/T04Entity.t.sol | 147 +++++++------- test/T04Market.t.sol | 191 +++++++++++------- test/defaults/D02TestSetup.sol | 9 +- test/defaults/D03ProtocolDefaults.sol | 53 ++++- 17 files changed, 370 insertions(+), 234 deletions(-) diff --git a/docs/facets/ITokenizedVaultFacet.md b/docs/facets/ITokenizedVaultFacet.md index dde14669..cfd568e2 100644 --- a/docs/facets/ITokenizedVaultFacet.md +++ b/docs/facets/ITokenizedVaultFacet.md @@ -140,3 +140,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.|

+### 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. +| +

+#### Returns: +| Type | Description | +| --- | --- | +|`amount` | of tokens that the entity has for sale in the marketplace.| +

diff --git a/docs/facets/IUserFacet.md b/docs/facets/IUserFacet.md index 3f543f32..9e7bea64 100644 --- a/docs/facets/IUserFacet.md +++ b/docs/facets/IUserFacet.md @@ -72,23 +72,3 @@ Gets the entity related to the user | --- | --- | |`entityId` | Unique platform ID of the entity|

-### 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. -| -

-#### Returns: -| Type | Description | -| --- | --- | -|`amount` | of tokens that the entity has for sale in the marketplace.| -

diff --git a/src/diamonds/nayms/AppStorage.sol b/src/diamonds/nayms/AppStorage.sol index 40a4fc3f..df243305 100644 --- a/src/diamonds/nayms/AppStorage.sol +++ b/src/diamonds/nayms/AppStorage.sol @@ -60,7 +60,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 { diff --git a/src/diamonds/nayms/facets/TokenizedVaultFacet.sol b/src/diamonds/nayms/facets/TokenizedVaultFacet.sol index dce2ca1f..cadb8b85 100644 --- a/src/diamonds/nayms/facets/TokenizedVaultFacet.sol +++ b/src/diamonds/nayms/facets/TokenizedVaultFacet.sol @@ -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"; @@ -108,9 +109,22 @@ contract TokenizedVaultFacet is Modifiers { bytes32 entityId = LibObject._getParentFromAddress(msg.sender); bytes32 dividendTokenId = LibEntity._getEntityInfo(entityId).assetId; - require(LibACL._isInGroup(LibHelpers._getIdForAddress(msg.sender), entityId, LibHelpers._stringToBytes32(LibConstants.GROUP_ENTITY_ADMINS)), "not the entity's admin"); + require( + LibACL._isInGroup(LibHelpers._getIdForAddress(msg.sender), 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); + } } diff --git a/src/diamonds/nayms/facets/UserFacet.sol b/src/diamonds/nayms/facets/UserFacet.sol index 6beaf8f1..9039d26a 100644 --- a/src/diamonds/nayms/facets/UserFacet.sol +++ b/src/diamonds/nayms/facets/UserFacet.sol @@ -57,14 +57,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); - } } diff --git a/src/diamonds/nayms/interfaces/ITokenizedVaultFacet.sol b/src/diamonds/nayms/interfaces/ITokenizedVaultFacet.sol index ce72a3d2..83d6b76d 100644 --- a/src/diamonds/nayms/interfaces/ITokenizedVaultFacet.sol +++ b/src/diamonds/nayms/interfaces/ITokenizedVaultFacet.sol @@ -81,4 +81,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); } diff --git a/src/diamonds/nayms/interfaces/IUserFacet.sol b/src/diamonds/nayms/interfaces/IUserFacet.sol index 7f34e28c..d84dbe49 100644 --- a/src/diamonds/nayms/interfaces/IUserFacet.sol +++ b/src/diamonds/nayms/interfaces/IUserFacet.sol @@ -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); } diff --git a/src/diamonds/nayms/libs/LibEntity.sol b/src/diamonds/nayms/libs/LibEntity.sol index 88a750c4..48d8c10b 100644 --- a/src/diamonds/nayms/libs/LibEntity.sol +++ b/src/diamonds/nayms/libs/LibEntity.sol @@ -26,13 +26,15 @@ library LibEntity { event EntityUpdated(bytes32 entityId); event SimplePolicyCreated(bytes32 indexed id, bytes32 entityId); event TokenSaleStarted(bytes32 indexed entityId, uint256 offerId); + event CollateralRatioUpdated(bytes32 indexed entityId, uint256 collateralRatio, uint256 utilizedCapacity); /** * @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"); @@ -58,18 +60,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; - // 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"); @@ -105,9 +104,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; @@ -210,16 +213,33 @@ library LibEntity { function _updateEntity(bytes32 _entityId, Entity memory _entity) internal { AppStorage storage s = LibAppStorage.diamondStorage(); + // Cannot update a non-existing entity's metadata. if (s.existingEntities[_entityId] == false) { revert EntityDoesNotExist(_entityId); } + validateEntity(_entity); - // assetId change not allowed + uint256 oldCollateralRatio = s.entities[_entityId].collateralRatio; + uint256 oldUtilizedCapacity = s.entities[_entityId].utilizedCapacity; bytes32 originalAssetId = s.entities[_entityId].assetId; + s.entities[_entityId] = _entity; - s.entities[_entityId].assetId = originalAssetId; + s.entities[_entityId].assetId = originalAssetId; // assetId change not allowed + + // if it's a cell, and collateral ratio changed + if (_entity.assetId != 0 && _entity.collateralRatio != oldCollateralRatio) { + uint256 newUtilizedCapacity = (oldUtilizedCapacity * _entity.collateralRatio) / oldCollateralRatio; + uint256 newLockedBalance = s.lockedBalances[_entityId][_entity.assetId] - oldUtilizedCapacity + newUtilizedCapacity; + + require(LibTokenizedVault._internalBalanceOf(_entityId, _entity.assetId) >= newLockedBalance, "collateral ratio invalid, not enough balance"); + + s.entities[_entityId].utilizedCapacity = newUtilizedCapacity; + s.lockedBalances[_entityId][_entity.assetId] = newLockedBalance; + + emit CollateralRatioUpdated(_entityId, _entity.collateralRatio, s.entities[_entityId].utilizedCapacity); + } emit EntityUpdated(_entityId); } diff --git a/src/diamonds/nayms/libs/LibMarket.sol b/src/diamonds/nayms/libs/LibMarket.sol index 5628df2c..828e7213 100644 --- a/src/diamonds/nayms/libs/LibMarket.sol +++ b/src/diamonds/nayms/libs/LibMarket.sol @@ -249,7 +249,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; @@ -282,7 +282,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); @@ -335,7 +335,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 @@ -378,7 +378,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"); @@ -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]; - } } diff --git a/src/diamonds/nayms/libs/LibSimplePolicy.sol b/src/diamonds/nayms/libs/LibSimplePolicy.sol index 27272e34..445b2898 100644 --- a/src/diamonds/nayms/libs/LibSimplePolicy.sol +++ b/src/diamonds/nayms/libs/LibSimplePolicy.sol @@ -99,10 +99,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; } } diff --git a/src/diamonds/nayms/libs/LibTokenizedVault.sol b/src/diamonds/nayms/libs/LibTokenizedVault.sol index dcb63f77..8e7b105a 100644 --- a/src/diamonds/nayms/libs/LibTokenizedVault.sol +++ b/src/diamonds/nayms/libs/LibTokenizedVault.sol @@ -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"); } @@ -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"); } @@ -292,4 +292,9 @@ library LibTokenizedVault { _withdrawableDividend = (_withdrawnSoFar >= holderDividend) ? 0 : holderDividend - _withdrawnSoFar; } + + function _getLockedBalance(bytes32 _accountId, bytes32 _tokenId) internal view returns (uint256 amount) { + AppStorage storage s = LibAppStorage.diamondStorage(); + return s.lockedBalances[_accountId][_tokenId]; + } } diff --git a/test/T02User.t.sol b/test/T02User.t.sol index 03c6408a..0b06150c 100644 --- a/test/T02User.t.sol +++ b/test/T02User.t.sol @@ -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, "ENTITYSYMBOL"); - - // now start token sale to create an offer - nayms.startTokenSale(entityId, 100, 100); - assertEq(nayms.getBalanceOfTokensForSale(entityId, entityId), 100); - } } diff --git a/test/T03TokenizedVault.t.sol b/test/T03TokenizedVault.t.sol index cf65f9b5..57b246ff 100644 --- a/test/T03TokenizedVault.t.sol +++ b/test/T03TokenizedVault.t.sol @@ -96,6 +96,19 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults, MockAccounts { return abi.decode(result, (TradingCommissionsConfig)); } + 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"); + nayms.startTokenSale(entityId, 100, 100); + + assertEq(nayms.getLockedBalance(entityId, entityId), 100); + } + function testBasisPoints() public { TradingCommissionsBasisPoints memory bp = nayms.getTradingCommissionsBasisPoints(); @@ -251,7 +264,7 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults, MockAccounts { writeTokenBalance(account0, naymsAddress, wethAddress, depositAmount); nayms.externalDeposit(wethAddress, 1 ether); vm.prank(account9); - vm.expectRevert("not the entity's admin"); + vm.expectRevert("payDividendFromEntity: not the entity's admin"); nayms.payDividendFromEntity(bytes32("0x1"), 1 ether); } @@ -273,6 +286,19 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults, MockAccounts { assertEq(nayms.internalTokenSupply(acc0EntityId), 0, "Testing when the participation token supply is 0, but par token supply is NOT 0"); 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); + nayms.payDividendFromEntity(randomGuid, 1 ether); // note: When the participation token supply is 0, payDividend() should transfer the payout directly to the payee assertEq(nayms.internalBalanceOf(acc0EntityId, nWETH), 1 ether, "acc0EntityId nWETH balance should INCREASE (transfer)"); @@ -400,7 +426,7 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults, MockAccounts { ); uint256 takerBuyAmount = 1e18; - console2.log(nayms.getBalanceOfTokensForSale(eAlice, eAlice)); + console2.log(nayms.getLockedBalance(eAlice, eAlice)); TradingCommissions memory tc = nayms.calculateTradingCommissions(takerBuyAmount); diff --git a/test/T04Entity.t.sol b/test/T04Entity.t.sol index c465c5d4..5a966889 100644 --- a/test/T04Entity.t.sol +++ b/test/T04Entity.t.sol @@ -11,14 +11,9 @@ import { LibACL } from "src/diamonds/nayms/libs/LibACL.sol"; import { LibTokenizedVault } from "src/diamonds/nayms/libs/LibTokenizedVault.sol"; import { LibFeeRouterFixture } from "test/fixtures/LibFeeRouterFixture.sol"; import { SimplePolicyFixture } from "test/fixtures/SimplePolicyFixture.sol"; - -import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "src/diamonds/nayms/interfaces/CustomErrors.sol"; contract T04EntityTest is D03ProtocolDefaults { - bytes32 internal wethId; - bytes32 internal wbtcId; - bytes32 internal entityId1 = "0xe1"; bytes32 internal policyId1 = "0xC0FFEE"; @@ -30,56 +25,9 @@ contract T04EntityTest is D03ProtocolDefaults { address internal account9; bytes32 internal account9Id; - function initPolicy(bytes32 policyId) internal returns (Stakeholders memory policyStakeholders, SimplePolicy memory policy) { - bytes32[] memory roles = new bytes32[](4); - roles[0] = LibHelpers._stringToBytes32(LibConstants.ROLE_UNDERWRITER); - roles[1] = LibHelpers._stringToBytes32(LibConstants.ROLE_BROKER); - roles[2] = LibHelpers._stringToBytes32(LibConstants.ROLE_CAPITAL_PROVIDER); - roles[3] = LibHelpers._stringToBytes32(LibConstants.ROLE_INSURED_PARTY); - - bytes32[] memory entityIds = new bytes32[](4); - entityIds[0] = DEFAULT_UNDERWRITER_ENTITY_ID; - entityIds[1] = DEFAULT_BROKER_ENTITY_ID; - entityIds[2] = DEFAULT_CAPITAL_PROVIDER_ENTITY_ID; - entityIds[3] = DEFAULT_INSURED_PARTY_ENTITY_ID; - - bytes[] memory signatures = new bytes[](4); - signatures[0] = initSig(0xACC2, policyId); - signatures[1] = initSig(0xACC1, policyId); - signatures[2] = initSig(0xACC3, policyId); - signatures[3] = initSig(0xACC4, policyId); - - console2.log(vm.addr(0xACC1)); - console2.log(vm.addr(0xACC2)); - console2.log(vm.addr(0xACC3)); - console2.log(vm.addr(0xACC4)); - - policyStakeholders = Stakeholders(roles, entityIds, signatures); - - bytes32[] memory commissionReceivers = new bytes32[](3); - commissionReceivers[0] = DEFAULT_UNDERWRITER_ENTITY_ID; - commissionReceivers[1] = DEFAULT_BROKER_ENTITY_ID; - commissionReceivers[2] = DEFAULT_CAPITAL_PROVIDER_ENTITY_ID; - - uint256[] memory commissions = new uint256[](3); - commissions[0] = 10; - commissions[1] = 10; - commissions[2] = 10; - - policy.startDate = 1000; - policy.maturationDate = 10000; - policy.asset = wethId; - policy.commissionReceivers = commissionReceivers; - policy.commissionBasisPoints = commissions; - policy.limit = 10000; - } - function setUp() public virtual override { super.setUp(); - wethId = LibHelpers._getIdForAddress(wethAddress); - wbtcId = LibHelpers._getIdForAddress(wbtcAddress); - account9 = vm.addr(0xACC9); account9Id = LibHelpers._getIdForAddress(account9); @@ -108,29 +56,25 @@ contract T04EntityTest is D03ProtocolDefaults { require(success, "Should update simple policy in app storage"); } - function initSig(uint256 account, bytes32 policyId) internal returns (bytes memory sig_) { - (uint8 v, bytes32 r, bytes32 s) = vm.sign(account, ECDSA.toEthSignedMessageHash(policyId)); - sig_ = abi.encodePacked(r, s, v); - } - function getReadyToCreatePolicies() public { // create entity - nayms.createEntity(entityId1, account0Id, initEntity(weth, 5000, 30000, 0, true), "test entity"); + nayms.createEntity(entityId1, account0Id, initEntity(weth, 5_000, 30_000, 0, true), "test entity"); // assign entity admin nayms.assignRole(account0Id, entityId1, LibConstants.ROLE_ENTITY_ADMIN); assertTrue(nayms.isInGroup(account0Id, entityId1, LibConstants.GROUP_ENTITY_ADMINS)); // fund the entity balance - weth.approve(naymsAddress, 10000); - writeTokenBalance(account0, naymsAddress, wethAddress, 10000); - assertEq(weth.balanceOf(account0), 10000); - nayms.externalDeposit(wethAddress, 10000); - assertEq(nayms.internalBalanceOf(entityId1, wethId), 10000); + uint256 amount = 21000; + weth.approve(naymsAddress, amount); + writeTokenBalance(account0, naymsAddress, wethAddress, amount); + assertEq(weth.balanceOf(account0), amount); + nayms.externalDeposit(wethAddress, amount); + assertEq(nayms.internalBalanceOf(entityId1, wethId), amount); } function testEnableEntityTokenization() public { - nayms.createEntity(entityId1, account0Id, initEntity(weth, 500, 10000, 0, false), "entity test hash"); + nayms.createEntity(entityId1, account0Id, initEntity(weth, 5000, 10000, 0, false), "entity test hash"); // Attempt to tokenize an entity when the entity does not exist. Should throw an error. bytes32 nonExistentEntity = bytes32("ffffaaa"); @@ -186,12 +130,60 @@ contract T04EntityTest is D03ProtocolDefaults { vm.recordLogs(); nayms.updateEntity(entityId1, initEntity(weth, LibConstants.BP_FACTOR, LibConstants.BP_FACTOR, 0, false)); Vm.Log[] memory entries = vm.getRecordedLogs(); - assertEq(entries[0].topics.length, 1); - assertEq(entries[0].topics[0], keccak256("EntityUpdated(bytes32)")); - bytes32 id = abi.decode(entries[0].data, (bytes32)); + assertEq(entries[1].topics.length, 1); + assertEq(entries[1].topics[0], keccak256("EntityUpdated(bytes32)")); + bytes32 id = abi.decode(entries[1].data, (bytes32)); assertEq(id, entityId1); } + function testUpdateCellCollateralRatio() public { + nayms.createEntity(entityId1, account0Id, initEntity(weth, 5_000, 30_000, 0, true), "test entity"); + nayms.assignRole(account0Id, entityId1, LibConstants.ROLE_ENTITY_ADMIN); + + // fund the entity balance + uint256 amount = 5_000; + weth.approve(naymsAddress, amount); + writeTokenBalance(account0, naymsAddress, wethAddress, amount); + nayms.externalDeposit(wethAddress, amount); + assertEq(nayms.internalBalanceOf(entityId1, wethId), amount); + + assertEq(nayms.getLockedBalance(entityId1, wethId), 0, "NO FUNDS shoud be locked"); + + nayms.createSimplePolicy(policyId1, entityId1, stakeholders, simplePolicy, "test"); + uint256 expectedLockedBalance = (simplePolicy.limit * 5_000) / LibConstants.BP_FACTOR; + assertEq(nayms.getLockedBalance(entityId1, wethId), expectedLockedBalance, "funds SHOULD BE locked"); + + Entity memory entity1 = nayms.getEntityInfo(entityId1); + assertEq(entity1.utilizedCapacity, (simplePolicy.limit * 5_000) / LibConstants.BP_FACTOR, "utilized capacity should increase"); + + entity1.collateralRatio = 7_000; + vm.expectRevert("collateral ratio invalid, not enough balance"); + nayms.updateEntity(entityId1, entity1); + + vm.recordLogs(); + + entity1.collateralRatio = 4_000; + nayms.updateEntity(entityId1, entity1); + assertEq(nayms.getLockedBalance(entityId1, wethId), (simplePolicy.limit * 4_000) / LibConstants.BP_FACTOR, "locked balance SHOULD decrease"); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + + assertEq(entries[0].topics.length, 2, "CollateralRatioUpdated: topics length incorrect"); + assertEq(entries[0].topics[0], keccak256("CollateralRatioUpdated(bytes32,uint256,uint256)"), "CollateralRatioUpdated: Invalid event signature"); + assertEq(entries[0].topics[1], entityId1, "CollateralRatioUpdated: incorrect entityID"); + (uint256 newCollateralRatio, uint256 newUtilisedCapacity) = abi.decode(entries[0].data, (uint256, uint256)); + assertEq(newCollateralRatio, 4_000, "CollateralRatioUpdated: invalid collateral ratio"); + assertEq(newUtilisedCapacity, (simplePolicy.limit * 4_000) / LibConstants.BP_FACTOR, "CollateralRatioUpdated: invalid utilised capacity"); + + Entity memory entity1AfterUpdate = nayms.getEntityInfo(entityId1); + assertEq(entity1AfterUpdate.utilizedCapacity, (simplePolicy.limit * 4_000) / LibConstants.BP_FACTOR, "utilized capacity should increase"); + + nayms.cancelSimplePolicy(policyId1); + assertEq(nayms.getLockedBalance(entityId1, wethId), 0, "locked balance SHOULD be released"); + Entity memory entity1After2ndUpdate = nayms.getEntityInfo(entityId1); + assertEq(entity1After2ndUpdate.utilizedCapacity, 0, "utilized capacity should increase"); + } + function testUpdateAllowSimplePolicy() public { vm.expectRevert(abi.encodePacked(EntityDoesNotExist.selector, (entityId1))); nayms.updateAllowSimplePolicy(entityId1, true); @@ -252,7 +244,7 @@ contract T04EntityTest is D03ProtocolDefaults { } function testCreateSimplePolicyValidation() public { - nayms.createEntity(entityId1, account0Id, initEntity(weth, 5000, 10000, 0, false), "entity test hash"); + nayms.createEntity(entityId1, account0Id, initEntity(weth, LibConstants.BP_FACTOR, LibConstants.BP_FACTOR, 0, false), "entity test hash"); // enable simple policy creation vm.expectRevert("simple policy creation disabled"); @@ -393,14 +385,14 @@ contract T04EntityTest is D03ProtocolDefaults { // check utilized capacity of entity Entity memory e = nayms.getEntityInfo(entityId1); - assertEq(e.utilizedCapacity, 10000, "utilized capacity"); + assertEq(e.utilizedCapacity, (10_000 * e.collateralRatio) / LibConstants.BP_FACTOR, "utilized capacity"); bytes32 policyId2 = "0xC0FFEF"; (Stakeholders memory stakeholders2, SimplePolicy memory policy2) = initPolicy(policyId2); nayms.createSimplePolicy(policyId2, entityId1, stakeholders2, policy2, "policy2"); e = nayms.getEntityInfo(entityId1); - assertEq(e.utilizedCapacity, 20000, "utilized capacity"); + assertEq(e.utilizedCapacity, (20_000 * e.collateralRatio) / LibConstants.BP_FACTOR, "utilized capacity"); } function testCreateSimplePolicyFundsAreLockedInitially() public { @@ -633,13 +625,14 @@ contract T04EntityTest is D03ProtocolDefaults { nayms.checkAndUpdateSimplePolicyState(policyId1); Entity memory entityAfter2 = nayms.getEntityInfo(entityId1); - assertEq(utilizedCapacityBefore - simplePolicy.limit, entityAfter2.utilizedCapacity, "utilized capacity should increase"); + uint256 expectedutilizedCapacity = utilizedCapacityBefore - (simplePolicy.limit * entityAfter2.collateralRatio) / LibConstants.BP_FACTOR; + assertEq(expectedutilizedCapacity, entityAfter2.utilizedCapacity, "utilized capacity should increase"); } function testPayPremiumCommissions() public { // Deploy the LibFeeRouterFixture LibFeeRouterFixture libFeeRouterFixture = new LibFeeRouterFixture(); - IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + bytes4[] memory functionSelectors = new bytes4[](5); functionSelectors[0] = libFeeRouterFixture.payPremiumCommissions.selector; functionSelectors[1] = libFeeRouterFixture.payTradingCommissions.selector; @@ -648,6 +641,7 @@ contract T04EntityTest is D03ProtocolDefaults { functionSelectors[4] = libFeeRouterFixture.getPremiumCommissionBasisPointsFixture.selector; // Diamond cut this fixture contract into our nayms diamond in order to test against the diamond + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); cut[0] = IDiamondCut.FacetCut({ facetAddress: address(libFeeRouterFixture), action: IDiamondCut.FacetCutAction.Add, functionSelectors: functionSelectors }); nayms.diamondCut(cut, address(0), ""); @@ -660,12 +654,11 @@ contract T04EntityTest is D03ProtocolDefaults { (bool success, bytes memory result) = address(nayms).call(abi.encodeWithSelector(libFeeRouterFixture.payPremiumCommissions.selector, policyId1, premiumPaid)); (success, result) = address(nayms).call(abi.encodeWithSelector(libFeeRouterFixture.getPremiumCommissionBasisPointsFixture.selector)); - SimplePolicy memory sp = getSimplePolicy(policyId1); - uint256 commissionNaymsLtd = (premiumPaid * nayms.getPremiumCommissionBasisPoints().premiumCommissionNaymsLtdBP) / LibConstants.BP_FACTOR; uint256 commissionNDF = (premiumPaid * nayms.getPremiumCommissionBasisPoints().premiumCommissionNDFBP) / LibConstants.BP_FACTOR; uint256 commissionSTM = (premiumPaid * nayms.getPremiumCommissionBasisPoints().premiumCommissionSTMBP) / LibConstants.BP_FACTOR; + SimplePolicy memory sp = getSimplePolicy(policyId1); assertEq(nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.NAYMS_LTD_IDENTIFIER), sp.asset), commissionNaymsLtd); assertEq(nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.NDF_IDENTIFIER), sp.asset), commissionNDF); assertEq(nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.STM_IDENTIFIER), sp.asset), commissionSTM); @@ -681,7 +674,11 @@ contract T04EntityTest is D03ProtocolDefaults { nayms.cancelSimplePolicy(policyId1); Entity memory entityAfter = nayms.getEntityInfo(entityId1); - assertEq(utilizedCapacityBefore - simplePolicy.limit, entityAfter.utilizedCapacity, "utilized capacity should change"); + assertEq( + utilizedCapacityBefore - ((simplePolicy.limit * entityAfter.collateralRatio) / LibConstants.BP_FACTOR), + entityAfter.utilizedCapacity, + "utilized capacity should change" + ); SimplePolicyInfo memory simplePolicyInfo = nayms.getSimplePolicyInfo(policyId1); assertEq(simplePolicyInfo.cancelled, true, "Simple policy should be cancelled"); diff --git a/test/T04Market.t.sol b/test/T04Market.t.sol index ebc0bb73..9621a32d 100644 --- a/test/T04Market.t.sol +++ b/test/T04Market.t.sol @@ -6,7 +6,7 @@ import { Vm } from "forge-std/Vm.sol"; import { MockAccounts } from "./utils/users/MockAccounts.sol"; -import { Entity, FeeRatio, MarketInfo, TradingCommissions } from "src/diamonds/nayms/interfaces/FreeStructs.sol"; +import { Entity, FeeRatio, MarketInfo, TradingCommissions, SimplePolicy, SimplePolicyInfo, Stakeholders } from "src/diamonds/nayms/interfaces/FreeStructs.sol"; import { INayms, IDiamondCut } from "src/diamonds/nayms/INayms.sol"; import { IERC20 } from "src/erc20/IERC20.sol"; @@ -15,7 +15,7 @@ import { TradingCommissionsFixture, TradingCommissionsConfig } from "test/fixtur /* Terminology: - nWETH: bytes32 ID of WETH + wethId: bytes32 ID of WETH nENTITYx: bytes32 ID of entityx when referring to entityx's token (nENTITYx == entityx) entityx: bytes32 ID of entity nEntity0: nEntity0 == DEFAULT_ACCOUNT0_ENTITY_ID @@ -37,8 +37,6 @@ struct TestInfo { } contract T04MarketTest is D03ProtocolDefaults, MockAccounts { - bytes32 internal nWETH; - bytes32 internal nWBTC; bytes32 internal dividendBankId; bytes32 internal entity1 = bytes32("e5"); @@ -78,10 +76,6 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { function setUp() public virtual override { super.setUp(); - // init token IDs - nWETH = LibHelpers._getIdForAddress(wethAddress); - nWBTC = LibHelpers._getIdForAddress(wbtcAddress); - // whitelist tokens nayms.addSupportedExternalToken(wethAddress); nayms.addSupportedExternalToken(wbtcAddress); @@ -131,7 +125,7 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { // 2nd param is the sell amount, 3rd param is the buy amount vm.recordLogs(); // putting an offer on behalf of entity1 to sell their nENTITY1 for the entity's associated asset - // 500 nENTITY1 for 500 nWETH, 1:1 ratio + // 500 nENTITY1 for 500 WETH, 1:1 ratio nayms.startTokenSale(entity1, dt.entity1MintAndSaleAmt, dt.entity1SalePrice); Vm.Log[] memory entries = vm.getRecordedLogs(); @@ -163,7 +157,7 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { assertEq(sellAmount, dt.entity1MintAndSaleAmt, "OrderAdded: invalid sell amount"); assertEq(sellAmountInitial, dt.entity1MintAndSaleAmt, "OrderAdded: invalid initial sell amount"); - assertEq(buyToken, nWETH, "OrderAdded: invalid buy token"); + assertEq(buyToken, wethId, "OrderAdded: invalid buy token"); assertEq(buyAmount, dt.entity1SalePrice, "OrderAdded: invalid buy amount"); assertEq(buyAmountInitial, dt.entity1SalePrice, "OrderAdded: invalid initial buy amount"); assertEq(state, LibConstants.OFFER_STATE_ACTIVE, "OrderAdded: invalid offer state"); @@ -186,18 +180,18 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { assertEq(marketInfo1.sellToken, entity1); assertEq(marketInfo1.sellAmount, dt.entity1MintAndSaleAmt); assertEq(marketInfo1.sellAmountInitial, dt.entity1MintAndSaleAmt); - assertEq(marketInfo1.buyToken, nWETH); + assertEq(marketInfo1.buyToken, wethId); assertEq(marketInfo1.buyAmount, dt.entity1SalePrice); assertEq(marketInfo1.buyAmountInitial, dt.entity1SalePrice); assertEq(marketInfo1.state, LibConstants.OFFER_STATE_ACTIVE); // a user should NOT be able to transfer / withdraw their tokens for sale // transfer to invalid entity check? - assertEq(nayms.getBalanceOfTokensForSale(entity1, entity1), dt.entity1MintAndSaleAmt, "entity1 nEntity1 balance of tokens for sale should INCREASE (lock)"); + assertEq(nayms.getLockedBalance(entity1, entity1), dt.entity1MintAndSaleAmt, "entity1 nEntity1 balance of tokens for sale should INCREASE (lock)"); // try transfering nEntity1 from entity1 to entity0 - this should REVERT! vm.startPrank(signer1); - vm.expectRevert("_internalTransferFrom: tokens for sale in mkt"); + vm.expectRevert("_internalTransferFrom: tokens locked"); nayms.internalTransfer(DEFAULT_ACCOUNT0_ENTITY_ID, entity1, 1); vm.stopPrank(); @@ -220,30 +214,30 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { nayms.externalDeposit(wethAddress, dt.entity3ExternalDepositAmt); vm.stopPrank(); - uint256 naymsBalanceBeforeTrade = nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.NAYMS_LTD_IDENTIFIER), nWETH); + uint256 naymsBalanceBeforeTrade = nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.NAYMS_LTD_IDENTIFIER), wethId); vm.startPrank(signer2); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt, entity1, dt.entity1MintAndSaleAmt); assertEq(nayms.getLastOfferId(), 2, "lastOfferId should INCREASE after executeLimitOffer"); vm.stopPrank(); - assertEq(nayms.internalBalanceOf(entity1, nWETH), dt.entity1ExternalDepositAmt + dt.entity1MintAndSaleAmt, "Maker should not pay commisisons"); + assertEq(nayms.internalBalanceOf(entity1, wethId), dt.entity1ExternalDepositAmt + dt.entity1MintAndSaleAmt, "Maker should not pay commisisons"); // assert trading commisions payed uint256 totalCommissions = (dt.entity1MintAndSaleAmt * c.tradingCommissionTotalBP) / LibConstants.BP_FACTOR; // see AppStorage: 4 => s.tradingCommissionTotalBP - assertEq(nayms.internalBalanceOf(entity2, nWETH), dt.entity2ExternalDepositAmt - dt.entity1MintAndSaleAmt - totalCommissions, "Taker should pay commissions"); + assertEq(nayms.internalBalanceOf(entity2, wethId), dt.entity2ExternalDepositAmt - dt.entity1MintAndSaleAmt - totalCommissions, "Taker should pay commissions"); uint256 naymsBalanceAfterTrade = naymsBalanceBeforeTrade + ((totalCommissions * c.tradingCommissionNaymsLtdBP) / LibConstants.BP_FACTOR); uint256 ndfBalanceAfterTrade = naymsBalanceBeforeTrade + ((totalCommissions * c.tradingCommissionNDFBP) / LibConstants.BP_FACTOR); uint256 stmBalanceAfterTrade = naymsBalanceBeforeTrade + ((totalCommissions * c.tradingCommissionSTMBP) / LibConstants.BP_FACTOR); assertEq( - nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.NAYMS_LTD_IDENTIFIER), nWETH), + nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.NAYMS_LTD_IDENTIFIER), wethId), naymsBalanceAfterTrade, "Nayms should receive half of trading commissions" ); - assertEq(nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.NDF_IDENTIFIER), nWETH), ndfBalanceAfterTrade, "NDF should get a trading commission"); + assertEq(nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.NDF_IDENTIFIER), wethId), ndfBalanceAfterTrade, "NDF should get a trading commission"); assertEq( - nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.STM_IDENTIFIER), nWETH), + nayms.internalBalanceOf(LibHelpers._stringToBytes32(LibConstants.STM_IDENTIFIER), wethId), stmBalanceAfterTrade, "Staking mechanism should get a trading commission" ); @@ -252,19 +246,19 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { assertEq(nayms.internalBalanceOf(entity2, entity1), dt.entity1MintAndSaleAmt); // test commission payed by taker on "secondary market" - uint256 e2WethBeforeTrade = nayms.internalBalanceOf(entity2, nWETH); - uint256 e3WethBeforeTrade = nayms.internalBalanceOf(entity3, nWETH); + uint256 e2WethBeforeTrade = nayms.internalBalanceOf(entity2, wethId); + uint256 e3WethBeforeTrade = nayms.internalBalanceOf(entity3, wethId); vm.startPrank(signer2); - nayms.executeLimitOffer(entity1, dt.entity1MintAndSaleAmt, nWETH, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(entity1, dt.entity1MintAndSaleAmt, wethId, dt.entity1MintAndSaleAmt); vm.stopPrank(); vm.startPrank(signer3); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt, entity1, dt.entity1MintAndSaleAmt); vm.stopPrank(); - assertEq(nayms.internalBalanceOf(entity2, nWETH), e2WethBeforeTrade + dt.entity1MintAndSaleAmt, "Maker pays no commissions, on secondary market"); - assertEq(nayms.internalBalanceOf(entity3, nWETH), e3WethBeforeTrade - dt.entity1MintAndSaleAmt - totalCommissions, "Taker should pay commissions, on secondary market"); + assertEq(nayms.internalBalanceOf(entity2, wethId), e2WethBeforeTrade + dt.entity1MintAndSaleAmt, "Maker pays no commissions, on secondary market"); + assertEq(nayms.internalBalanceOf(entity3, wethId), e3WethBeforeTrade - dt.entity1MintAndSaleAmt - totalCommissions, "Taker should pay commissions, on secondary market"); } function testMatchMakerPriceWithTakerBuyAmount() public { @@ -277,7 +271,7 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { writeTokenBalance(signer2, naymsAddress, wethAddress, dt.entity2ExternalDepositAmt); nayms.externalDeposit(wethAddress, dt.entity2ExternalDepositAmt); - nayms.executeLimitOffer(nWETH, 1_000 ether, entity1, 500 ether); + nayms.executeLimitOffer(wethId, 1_000 ether, entity1, 500 ether); vm.stopPrank(); assertEq(nayms.internalBalanceOf(entity2, entity1), 500 ether, "should match takers buy amount, not sell amount"); @@ -350,7 +344,7 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { nayms.externalDeposit(wethAddress, e2Balance); vm.stopPrank(); - // sell x nENTITY1 for y nWETH + // sell x nENTITY1 for y WETH nayms.startTokenSale(entity1, saleAmount, salePrice); MarketInfo memory marketInfo1 = nayms.getOffer(1); @@ -358,15 +352,15 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { assertEq(marketInfo1.sellToken, entity1, "sell token"); assertEq(marketInfo1.sellAmount, saleAmount, "sell amount"); assertEq(marketInfo1.sellAmountInitial, saleAmount, "sell amount initial"); - assertEq(marketInfo1.buyToken, nWETH, "buy token"); + assertEq(marketInfo1.buyToken, wethId, "buy token"); assertEq(marketInfo1.buyAmount, salePrice, "buy amount"); assertEq(marketInfo1.buyAmountInitial, salePrice, "buy amount initial"); assertEq(marketInfo1.state, LibConstants.OFFER_STATE_ACTIVE, "state"); vm.prank(signer2); - nayms.executeLimitOffer(nWETH, salePrice, entity1, saleAmount); + nayms.executeLimitOffer(wethId, salePrice, entity1, saleAmount); - assertOfferFilled(1, entity1, entity1, saleAmount, nWETH, salePrice); + assertOfferFilled(1, entity1, entity1, saleAmount, wethId, salePrice); } } @@ -398,9 +392,9 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { nayms.externalDeposit(wethAddress, salePrice); assertEq(nayms.internalBalanceOf(entity2, LibHelpers._getIdForAddress(wethAddress)), salePrice, "Entity2: invalid balance"); - // buy x nENTITY1 for y nWETH + // buy x nENTITY1 for y WETH - nayms.executeLimitOffer(nWETH, salePrice, entity1, saleAmount); + nayms.executeLimitOffer(wethId, salePrice, entity1, saleAmount); vm.stopPrank(); // taker needs balance for trading commissions @@ -411,11 +405,11 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { vm.stopPrank(); assertEq(nayms.internalBalanceOf(entity1, LibHelpers._getIdForAddress(wethAddress)), e1Balance, "Entity1: invalid balance"); - // sell x nENTITY1 for y nWETH + // sell x nENTITY1 for y WETH nayms.startTokenSale(entity1, saleAmount, salePrice); - assertOfferFilled(1, entity2, nWETH, salePrice, entity1, saleAmount); - assertOfferFilled(2, entity1, entity1, saleAmount, nWETH, salePrice); + assertOfferFilled(1, entity2, wethId, salePrice, entity1, saleAmount); + assertOfferFilled(2, entity1, entity1, saleAmount, wethId, salePrice); } } @@ -430,9 +424,9 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { writeTokenBalance(signer2, naymsAddress, wethAddress, 1_000 ether); nayms.externalDeposit(wethAddress, 1_000 ether); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt - 200 ether, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt - 200 ether, entity1, dt.entity1MintAndSaleAmt); - vm.expectRevert("_internalBurn: tokens for sale in mkt"); + vm.expectRevert("_internalBurn: tokens locked"); nayms.externalWithdrawFromEntity(entity2, signer2, wethAddress, 500 ether); uint256 lastOfferId = nayms.getLastOfferId(); @@ -442,12 +436,12 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { assertEq(offer.state, LibConstants.OFFER_STATE_CANCELLED); nayms.externalWithdrawFromEntity(entity2, signer2, wethAddress, 500 ether); - uint256 balanceAfterWithdraw = nayms.internalBalanceOf(entity2, nWETH); + uint256 balanceAfterWithdraw = nayms.internalBalanceOf(entity2, wethId); assertEq(balanceAfterWithdraw, 500 ether); } function testGetBestOfferId() public { - assertEq(nayms.getBestOfferId(nWETH, entity1), 0, "invalid best offer, when no offer exists"); + assertEq(nayms.getBestOfferId(wethId, entity1), 0, "invalid best offer, when no offer exists"); testStartTokenSale(); @@ -473,21 +467,21 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { vm.stopPrank(); vm.startPrank(signer2); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt - 200 ether, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt - 200 ether, entity1, dt.entity1MintAndSaleAmt); vm.stopPrank(); vm.startPrank(signer3); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt - 150 ether, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt - 150 ether, entity1, dt.entity1MintAndSaleAmt); vm.stopPrank(); // last offer at this point will be the actual best offer uint256 bestOfferID = nayms.getLastOfferId(); vm.startPrank(signer4); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt - 190 ether, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt - 190 ether, entity1, dt.entity1MintAndSaleAmt); vm.stopPrank(); // confirm best offer - assertEq(bestOfferID, nayms.getBestOfferId(nWETH, entity1), "Not the best offer"); + assertEq(bestOfferID, nayms.getBestOfferId(wethId, entity1), "Not the best offer"); } function testOfferValidation() public { @@ -495,7 +489,7 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { vm.startPrank(account9); vm.expectRevert("must belong to entity to make an offer"); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt, entity1, dt.entity1MintAndSaleAmt); vm.stopPrank(); // init taker entity @@ -507,33 +501,33 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { nayms.externalDeposit(wethAddress, 1_000 ether); vm.expectRevert("sell amount exceeds uint128 limit"); - nayms.executeLimitOffer(nWETH, 2**128 + 1000, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, 2**128 + 1000, entity1, dt.entity1MintAndSaleAmt); vm.expectRevert("buy amount exceeds uint128 limit"); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt, entity1, 2**128 + 1000); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt, entity1, 2**128 + 1000); vm.expectRevert("sell amount must be >0"); - nayms.executeLimitOffer(nWETH, 0, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, 0, entity1, dt.entity1MintAndSaleAmt); vm.expectRevert("buy amount must be >0"); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt, entity1, 0); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt, entity1, 0); vm.expectRevert("sell token must be valid"); nayms.executeLimitOffer("", dt.entity1MintAndSaleAmt, entity1, dt.entity1MintAndSaleAmt); vm.expectRevert("buy token must be valid"); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt, "", dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt, "", dt.entity1MintAndSaleAmt); vm.expectRevert("must be one platform token"); // 2 non-platform tokens - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt, nWBTC, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt, wbtcId, dt.entity1MintAndSaleAmt); vm.expectRevert("cannot sell and buy same token"); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt, nWETH, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt, wethId, dt.entity1MintAndSaleAmt); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt - 10 ether, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt - 10 ether, entity1, dt.entity1MintAndSaleAmt); - vm.expectRevert("tokens locked in market"); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt, entity1, dt.entity1MintAndSaleAmt); + vm.expectRevert("tokens locked"); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt, entity1, dt.entity1MintAndSaleAmt); uint256 lastOfferId = nayms.getLastOfferId(); nayms.cancelOffer(lastOfferId); @@ -566,17 +560,17 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { writeTokenBalance(signer2, naymsAddress, wethAddress, dt.entity2ExternalDepositAmt * 2); nayms.externalDeposit(wethAddress, dt.entity2ExternalDepositAmt * 2); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt * 2, entity1, dt.entity1MintAndSaleAmt * 2); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt * 2, entity1, dt.entity1MintAndSaleAmt * 2); vm.stopPrank(); - assertOfferPartiallyFilled(2, entity2, nWETH, dt.entity1MintAndSaleAmt, dt.entity1MintAndSaleAmt * 2, entity1, dt.entity1MintAndSaleAmt, dt.entity1MintAndSaleAmt * 2); + assertOfferPartiallyFilled(2, entity2, wethId, dt.entity1MintAndSaleAmt, dt.entity1MintAndSaleAmt * 2, entity1, dt.entity1MintAndSaleAmt, dt.entity1MintAndSaleAmt * 2); // start another nENTITY1 token sale nayms.startTokenSale(entity1, dt.entity1MintAndSaleAmt, dt.entity1SalePrice); - assertOfferFilled(1, entity1, entity1, dt.entity1MintAndSaleAmt, nWETH, dt.entity1SalePrice); - assertOfferFilled(2, entity2, nWETH, dt.entity1MintAndSaleAmt * 2, entity1, dt.entity1SalePrice * 2); - assertOfferFilled(3, entity1, entity1, dt.entity1MintAndSaleAmt, nWETH, dt.entity1SalePrice); + assertOfferFilled(1, entity1, entity1, dt.entity1MintAndSaleAmt, wethId, dt.entity1SalePrice); + assertOfferFilled(2, entity2, wethId, dt.entity1MintAndSaleAmt * 2, entity1, dt.entity1SalePrice * 2); + assertOfferFilled(3, entity1, entity1, dt.entity1MintAndSaleAmt, wethId, dt.entity1SalePrice); } function testBestOffersWithCancel() public { @@ -604,34 +598,34 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { vm.stopPrank(); vm.startPrank(signer2); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt - 200 ether, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt - 200 ether, entity1, dt.entity1MintAndSaleAmt); vm.stopPrank(); vm.startPrank(signer3); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt - 300 ether, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt - 300 ether, entity1, dt.entity1MintAndSaleAmt); vm.stopPrank(); vm.startPrank(signer4); - nayms.executeLimitOffer(nWETH, dt.entity1MintAndSaleAmt - 400 ether, entity1, dt.entity1MintAndSaleAmt); + nayms.executeLimitOffer(wethId, dt.entity1MintAndSaleAmt - 400 ether, entity1, dt.entity1MintAndSaleAmt); vm.stopPrank(); vm.startPrank(signer4); nayms.cancelOffer(4); vm.stopPrank(); - assertEq(nayms.getBestOfferId(nWETH, entity1), 2, "invalid best offer ID"); + assertEq(nayms.getBestOfferId(wethId, entity1), 2, "invalid best offer ID"); vm.startPrank(signer2); nayms.cancelOffer(2); vm.stopPrank(); - assertEq(nayms.getBestOfferId(nWETH, entity1), 3, "invalid best offer ID"); + assertEq(nayms.getBestOfferId(wethId, entity1), 3, "invalid best offer ID"); vm.startPrank(signer3); nayms.cancelOffer(3); vm.stopPrank(); - assertEq(nayms.getBestOfferId(nWETH, entity1), 0, "invalid best offer ID"); + assertEq(nayms.getBestOfferId(wethId, entity1), 0, "invalid best offer ID"); } function assertOfferFilled( @@ -696,7 +690,7 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { testStartTokenSale(); bytes32 makerId = account0Id; bytes32 takerId = entity1; - bytes32 tokenId = nWETH; + bytes32 tokenId = wethId; uint256 requestedBuyAmount = 10_000; (success, result) = address(nayms).call(abi.encodeWithSelector(libFeeRouterFixture.payTradingCommissions.selector, makerId, takerId, tokenId, requestedBuyAmount)); @@ -706,9 +700,9 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { bytes32 ndfId = LibHelpers._stringToBytes32(LibConstants.NDF_IDENTIFIER); bytes32 stakingId = LibHelpers._stringToBytes32(LibConstants.STM_IDENTIFIER); - // assertEq(nayms.internalBalanceOf(naymsLtdId, nWETH), tc.commissionNaymsLtd, "balance of naymsLtd should have INCREASED (trading commissions)"); - // assertEq(nayms.internalBalanceOf(ndfId, nWETH), tc.commissionNDF, "balance of ndfId should have INCREASED (trading commissions)"); - // assertEq(nayms.internalBalanceOf(stakingId, nWETH), tc.commissionSTM, "balance of stakingId should have INCREASED (trading commissions)"); + assertEq(nayms.internalBalanceOf(naymsLtdId, wethId), tc.commissionNaymsLtd, "balance of naymsLtd should have INCREASED (trading commissions)"); + assertEq(nayms.internalBalanceOf(ndfId, wethId), tc.commissionNDF, "balance of ndfId should have INCREASED (trading commissions)"); + assertEq(nayms.internalBalanceOf(stakingId, wethId), tc.commissionSTM, "balance of stakingId should have INCREASED (trading commissions)"); (success, result) = address(nayms).call(abi.encodeWithSelector(libFeeRouterFixture.getTradingCommissionsBasisPointsFixture.selector)); } @@ -742,18 +736,65 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { vm.startPrank(signer2); writeTokenBalance(signer2, naymsAddress, wethAddress, e2balance); nayms.externalDeposit(wethAddress, e2balance); - nayms.executeLimitOffer(nWETH, offer2sell, entity1, offer2buy); + nayms.executeLimitOffer(wethId, offer2sell, entity1, offer2buy); vm.stopPrank(); // half should match so we should be left with offer 2 partially matched // 2000 WETH -> 2000 pTokens - assertOfferPartiallyFilled(2, entity2, nWETH, offer1buy, offer2sell, entity1, offer1sell, offer2buy); + assertOfferPartiallyFilled(2, entity2, wethId, offer1buy, offer2sell, entity1, offer1sell, offer2buy); // OFFER 3: 2000 pTokens -> 1000 WETH nayms.startTokenSale(entity1, offer3sell, offer3buy); - assertOfferFilled(1, entity1, entity1, offer1sell, nWETH, offer1buy); - assertOfferFilled(2, entity2, nWETH, offer2sell, entity1, offer2buy); - assertOfferFilled(3, entity1, entity1, offer3sell, nWETH, offer3buy); + assertOfferFilled(1, entity1, entity1, offer1sell, wethId, offer1buy); + assertOfferFilled(2, entity2, wethId, offer2sell, entity1, offer2buy); + assertOfferFilled(3, entity1, entity1, offer3sell, wethId, offer3buy); + } + + function testNotAbleToTradeWithLockedFunds() public { + uint256 salePrice = 100 ether; + uint256 saleAmount = 100 ether; + + nayms.addSupportedExternalToken(wethAddress); + + nayms.createEntity(entity1, signer1Id, initEntity(weth, collateralRatio_500, salePrice, salePrice, true), "test"); + nayms.createEntity(entity2, signer2Id, initEntity(weth, collateralRatio_500, salePrice, salePrice, true), "test"); + + // init test funds to maxint + writeTokenBalance(account0, naymsAddress, wethAddress, ~uint256(0)); + + uint256 e2Balance = (salePrice * (LibConstants.BP_FACTOR + c.tradingCommissionTotalBP)) / LibConstants.BP_FACTOR; + + vm.startPrank(signer2); + writeTokenBalance(signer2, naymsAddress, wethAddress, e2Balance); + nayms.externalDeposit(wethAddress, e2Balance); + vm.stopPrank(); + + // sell x nENTITY1 for y WETH + nayms.enableEntityTokenization(entity1, "e1token"); + nayms.startTokenSale(entity1, saleAmount, salePrice); + + vm.prank(signer2); + nayms.executeLimitOffer(wethId, salePrice, entity1, saleAmount); + + assertOfferFilled(1, entity1, entity1, saleAmount, wethId, salePrice); + assertEq(nayms.internalBalanceOf(entity1, wethId), saleAmount, "balance should have INCREASED"); // has 100 weth + + // assign entity admin + nayms.assignRole(account0Id, entity1, LibConstants.ROLE_ENTITY_ADMIN); + assertTrue(nayms.isInGroup(account0Id, entity1, LibConstants.GROUP_ENTITY_ADMINS)); + + assertEq(nayms.getLockedBalance(entity1, wethId), 0, "locked balance should be 0"); + + bytes32 policyId1 = "policy1"; + uint256 policyLimit = 85 ether; + + (Stakeholders memory stakeholders, SimplePolicy memory policy) = initPolicyWithLimit(policyId1, policyLimit); + nayms.createSimplePolicy(policyId1, entity1, stakeholders, policy, "test"); + + assertEq(nayms.getLockedBalance(entity1, wethId), (policyLimit * collateralRatio_500) / LibConstants.BP_FACTOR, "locked balance should increase"); + + vm.expectRevert("tokens locked"); + nayms.executeLimitOffer(entity1, salePrice, wethId, saleAmount); } } diff --git a/test/defaults/D02TestSetup.sol b/test/defaults/D02TestSetup.sol index d556d094..3b96db58 100644 --- a/test/defaults/D02TestSetup.sol +++ b/test/defaults/D02TestSetup.sol @@ -12,16 +12,23 @@ contract D02TestSetup is D01Deployment { //// test tokens //// MockERC20 public weth; address public wethAddress; + bytes32 public wethId; MockERC20 public wbtc; address public wbtcAddress; + bytes32 public wbtcId; function setUp() public virtual override { super.setUp(); + weth = new MockERC20("Wrapped ETH", "WETH", 18); - wbtc = new MockERC20("Wrapped BTC", "WBTC", 18); wethAddress = address(weth); + wethId = LibHelpers._getIdForAddress(wethAddress); + + wbtc = new MockERC20("Wrapped BTC", "WBTC", 18); wbtcAddress = address(wbtc); + wbtcId = LibHelpers._getIdForAddress(wbtcAddress); + vm.label(wethAddress, "WETH"); vm.label(wbtcAddress, "WBTC"); } diff --git a/test/defaults/D03ProtocolDefaults.sol b/test/defaults/D03ProtocolDefaults.sol index f6821d99..a14ad35e 100644 --- a/test/defaults/D03ProtocolDefaults.sol +++ b/test/defaults/D03ProtocolDefaults.sol @@ -3,12 +3,13 @@ pragma solidity >=0.8.13; import { D02TestSetup, console2, LibHelpers, LibConstants, LibAdmin, LibObject } from "./D02TestSetup.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; -import { Entity } from "src/diamonds/nayms/interfaces/FreeStructs.sol"; +import { Entity, SimplePolicy, SimplePolicyInfo, Stakeholders } from "src/diamonds/nayms/interfaces/FreeStructs.sol"; + +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; /// @notice Default test setup part 03 /// Protocol / project level defaults /// Setup internal token IDs, entities, - contract D03ProtocolDefaults is D02TestSetup { bytes32 public immutable account0Id = LibHelpers._getIdForAddress(address(this)); bytes32 public naymsTokenId; @@ -105,4 +106,52 @@ contract D03ProtocolDefaults is D02TestSetup { e.utilizedCapacity = _utilizedCapacity; e.simplePolicyEnabled = _simplePolicyEnabled; } + + function initPolicy(bytes32 policyId) internal returns (Stakeholders memory policyStakeholders, SimplePolicy memory policy) { + return initPolicyWithLimit(policyId, 10_000); + } + + function initPolicyWithLimit(bytes32 policyId, uint256 limitAmount) internal returns (Stakeholders memory policyStakeholders, SimplePolicy memory policy) { + bytes32[] memory roles = new bytes32[](4); + roles[0] = LibHelpers._stringToBytes32(LibConstants.ROLE_UNDERWRITER); + roles[1] = LibHelpers._stringToBytes32(LibConstants.ROLE_BROKER); + roles[2] = LibHelpers._stringToBytes32(LibConstants.ROLE_CAPITAL_PROVIDER); + roles[3] = LibHelpers._stringToBytes32(LibConstants.ROLE_INSURED_PARTY); + + bytes32[] memory entityIds = new bytes32[](4); + entityIds[0] = DEFAULT_UNDERWRITER_ENTITY_ID; + entityIds[1] = DEFAULT_BROKER_ENTITY_ID; + entityIds[2] = DEFAULT_CAPITAL_PROVIDER_ENTITY_ID; + entityIds[3] = DEFAULT_INSURED_PARTY_ENTITY_ID; + + bytes[] memory signatures = new bytes[](4); + signatures[0] = initSig(0xACC2, policyId); + signatures[1] = initSig(0xACC1, policyId); + signatures[2] = initSig(0xACC3, policyId); + signatures[3] = initSig(0xACC4, policyId); + + policyStakeholders = Stakeholders(roles, entityIds, signatures); + + bytes32[] memory commissionReceivers = new bytes32[](3); + commissionReceivers[0] = DEFAULT_UNDERWRITER_ENTITY_ID; + commissionReceivers[1] = DEFAULT_BROKER_ENTITY_ID; + commissionReceivers[2] = DEFAULT_CAPITAL_PROVIDER_ENTITY_ID; + + uint256[] memory commissions = new uint256[](3); + commissions[0] = 10; + commissions[1] = 10; + commissions[2] = 10; + + policy.startDate = 1000; + policy.maturationDate = 10000; + policy.asset = wethId; + policy.commissionReceivers = commissionReceivers; + policy.commissionBasisPoints = commissions; + policy.limit = limitAmount; + } + + function initSig(uint256 account, bytes32 policyId) internal returns (bytes memory sig_) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(account, ECDSA.toEthSignedMessageHash(policyId)); + sig_ = abi.encodePacked(r, s, v); + } }