Skip to content

Commit

Permalink
Simple policy signing updates (#46)
Browse files Browse the repository at this point in the history
* feat: create hash for signing from attributes and offchain data hash

* test: fix simple policy tests for new signing procedure

* chore: remove test menemonic

* test: fix simple policy validation signatures

* doc: update natspec generated markdown

* fix: policy stakeholder verification

* chore: rename variables

* test: gain back some coverage on LibERC20

* chore: bump npm package version
  • Loading branch information
amarinkovic authored Jan 19, 2023
1 parent b68ad6c commit 3a0523e
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 80 deletions.
21 changes: 21 additions & 0 deletions docs/facets/ISimplePolicyFacet.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,24 @@ Cancel a simple policy
| --- | --- | --- |
|`_policyId` | bytes32 | Id of the simple policy|
<br></br>
### getSigningHash
No description
Generate a simple policy hash for singing by the stakeholders
```solidity
function getSigningHash(
uint256 _startDate,
uint256 _maturationDate,
bytes32 _asset,
uint256 _limit,
bytes32 _dataHash
) external returns (bytes32)
```
#### Arguments:
| Argument | Type | Description |
| --- | --- | --- |
|`_startDate` | uint256 | Date when policy becomes active
|`_maturationDate` | uint256 | Date after which policy becomes matured
|`_asset` | bytes32 | ID of the underlying asset, used as collateral and to pay out claims
|`_limit` | uint256 | Policy coverage limit
|`_dataHash` | bytes32 | Hash of all the important policy data stored offchain|
<br></br>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nayms/contracts",
"version": "3.3.1",
"version": "3.3.2",
"main": "index.js",
"repository": "https://github.com/nayms/contracts-v3.git",
"author": "Kevin Park <[email protected]>",
Expand Down
18 changes: 18 additions & 0 deletions src/diamonds/nayms/facets/SimplePolicyFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,22 @@ contract SimplePolicyFacet is ISimplePolicyFacet, Modifiers {
function cancelSimplePolicy(bytes32 _policyId) external assertSysMgr {
LibSimplePolicy._cancel(_policyId);
}

/**
* @dev Generate a simple policy hash for singing by the stakeholders
* @param _startDate Date when policy becomes active
* @param _maturationDate Date after which policy becomes matured
* @param _asset ID of the underlying asset, used as collateral and to pay out claims
* @param _limit Policy coverage limit
* @param _offchainDataHash Hash of all the important policy data stored offchain
*/
function getSigningHash(
uint256 _startDate,
uint256 _maturationDate,
bytes32 _asset,
uint256 _limit,
bytes32 _offchainDataHash
) external returns (bytes32) {
return LibSimplePolicy._getSigningHash(_startDate, _maturationDate, _asset, _limit, _offchainDataHash);
}
}
16 changes: 16 additions & 0 deletions src/diamonds/nayms/interfaces/ISimplePolicyFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,20 @@ interface ISimplePolicyFacet {
* @param _policyId Id of the simple policy
*/
function cancelSimplePolicy(bytes32 _policyId) external;

/**
* @dev Generate a simple policy hash for singing by the stakeholders
* @param _startDate Date when policy becomes active
* @param _maturationDate Date after which policy becomes matured
* @param _asset ID of the underlying asset, used as collateral and to pay out claims
* @param _limit Policy coverage limit
* @param _offchainDataHash Hash of all the important policy data stored offchain
*/
function getSigningHash(
uint256 _startDate,
uint256 _maturationDate,
bytes32 _asset,
uint256 _limit,
bytes32 _offchainDataHash
) external returns (bytes32);
}
18 changes: 9 additions & 9 deletions src/diamonds/nayms/libs/LibEntity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { LibObject } from "./LibObject.sol";
import { LibACL } from "./LibACL.sol";
import { LibTokenizedVault } from "./LibTokenizedVault.sol";
import { LibMarket } from "./LibMarket.sol";
import { LibSimplePolicy } from "./LibSimplePolicy.sol";
import { LibEIP712 } from "src/diamonds/nayms/libs/LibEIP712.sol";

import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
Expand Down Expand Up @@ -85,7 +86,7 @@ library LibEntity {
bytes32 _entityId,
Stakeholders calldata _stakeholders,
SimplePolicy calldata _simplePolicy,
bytes32 _dataHash
bytes32 _offchainDataHash
) internal {
if (_policyId == 0) {
revert PolicyIdCannotBeZero();
Expand All @@ -105,30 +106,29 @@ library LibEntity {
entity.utilizedCapacity += factoredLimit;
s.lockedBalances[_entityId][entity.assetId] += factoredLimit;

LibObject._createObject(_policyId, _entityId, _dataHash);
// hash contents are implicitlly checked by making sure that resolved signer is the stakeholder entity's admin
bytes32 signingHash = LibSimplePolicy._getSigningHash(_simplePolicy.startDate, _simplePolicy.maturationDate, _simplePolicy.asset, _simplePolicy.limit, _offchainDataHash);

LibObject._createObject(_policyId, _entityId, signingHash);
s.simplePolicies[_policyId] = _simplePolicy;
s.simplePolicies[_policyId].fundsLocked = true;

uint256 rolesCount = _stakeholders.roles.length;
address signer;
bytes32 signerId;
address previousSigner;

bytes32 structHash;

for (uint256 i = 0; i < rolesCount; i++) {
structHash = keccak256(abi.encode(keccak256("PolicyHash(bytes32 signerEntityId, bytes32 dataHash))"), _stakeholders.entityIds[i], _dataHash));
previousSigner = signer;

signer = ECDSA.recover(LibEIP712._hashTypedDataV4(structHash), _stakeholders.signatures[i]);
signer = ECDSA.recover(signingHash, _stakeholders.signatures[i]);

// Ensure there are no duplicate signers.
if (previousSigner >= signer) {
revert DuplicateSignerCreatingSimplePolicy(previousSigner, signer);
}
signerId = LibHelpers._getIdForAddress(signer);

require(LibACL._isInGroup(signerId, _stakeholders.entityIds[i], LibHelpers._stringToBytes32(LibConstants.GROUP_ENTITY_ADMINS)), "invalid stakeholder");
require(LibObject._getParentFromAddress(signer) == _stakeholders.entityIds[i], "invalid stakeholder");

LibACL._assignRole(_stakeholders.entityIds[i], _policyId, _stakeholders.roles[i]);
}

Expand Down
24 changes: 24 additions & 0 deletions src/diamonds/nayms/libs/LibSimplePolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { LibObject } from "./LibObject.sol";
import { LibTokenizedVault } from "./LibTokenizedVault.sol";
import { LibFeeRouter } from "./LibFeeRouter.sol";
import { LibHelpers } from "./LibHelpers.sol";
import { LibEIP712 } from "src/diamonds/nayms/libs/LibEIP712.sol";

import { EntityDoesNotExist, PolicyDoesNotExist } from "src/diamonds/nayms/interfaces/CustomErrors.sol";

library LibSimplePolicy {
Expand Down Expand Up @@ -110,4 +112,26 @@ library LibSimplePolicy {

simplePolicy.fundsLocked = false;
}

function _getSigningHash(
uint256 _startDate,
uint256 _maturationDate,
bytes32 _asset,
uint256 _limit,
bytes32 _offchainDataHash
) internal returns (bytes32) {
return
LibEIP712._hashTypedDataV4(
keccak256(
abi.encode(
keccak256("SimplePolicy(uint256 startDate,uint256 maturationDate,bytes32 asset,uint256 limit,bytes32 offchainDataHash)"),
_startDate,
_maturationDate,
_asset,
_limit,
_offchainDataHash
)
)
);
}
}
31 changes: 31 additions & 0 deletions test/T01LibERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Vm } from "forge-std/Vm.sol";
import { D03ProtocolDefaults, console2 } from "./defaults/D03ProtocolDefaults.sol";
import { DummyToken } from "./utils/DummyToken.sol";
import { LibERC20Fixture } from "./fixtures/LibERC20Fixture.sol";
import { IDiamondCut } from "src/diamonds/nayms/INayms.sol";

contract T01LibERC20 is D03ProtocolDefaults {
DummyToken private token;
Expand All @@ -22,6 +23,32 @@ contract T01LibERC20 is D03ProtocolDefaults {

fixture = new LibERC20Fixture();
fixtureAddress = address(fixture);

bytes4[] memory funcSelectors = new bytes4[](2);
funcSelectors[0] = fixture.decimals.selector;
funcSelectors[1] = fixture.balanceOf.selector;

IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);
cut[0] = IDiamondCut.FacetCut({ facetAddress: address(fixture), action: IDiamondCut.FacetCutAction.Add, functionSelectors: funcSelectors });

nayms.diamondCut(cut, address(0), "");
}

function getDecimals(address tokenAddress) internal returns (uint8) {
(bool success, bytes memory result) = address(nayms).call(abi.encodeWithSelector(fixture.decimals.selector, tokenAddress));
require(success, "Should get token decimals via library fixture");
return abi.decode(result, (uint8));
}

function getBalanceOf(address _token, address _who) internal returns (uint256) {
(bool success, bytes memory result) = address(nayms).call(abi.encodeWithSelector(fixture.balanceOf.selector, _token, _who));
require(success, "Should get holders balance via library fixture");
return abi.decode(result, (uint256));
}

function testBalanceOf() public {
token.mint(fixtureAddress, 100);
assertEq(getBalanceOf(tokenAddress, fixtureAddress), 100, "invalid balance of");
}

function testTransfer() public {
Expand Down Expand Up @@ -77,4 +104,8 @@ contract T01LibERC20 is D03ProtocolDefaults {
assertEq(token.balanceOf(signer1), 0);
assertEq(token.balanceOf(account0), 100);
}

function testDecimals() public {
assertEq(getDecimals(tokenAddress), 18, "invalid decimals");
}
}
47 changes: 24 additions & 23 deletions test/T04Entity.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.17;

import { Vm } from "forge-std/Vm.sol";

import { D03ProtocolDefaults, console2, LibConstants, LibHelpers } from "./defaults/D03ProtocolDefaults.sol";
import { D03ProtocolDefaults, console2, LibConstants, LibHelpers, LibObject } from "./defaults/D03ProtocolDefaults.sol";
import { Entity, MarketInfo, SimplePolicy, SimplePolicyInfo, Stakeholders } from "src/diamonds/nayms/interfaces/FreeStructs.sol";
import { INayms, IDiamondCut } from "src/diamonds/nayms/INayms.sol";

Expand Down Expand Up @@ -32,9 +32,6 @@ contract T04EntityTest is D03ProtocolDefaults {

account9 = vm.addr(0xACC9);
account9Id = LibHelpers._getIdForAddress(account9);
// bytes32 structHash = keccak256(abi.encode(keccak256("PolicyHash(bytes32 dataHash))"), testPolicyDataHash));

// policyHashedTypedData = nayms.hashTypedDataV4(structHash);

(stakeholders, simplePolicy) = initPolicy(testPolicyDataHash);

Expand Down Expand Up @@ -221,9 +218,11 @@ contract T04EntityTest is D03ProtocolDefaults {
writeTokenBalance(account0, naymsAddress, wethAddress, 100000);
nayms.externalDeposit(wethAddress, 100000);

bytes32 signingHash = nayms.getSigningHash(simplePolicy.startDate, simplePolicy.maturationDate, simplePolicy.asset, simplePolicy.limit, testPolicyDataHash);

bytes[] memory signatures = new bytes[](2);
signatures[0] = initPolicySig(0xACC1, eAlice, testPolicyDataHash); // 0x2337f702bc9A7D1f415050365634FEbEdf4054Be
signatures[1] = initPolicySig(0xACC2, eBob, testPolicyDataHash); // 0x167D6b35e51df22f42c4F42f26d365756D244fDE
signatures[0] = initPolicySig(0xACC1, signingHash); // 0x2337f702bc9A7D1f415050365634FEbEdf4054Be
signatures[1] = initPolicySig(0xACC2, signingHash); // 0x167D6b35e51df22f42c4F42f26d365756D244fDE

bytes32[] memory roles = new bytes32[](2);
roles[0] = LibHelpers._stringToBytes32(LibConstants.ROLE_UNDERWRITER);
Expand All @@ -242,8 +241,6 @@ contract T04EntityTest is D03ProtocolDefaults {
function testSignatureWhenCreatingSimplePolicy() public {
nayms.createEntity(entityId1, account0Id, initEntity(wethId, 5000, 10000, true), "entity test hash");

address alice = vm.addr(0xACC2);
address bob = vm.addr(0xACC1);
bytes32 bobId = LibHelpers._getIdForAddress(vm.addr(0xACC1));
bytes32 aliceId = LibHelpers._getIdForAddress(vm.addr(0xACC2));

Expand All @@ -264,9 +261,11 @@ contract T04EntityTest is D03ProtocolDefaults {
writeTokenBalance(account0, naymsAddress, wethAddress, 100000);
nayms.externalDeposit(wethAddress, 100000);

bytes32 signingHash = nayms.getSigningHash(simplePolicy.startDate, simplePolicy.maturationDate, simplePolicy.asset, simplePolicy.limit, testPolicyDataHash);

bytes[] memory signatures = new bytes[](2);
signatures[0] = initPolicySig(0xACC2, eAlice, testPolicyDataHash);
signatures[1] = initPolicySig(0xACC1, eBob, testPolicyDataHash);
signatures[0] = initPolicySig(0xACC2, signingHash);
signatures[1] = initPolicySig(0xACC1, signingHash);

bytes32[] memory roles = new bytes32[](2);
roles[0] = LibHelpers._stringToBytes32(LibConstants.ROLE_UNDERWRITER);
Expand Down Expand Up @@ -302,6 +301,13 @@ contract T04EntityTest is D03ProtocolDefaults {
nayms.createSimplePolicy(policyId1, entityId1, stakeholders, simplePolicy, testPolicyDataHash);
simplePolicy.limit = 100000;

bytes32 signingHash = nayms.getSigningHash(simplePolicy.startDate, simplePolicy.maturationDate, simplePolicy.asset, simplePolicy.limit, testPolicyDataHash);

stakeholders.signatures[0] = initPolicySig(0xACC2, signingHash);
stakeholders.signatures[1] = initPolicySig(0xACC1, signingHash);
stakeholders.signatures[2] = initPolicySig(0xACC3, signingHash);
stakeholders.signatures[3] = initPolicySig(0xACC4, signingHash);

// external token not supported
vm.expectRevert("external token is not supported");
simplePolicy.asset = LibHelpers._getIdForAddress(wbtcAddress);
Expand Down Expand Up @@ -433,35 +439,30 @@ contract T04EntityTest is D03ProtocolDefaults {
assertTrue(p.fundsLocked, "funds locked");
}

function testCreateSimplePolicySignersAreNotEntityAdminsOfStakeholderEntities() public {
function testCreateSimplePolicyStakeholderEntitiesAreNotSignersParent() public {
getReadyToCreatePolicies();

// assign parent entity as system manager so that I can assign roles below
nayms.assignRole(entityId1, systemContext, LibConstants.ROLE_SYSTEM_MANAGER);

bytes32[] memory signerIds = new bytes32[](4);
signerIds[0] = signer1Id;
signerIds[1] = signer1Id;
signerIds[2] = signer1Id;
signerIds[3] = signer1Id;
signerIds[1] = signer2Id;
signerIds[2] = signer3Id;
signerIds[3] = signer4Id;

uint256 rolesCount = 1; //stakeholders.roles.length;
for (uint256 i = 0; i < rolesCount; i++) {
bytes32 signerId = signerIds[i];

// check permissions
assertEq(nayms.getRoleInContext(signerId, stakeholders.entityIds[i]), LibHelpers._stringToBytes32(LibConstants.ROLE_ENTITY_ADMIN), "must have role");
assertTrue(nayms.canAssign(account0Id, signerId, stakeholders.entityIds[i], LibConstants.ROLE_ENTITY_ADMIN), "can assign");
assertEq(nayms.getEntity(signerId), stakeholders.entityIds[i], "must be parent");

// remove role
nayms.unassignRole(signerId, stakeholders.entityIds[i]);
// change parent
nayms.setEntity(signerId, bytes32("e0"));

// try creating
vm.expectRevert("invalid stakeholder");
nayms.createSimplePolicy(policyId1, entityId1, stakeholders, simplePolicy, testPolicyDataHash);

// restore role
nayms.assignRole(signerId, stakeholders.entityIds[i], LibConstants.ROLE_ENTITY_ADMIN);
nayms.setEntity(signerId, stakeholders.entityIds[i]);
}
}

Expand Down
27 changes: 12 additions & 15 deletions test/T04Market.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -761,8 +761,7 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts {

nayms.addSupportedExternalToken(wethAddress);

nayms.createEntity(entity1, signer1Id, initEntity(wethId, collateralRatio_500, salePrice, true), "test");
nayms.createEntity(entity2, signer2Id, initEntity(wethId, collateralRatio_500, salePrice, true), "test");
bytes32 e1Id = DEFAULT_UNDERWRITER_ENTITY_ID;

// init test funds to maxint
writeTokenBalance(account0, naymsAddress, wethAddress, ~uint256(0));
Expand All @@ -775,30 +774,28 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts {
vm.stopPrank();

// sell x nENTITY1 for y WETH
nayms.enableEntityTokenization(entity1, "e1token", "e1token");
nayms.startTokenSale(entity1, saleAmount, salePrice);
nayms.enableEntityTokenization(e1Id, "e1token", "e1token");
nayms.startTokenSale(e1Id, 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
nayms.executeLimitOffer(wethId, salePrice, e1Id, saleAmount);

// assign entity admin
nayms.assignRole(account0Id, entity1, LibConstants.ROLE_ENTITY_ADMIN);
assertTrue(nayms.isInGroup(account0Id, entity1, LibConstants.GROUP_ENTITY_ADMINS));
assertOfferFilled(1, e1Id, e1Id, saleAmount, wethId, salePrice);
assertEq(nayms.internalBalanceOf(e1Id, wethId), saleAmount, "balance should have INCREASED"); // has 100 weth

assertEq(nayms.getLockedBalance(entity1, wethId), 0, "locked balance should be 0");
assertEq(nayms.getLockedBalance(e1Id, wethId), 0, "locked balance should be 0");

bytes32 policyId1 = "policy1";
uint256 policyLimit = 85 ether;

(Stakeholders memory stakeholders, SimplePolicy memory policy) = initPolicyWithLimit(testPolicyDataHash, policyLimit);
nayms.createSimplePolicy(policyId1, entity1, stakeholders, policy, testPolicyDataHash);
nayms.createSimplePolicy(policyId1, e1Id, stakeholders, policy, testPolicyDataHash);

assertEq(nayms.getLockedBalance(entity1, wethId), (policyLimit * collateralRatio_500) / LibConstants.BP_FACTOR, "locked balance should increase");
uint256 lockedBalance = nayms.getLockedBalance(e1Id, wethId);
assertEq(lockedBalance, policyLimit, "locked balance should increase");

vm.expectRevert("tokens locked");
nayms.executeLimitOffer(entity1, salePrice, wethId, saleAmount);
vm.prank(signer1);
nayms.executeLimitOffer(e1Id, salePrice, wethId, saleAmount);
}
}
1 change: 1 addition & 0 deletions test/defaults/D01Deployment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { LibAdmin } from "src/diamonds/nayms/libs/LibAdmin.sol";
import { LibConstants } from "src/diamonds/nayms/libs/LibConstants.sol";
import { LibHelpers } from "src/diamonds/nayms/libs/LibHelpers.sol";
import { LibObject } from "src/diamonds/nayms/libs/LibObject.sol";
import { LibSimplePolicy } from "src/diamonds/nayms/libs/LibSimplePolicy.sol";

import { LibGeneratedNaymsFacetHelpers } from "script/utils/LibGeneratedNaymsFacetHelpers.sol";

Expand Down
Loading

0 comments on commit 3a0523e

Please sign in to comment.