diff --git a/brownie-config.yaml b/brownie-config.yaml index 8ea60df..557acd4 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -22,7 +22,7 @@ compiler: remappings: - "@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.7.0" - "@chainlink=smartcontractkit/chainlink@1.6.0" - - "@etherisc/gif-interface=etherisc/gif-interface@a12bb3f" + - "@etherisc/gif-interface=etherisc/gif-interface@0f870d9" # packages below will be added to brownie # you may use 'brownie pm list' after 'brownie compile' @@ -31,7 +31,7 @@ dependencies: # github dependency format: /@ - OpenZeppelin/openzeppelin-contracts@4.7.0 - smartcontractkit/chainlink@1.6.0 - - etherisc/gif-interface@a12bb3f + - etherisc/gif-interface@0f870d9 # exclude Ownable when calculating test coverage # https://eth-brownie.readthedocs.io/en/v1.10.3/config.html#exclude_paths diff --git a/contracts/flows/PolicyDefaultFlow.sol b/contracts/flows/PolicyDefaultFlow.sol index 5393103..a8eb138 100644 --- a/contracts/flows/PolicyDefaultFlow.sol +++ b/contracts/flows/PolicyDefaultFlow.sol @@ -1,13 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; +import "../modules/ComponentController.sol"; import "../modules/PoolController.sol"; import "../modules/PolicyController.sol"; import "../modules/TreasuryModule.sol"; import "../shared/WithRegistry.sol"; -// import "../shared/CoreController.sol"; -import "@etherisc/gif-interface/contracts/modules/ILicense.sol"; import "@etherisc/gif-interface/contracts/modules/IPolicy.sol"; import "@etherisc/gif-interface/contracts/modules/IQuery.sol"; import "@etherisc/gif-interface/contracts/modules/IRegistry.sol"; @@ -24,7 +23,6 @@ import "@etherisc/gif-interface/contracts/modules/IPool.sol"; contract PolicyDefaultFlow is WithRegistry - // CoreController { bytes32 public constant NAME = "PolicyDefaultFlow"; @@ -61,8 +59,8 @@ contract PolicyDefaultFlow is external returns(bytes32 processId) { - ILicense license = getLicenseContract(); - uint256 productId = license.getProductId(msg.sender); + ComponentController component = getComponentContract(); + uint256 productId = component.getComponentId(msg.sender); IPolicy policy = getPolicyContract(); processId = policy.createPolicyFlow(owner, productId, metaData); @@ -265,8 +263,8 @@ contract PolicyDefaultFlow is return policy.getPayout(processId, payoutId).data; } - function getLicenseContract() internal view returns (ILicense) { - return ILicense(getContractFromRegistry("License")); + function getComponentContract() internal view returns (ComponentController) { + return ComponentController(getContractFromRegistry("Component")); } function getPoolContract() internal view returns (PoolController) { diff --git a/contracts/modules/ComponentController.sol b/contracts/modules/ComponentController.sol index 715a5b5..ac0decf 100644 --- a/contracts/modules/ComponentController.sol +++ b/contracts/modules/ComponentController.sol @@ -14,7 +14,6 @@ contract ComponentController is mapping(bytes32 => uint256) private _componentIdByName; mapping(address => uint256) private _componentIdByAddress; - mapping(uint256 => IComponent.ComponentType) private _componentType; mapping(uint256 => IComponent.ComponentState) private _componentState; uint256 [] private _products; @@ -68,7 +67,6 @@ contract ComponentController is // update component state _changeState(id, IComponent.ComponentState.Proposed); - _componentType[id] = component.getType(); component.setId(id); // update controller book keeping @@ -196,13 +194,21 @@ contract ComponentController is } function getComponentType(uint256 id) public view returns (IComponent.ComponentType componentType) { - return _componentType[id]; + IComponent component = _componentById[id]; + componentType = component.getType(); } function getComponentState(uint256 id) public view returns (IComponent.ComponentState componentState) { return _componentState[id]; } + function getRequiredRole(IComponent.ComponentType componentType) external returns (bytes32) { + if (componentType == IComponent.ComponentType.Product) { return _access.productOwnerRole(); } + else if (componentType == IComponent.ComponentType.Oracle) { return _access.oracleProviderRole(); } + else if (componentType == IComponent.ComponentType.Riskpool) { return _access.riskpoolKeeperRole(); } + else { revert("ERROR:CCR-008:COMPONENT_TYPE_UNKNOWN"); } + } + function components() public view returns (uint256 count) { return _componentCount; } function products() public view returns (uint256 count) { return _products.length; } function oracles() public view returns (uint256 count) { return _oracles.length; } @@ -226,31 +232,31 @@ contract ComponentController is pure { require(newState != oldState, - "ERROR:CMP-011:SOURCE_AND_TARGET_STATE_IDENTICAL"); + "ERROR:CCR-011:SOURCE_AND_TARGET_STATE_IDENTICAL"); if (oldState == IComponent.ComponentState.Created) { require(newState == IComponent.ComponentState.Proposed, - "ERROR:CMP-012:CREATED_INVALID_TRANSITION"); + "ERROR:CCR-012:CREATED_INVALID_TRANSITION"); } else if (oldState == IComponent.ComponentState.Proposed) { require(newState == IComponent.ComponentState.Active || newState == IComponent.ComponentState.Declined, - "ERROR:CMP-013:PROPOSED_INVALID_TRANSITION"); + "ERROR:CCR-013:PROPOSED_INVALID_TRANSITION"); } else if (oldState == IComponent.ComponentState.Declined) { - revert("ERROR:CMP-014:DECLINED_IS_FINAL_STATE"); + revert("ERROR:CCR-014:DECLINED_IS_FINAL_STATE"); } else if (oldState == IComponent.ComponentState.Active) { require(newState == IComponent.ComponentState.Paused || newState == IComponent.ComponentState.Suspended, - "ERROR:CMP-015:ACTIVE_INVALID_TRANSITION"); + "ERROR:CCR-015:ACTIVE_INVALID_TRANSITION"); } else if (oldState == IComponent.ComponentState.Paused) { require(newState == IComponent.ComponentState.Active || newState == IComponent.ComponentState.Archived, - "ERROR:CMP-016:PAUSED_INVALID_TRANSITION"); + "ERROR:CCR-016:PAUSED_INVALID_TRANSITION"); } else if (oldState == IComponent.ComponentState.Suspended) { require(newState == IComponent.ComponentState.Active || newState == IComponent.ComponentState.Archived, - "ERROR:CMP-017:SUSPENDED_INVALID_TRANSITION"); + "ERROR:CCR-017:SUSPENDED_INVALID_TRANSITION"); } else { - revert("ERROR:CMP-018:INITIAL_STATE_NOT_HANDLED"); + revert("ERROR:CCR-018:INITIAL_STATE_NOT_HANDLED"); } } } diff --git a/contracts/modules/LicenseController.sol b/contracts/modules/LicenseController.sol index 761469d..a911a6a 100644 --- a/contracts/modules/LicenseController.sol +++ b/contracts/modules/LicenseController.sol @@ -20,27 +20,19 @@ contract LicenseController is _component = ComponentController(_getContractAddress("Component")); } + // ensures that calling component (productAddress) is a product function getAuthorizationStatus(address productAddress) public override view returns (uint256 productId, bool isAuthorized, address policyFlow) { - productId = getProductId(productAddress); - isAuthorized = _isValidCall(productAddress); + productId = _component.getComponentId(productAddress); + isAuthorized = _isValidCall(productId); policyFlow = _getProduct(productId).getPolicyFlow(); } - function getProductId(address sender) - public override - view - returns(uint256 productId) - { - productId = _component.getComponentId(sender); - } - - function _isValidCall(address componentAddress) internal view returns (bool) { - uint256 componentId = _component.getComponentId(componentAddress); - return _component.getComponentState(componentId) == IComponent.ComponentState.Active; + function _isValidCall(uint256 productId) internal view returns (bool) { + return _component.getComponentState(productId) == IComponent.ComponentState.Active; } function _getProduct(uint256 id) internal view returns (IProduct product) { diff --git a/contracts/services/ComponentOwnerService.sol b/contracts/services/ComponentOwnerService.sol index 59cb304..d070d39 100644 --- a/contracts/services/ComponentOwnerService.sol +++ b/contracts/services/ComponentOwnerService.sol @@ -18,9 +18,9 @@ contract ComponentOwnerService is modifier onlyOwnerWithRoleFromComponent(IComponent component) { address owner = component.getOwner(); - bytes32 requiredRole = component.getRequiredRole(); - require(_access.hasRole(requiredRole, owner), "ERROR:COS-002:REQUIRED_ROLE_MISSING"); + bytes32 requiredRole = _component.getRequiredRole(component.getType()); require(_msgSender() == owner, "ERROR:COS-001:NOT_OWNER"); + require(_access.hasRole(requiredRole, owner), "ERROR:COS-002:REQUIRED_ROLE_MISSING"); _; } @@ -29,7 +29,7 @@ contract ComponentOwnerService is require(address(component) != address(0), "ERROR:COS-003:COMPONENT_ID_INVALID"); address owner = component.getOwner(); - bytes32 requiredRole = component.getRequiredRole(); + bytes32 requiredRole = _component.getRequiredRole(component.getType()); require(_msgSender() == owner, "ERROR:COS-004:NOT_OWNER"); require(_access.hasRole(requiredRole, owner), "ERROR:COS-005:REQUIRED_ROLE_MISSING"); diff --git a/contracts/services/OracleService.sol b/contracts/services/OracleService.sol index e89d2aa..b02c67c 100644 --- a/contracts/services/OracleService.sol +++ b/contracts/services/OracleService.sol @@ -18,6 +18,7 @@ contract OracleService is } function respond(uint256 _requestId, bytes calldata _data) external override { + // function below enforces msg.sender to be a registered oracle _query.respond(_requestId, _msgSender(), _data); } } diff --git a/contracts/services/ProductService.sol b/contracts/services/ProductService.sol index 0058f75..afc9228 100644 --- a/contracts/services/ProductService.sol +++ b/contracts/services/ProductService.sol @@ -18,6 +18,7 @@ contract ProductService is constructor(address _registry) WithRegistry(_registry) {} fallback() external { + // getAuthorizationStatus enforces msg.sender to be a registered product (uint256 id, bool isAuthorized, address policyFlow) = _license().getAuthorizationStatus(_msgSender()); require(isAuthorized, "ERROR:PRS-001:NOT_AUTHORIZED"); diff --git a/contracts/test/TestCompromisedProduct.sol b/contracts/test/TestCompromisedProduct.sol index 6e6e917..ff89606 100644 --- a/contracts/test/TestCompromisedProduct.sol +++ b/contracts/test/TestCompromisedProduct.sol @@ -146,12 +146,6 @@ contract TestCompromisedProduct is function getState() external override view returns(ComponentState) { return IComponent.ComponentState.Active; } function getOwner() external override view returns(address) { return owner(); } - function getRequiredRole() public override view returns (bytes32) { - if (isProduct()) { return _access.productOwnerRole(); } - if (isOracle()) { return _access.oracleProviderRole(); } - if (isRiskpool()) { return _access.riskpoolKeeperRole(); } - } - function isProduct() public override view returns(bool) { return true; } function isOracle() public override view returns(bool) { return false; } function isRiskpool() public override view returns(bool) { return false; } diff --git a/tests/test_deploy_instance.py b/tests/test_deploy_instance.py index 724d83f..eac119c 100644 --- a/tests/test_deploy_instance.py +++ b/tests/test_deploy_instance.py @@ -90,7 +90,7 @@ def test_License(instance: GifInstance, owner): assert license.address != 0x0 with brownie.reverts("ERROR:CCR-007:COMPONENT_UNKNOWN"): - license.getProductId(owner) + license.getAuthorizationStatus(registry.address) with pytest.raises(AttributeError): assert license.foo({'from': owner}) diff --git a/tests/test_riskpool_lifecycle.py b/tests/test_riskpool_lifecycle.py index 1c1241f..f848d24 100644 --- a/tests/test_riskpool_lifecycle.py +++ b/tests/test_riskpool_lifecycle.py @@ -400,7 +400,7 @@ def test_suspend_archive( componentOwnerService.archive(riskpoolId, {'from':riskpoolKeeper}) # ensure that component owner may not resume riskpool - with brownie.reverts("ERROR:CMP-018:INITIAL_STATE_NOT_HANDLED"): + with brownie.reverts("ERROR:CCR-018:INITIAL_STATE_NOT_HANDLED"): instanceOperatorService.resume(riskpoolId, {'from':owner}) # ensure that instance operator may not archive archived riskpool @@ -504,7 +504,7 @@ def test_pause_archive_as_owner( riskpool.createBundle(bytes(0), 50, {'from':bundleOwner}) # ensure that owner may not unpause archived riskpool - with brownie.reverts("ERROR:CMP-018:INITIAL_STATE_NOT_HANDLED"): + with brownie.reverts("ERROR:CCR-018:INITIAL_STATE_NOT_HANDLED"): componentOwnerService.unpause(riskpoolId, {'from':riskpoolKeeper}) assert instanceService.getComponentState(riskpoolId) == 6 @@ -569,7 +569,7 @@ def test_pause_archive_as_instance_operator( riskpool.createBundle(bytes(0), 50, {'from':bundleOwner}) # ensure that owner may not unpause archived riskpool - with brownie.reverts("ERROR:CMP-018:INITIAL_STATE_NOT_HANDLED"): + with brownie.reverts("ERROR:CCR-018:INITIAL_STATE_NOT_HANDLED"): componentOwnerService.unpause(riskpoolId, {'from':riskpoolKeeper}) assert instanceService.getComponentState(riskpoolId) == 6 @@ -632,19 +632,19 @@ def test_propose_decline( assert instanceService.getComponentState(riskpoolId) == 2 # ensure that declined riskpool cannot be approved - with brownie.reverts("ERROR:CMP-014:DECLINED_IS_FINAL_STATE"): + with brownie.reverts("ERROR:CCR-014:DECLINED_IS_FINAL_STATE"): instanceOperatorService.approve( riskpoolId, {'from': instance.getOwner()}) # ensure that declined riskpool cannot be suspended - with brownie.reverts("ERROR:CMP-014:DECLINED_IS_FINAL_STATE"): + with brownie.reverts("ERROR:CCR-014:DECLINED_IS_FINAL_STATE"): instanceOperatorService.suspend( riskpoolId, {'from': instance.getOwner()}) # ensure that declined riskpool cannot be resumed - with brownie.reverts("ERROR:CMP-014:DECLINED_IS_FINAL_STATE"): + with brownie.reverts("ERROR:CCR-014:DECLINED_IS_FINAL_STATE"): instanceOperatorService.resume( riskpoolId, {'from': instance.getOwner()}) @@ -656,13 +656,13 @@ def test_propose_decline( {'from': instance.getOwner()}) # ensure that declined riskpool cannot be paused - with brownie.reverts("ERROR:CMP-014:DECLINED_IS_FINAL_STATE"): + with brownie.reverts("ERROR:CCR-014:DECLINED_IS_FINAL_STATE"): componentOwnerService.pause( riskpoolId, {'from': riskpoolKeeper}) # ensure that declined riskpool cannot be unpaused - with brownie.reverts("ERROR:CMP-014:DECLINED_IS_FINAL_STATE"): + with brownie.reverts("ERROR:CCR-014:DECLINED_IS_FINAL_STATE"): componentOwnerService.unpause( riskpoolId, {'from': riskpoolKeeper})