diff --git a/contracts/authorization/AccessAdmin.sol b/contracts/authorization/AccessAdmin.sol index 0f1c0dc40..705dcdab6 100644 --- a/contracts/authorization/AccessAdmin.sol +++ b/contracts/authorization/AccessAdmin.sol @@ -344,15 +344,18 @@ contract AccessAdmin is revert ErrorAuthorizeForAdminRoleInvalid(target); } - bool addFunctions = true; - bytes4[] memory functionSelectors = _processFunctionSelectors(target, functions, addFunctions); + ( + bytes4[] memory functionSelectors, + string[] memory functionNames + ) = _processFunctionSelectors(target, functions, true); // apply authz via access manager _grantRoleAccessToFunctions( target, roleId, - functionSelectors, - false); // allowLockedRoles + functionSelectors, + functionNames, + false); // allow locked roles } function _unauthorizeTargetFunctions( @@ -361,14 +364,17 @@ contract AccessAdmin is ) internal { - bool addFunctions = false; - bytes4[] memory functionSelectors = _processFunctionSelectors(target, functions, addFunctions); + ( + bytes4[] memory functionSelectors, + string[] memory functionNames + ) = _processFunctionSelectors(target, functions, false); _grantRoleAccessToFunctions( target, getAdminRole(), functionSelectors, - true); // allowLockedRoles + functionNames, + true); // allowLockedRoles } function _processFunctionSelectors( @@ -379,11 +385,13 @@ contract AccessAdmin is internal onlyExistingTarget(target) returns ( - bytes4[] memory functionSelectors + bytes4[] memory functionSelectors, + string[] memory functionNames ) { uint256 n = functions.length; functionSelectors = new bytes4[](n); + functionNames = new string[](n); FunctionInfo memory func; Selector selector; @@ -400,6 +408,7 @@ contract AccessAdmin is // add bytes4 selector to function selector array functionSelectors[i] = selector.toBytes4(); + functionNames[i] = func.name.toString(); } } @@ -408,18 +417,27 @@ contract AccessAdmin is address target, RoleId roleId, bytes4[] memory functionSelectors, + string[] memory functionNames, bool allowLockedRoles // admin and public roles are locked ) internal onlyExistingTarget(target) onlyExistingRole(roleId, true, allowLockedRoles) { + _authority.setTargetFunctionRole( target, functionSelectors, RoleId.unwrap(roleId)); - // implizit logging: rely on OpenZeppelin log TargetFunctionRoleUpdated + for (uint256 i = 0; i < functionSelectors.length; i++) { + emit LogAccessAdminFunctionGranted( + target, + // _getAccountName(target), + // string(abi.encodePacked("", functionSelectors[i])), + functionNames[i], + _getRoleName(roleId)); + } } @@ -448,7 +466,7 @@ contract AccessAdmin is account, 0); - // indirect logging: rely on OpenZeppelin log RoleGranted + emit LogAccessAdminRoleGranted(account, _getRoleName(roleId)); } /// @dev revoke the specified role from the provided account @@ -468,7 +486,7 @@ contract AccessAdmin is RoleId.unwrap(roleId), account); - // indirect logging: rely on OpenZeppelin log RoleGranted + emit LogAccessAdminRoleRevoked(account, _roleInfo[roleId].name.toString()); } @@ -527,7 +545,7 @@ contract AccessAdmin is // add role to list of roles _roleIds.push(roleId); - emit LogRoleCreated(roleId, info.roleType, info.adminRoleId, info.name.toString()); + emit LogAccessAdminRoleCreated(roleId, info.roleType, info.adminRoleId, info.name.toString()); } @@ -586,7 +604,7 @@ contract AccessAdmin is // add role to list of roles _targets.push(target); - emit LogTargetCreated(target, targetName); + emit LogAccessAdminTargetCreated(target, targetName); } @@ -620,6 +638,21 @@ contract AccessAdmin is _authority.setTargetClosed(target, locked); } + function _getAccountName(address account) internal view returns (string memory) { + if (targetExists(account)) { + return _targetInfo[account].name.toString(); + } + return ""; + } + + + function _getRoleName(RoleId roleId) internal view returns (string memory) { + if (roleExists(roleId)) { + return _roleInfo[roleId].name.toString(); + } + return ""; + } + function _checkRoleExists( RoleId roleId, @@ -633,10 +666,7 @@ contract AccessAdmin is } uint64 roleIdInt = RoleId.unwrap(roleId); - if (roleIdInt == _authority.ADMIN_ROLE()) - // TODO cleanup - //|| roleIdInt == _authority.PUBLIC_ROLE()) prevents granting of public role - { + if (roleIdInt == _authority.ADMIN_ROLE()) { revert ErrorRoleIsLocked(roleId); } diff --git a/contracts/authorization/Authorization.sol b/contracts/authorization/Authorization.sol index 9eafc5526..ab45f7788 100644 --- a/contracts/authorization/Authorization.sol +++ b/contracts/authorization/Authorization.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import {IAccess} from "./IAccess.sol"; import {IAuthorization} from "./IAuthorization.sol"; -import {ObjectType, ObjectTypeLib} from "../type/ObjectType.sol"; +import {ObjectType, ObjectTypeLib, PRODUCT, ORACLE, DISTRIBUTION, POOL} from "../type/ObjectType.sol"; import {RoleId, RoleIdLib, ADMIN_ROLE} from "../type/RoleId.sol"; import {SelectorLib} from "../type/Selector.sol"; import {Str, StrLib} from "../type/String.sol"; @@ -22,6 +22,10 @@ contract Authorization mapping(ObjectType domain => Str target) internal _serviceTarget; string internal _mainTargetName = "Component"; + string internal _tokenHandlerName = "ComponentTH"; + + Str internal _mainTarget; + Str internal _tokenHandlerTarget; Str[] internal _targets; mapping(Str target => RoleId roleid) internal _targetRole; @@ -34,256 +38,300 @@ contract Authorization mapping(Str target => mapping(RoleId authorizedRole => IAccess.FunctionInfo[] functions)) internal _authorizedFunctions; - constructor(string memory mainTargetName) { + constructor( + string memory mainTargetName, + ObjectType targetDomain + ) + { + // checks + if (bytes(mainTargetName).length == 0) { + revert ErrorAuthorizationMainTargetNameEmpty(); + } + + if (targetDomain.eqz()) { + revert ErrorAuthorizationTargetDomainZero(); + } + + // effects + // setup main target, main role id and main role info _mainTargetName = mainTargetName; + _tokenHandlerName = string(abi.encodePacked(mainTargetName, "TH")); + + RoleId mainRoleId = RoleIdLib.toComponentRoleId(targetDomain, 0); + string memory mainRolName = _toTargetRoleName(mainTargetName); + + _addTargetWithRole( + _mainTargetName, + mainRoleId, + mainRolName); + + _mainTarget = StrLib.toStr(mainTargetName); + _targetRole[_mainTarget] = mainRoleId; + + // add token handler target for components + if (targetDomain == PRODUCT() + || targetDomain == DISTRIBUTION() + || targetDomain == ORACLE() + || targetDomain == POOL() + ) { + _addTarget(_tokenHandlerName); + } + + // setup token handler target + _tokenHandlerTarget = StrLib.toStr(_tokenHandlerName); + // setup use case specific parts _setupServiceTargets(); - _setupRoles(); - _setupTargets(); - _setupTargetAuthorizations(); + _setupRoles(); // not including main target role + _setupTargets(); // not including main target (and token handler target) + + _setupTokenHandlerAuthorizations(); + _setupTargetAuthorizations(); // not including token handler target + } + + function getServiceDomains() external view returns(ObjectType[] memory serviceDomains) { + return _serviceDomains; + } + + function getComponentRole(ObjectType componentDomain) public view returns(RoleId roleId) { + return RoleIdLib.toComponentRoleId(componentDomain, 0); + } + + function getServiceRole(ObjectType serviceDomain) public virtual pure returns (RoleId serviceRoleId) { + return RoleIdLib.roleForTypeAndVersion( + serviceDomain, + getRelease()); } - function getServiceDomains() external view returns(ObjectType[] memory serviceDomains) { - return _serviceDomains; - } + function getServiceTarget(ObjectType serviceDomain) external view returns(Str serviceTarget) { + return _serviceTarget[serviceDomain]; + } - function getServiceRole(ObjectType serviceDomain) public virtual pure returns (RoleId serviceRoleId) { - return RoleIdLib.roleForTypeAndVersion( - serviceDomain, - getRelease()); - } + function getRoles() external view returns(RoleId[] memory roles) { + return _roles; + } - function getServiceTarget(ObjectType serviceDomain) external view returns(Str serviceTarget) { - return _serviceTarget[serviceDomain]; - } + function roleExists(RoleId roleId) public view returns(bool exists) { + return _roleInfo[roleId].roleType != RoleType.Undefined; + } - function getRoles() external view returns(RoleId[] memory roles) { - return _roles; - } + function getRoleInfo(RoleId roleId) external view returns (RoleInfo memory info) { + return _roleInfo[roleId]; + } - function roleExists(RoleId roleId) public view returns(bool exists) { - return _roleInfo[roleId].roleType != RoleType.Undefined; - } + function getMainTargetName() public virtual view returns (string memory name) { + return _mainTargetName; + } - function getRoleInfo(RoleId roleId) external view returns (RoleInfo memory info) { - return _roleInfo[roleId]; - } + function getMainTarget() public view returns(Str) { + return _mainTarget; + } - function getTargetName() public virtual view returns (string memory name) { - return _mainTargetName; - } + function getTokenHandlerName() public view returns(string memory) { + return _tokenHandlerName; + } - function getMainTarget() public view returns(Str) { - return getTarget(_mainTargetName); - } + function getTokenHandlerTarget() public view returns(Str) { + return _tokenHandlerTarget; + } - function getTarget(string memory targetName) public view returns(Str target) { - return StrLib.toStr(targetName); - } + function getTarget(string memory targetName) public view returns(Str target) { + return StrLib.toStr(targetName); + } - function getTargets() external view returns(Str[] memory targets) { - return _targets; - } + function getTargets() external view returns(Str[] memory targets) { + return _targets; + } - function targetExists(Str target) external view returns(bool exists) { - return _targetExists[target]; - } + function targetExists(Str target) external view returns(bool exists) { + return target == _mainTarget || _targetExists[target]; + } - function getTargetRole(Str target) external view returns(RoleId roleId) { - return _targetRole[target]; - } + function getTargetRole(Str target) external view returns(RoleId roleId) { + return _targetRole[target]; + } - function getAuthorizedRoles(Str target) external view returns(RoleId[] memory roleIds) { - return _authorizedRoles[target]; - } + function getAuthorizedRoles(Str target) external view returns(RoleId[] memory roleIds) { + return _authorizedRoles[target]; + } - function getAuthorizedFunctions(Str target, RoleId roleId) external view returns(IAccess.FunctionInfo[] memory authorizatedFunctions) { - return _authorizedFunctions[target][roleId]; - } + function getAuthorizedFunctions(Str target, RoleId roleId) external view returns(IAccess.FunctionInfo[] memory authorizatedFunctions) { + return _authorizedFunctions[target][roleId]; + } function getRelease() public virtual pure returns(VersionPart release) { return VersionPartLib.toVersionPart(GIF_RELEASE); } - /// @dev Sets up the relevant service targets for the component. - /// Overwrite this function for a specific component. - function _setupServiceTargets() internal virtual { } - - /// @dev Sets up the relevant (non-service) targets for the component. - /// Overwrite this function for a specific component. - function _setupTargets() internal virtual { } - - /// @dev Sets up the relevant roles for the component. - /// Overwrite this function for a specific component. - function _setupRoles() internal virtual {} - - /// @dev Sets up the relevant target authorizations for the component. - /// Overwrite this function for a specific realease. - function _setupTargetAuthorizations() internal virtual {} - - - /// @dev Add the service target role for the specified service domain - function _addServiceTargetWithRole(ObjectType serviceDomain) internal { - // add service domain - _serviceDomains.push(serviceDomain); - - // get versioned target name - string memory serviceTargetName = ObjectTypeLib.toVersionedName( - ObjectTypeLib.toName(serviceDomain), - "Service", - getRelease().toInt()); - - _serviceTarget[serviceDomain] = StrLib.toStr(serviceTargetName); - - RoleId serviceRoleId = getServiceRole(serviceDomain); - string memory serviceRoleName = ObjectTypeLib.toVersionedName( - ObjectTypeLib.toName(serviceDomain), - "ServiceRole", - getRelease().toInt()); - - _addTargetWithRole( - serviceTargetName, - serviceRoleId, - serviceRoleName); - } - - - /// @dev Use this method to to add an authorized role. - function _addRole(RoleId roleId, RoleInfo memory info) internal { - _roles.push(roleId); - _roleInfo[roleId] = info; - } - - - /// @dev Add a contract role for the provided role id and name. - function _addContractRole(RoleId roleId, string memory name) internal { - _addRole( - roleId, - _toRoleInfo( - ADMIN_ROLE(), - RoleType.Contract, - 1, - name)); - } - - - /// @dev Add the versioned service role for the specified service domain - function _addServiceRole(ObjectType serviceDomain) internal { - _addContractRole( - getServiceRole(serviceDomain), - ObjectTypeLib.toVersionedName( - ObjectTypeLib.toName(serviceDomain), - SERVICE_ROLE_NAME_SUFFIX, - getRelease().toInt())); - } - - - function _addComponentTargetWithRole(ObjectType componentType) internal { - _addComponentTargetWithRole(componentType, 0); - } - - - function _addComponentTargetWithRole(ObjectType componentType, uint64 index) internal { - _addTargetWithRole( - getTargetName(), - RoleIdLib.toComponentRoleId(componentType, index), - _toTargetRoleName( - getTargetName())); - } - - - /// @dev Add a contract role for the provided role id and name. - function _addCustomRole(RoleId roleId, RoleId adminRoleId, uint32 maxMemberCount, string memory name) internal { - _addRole( - roleId, - _toRoleInfo( - adminRoleId, - RoleType.Custom, - maxMemberCount, - name)); - } - - /// @dev Use this method to to add an authorized target together with its target role. - function _addTargetWithRole( - string memory targetName, - RoleId roleId, - string memory roleName - ) - internal - { - // add target - Str target = StrLib.toStr(targetName); - _targets.push(target); - - _targetExists[target] = true; - - // link role to target if defined - if (roleId != RoleIdLib.zero()) { - // add role if new - if (!roleExists(roleId)) { - _addContractRole(roleId, roleName); - } - - // link target to role - _targetRole[target] = roleId; - } - } - - - /// @dev Use this method to to add an authorized target. - function _addTarget(string memory name) internal { - _addTargetWithRole(name, RoleIdLib.zero(), ""); - } - - - /// @dev Use this method to authorize the specified role to access the target. - function _authorizeForTarget(string memory target, RoleId authorizedRoleId) - internal - returns (IAccess.FunctionInfo[] storage authorizatedFunctions) - { - Str targetStr = StrLib.toStr(target); - _authorizedRoles[targetStr].push(authorizedRoleId); - return _authorizedFunctions[targetStr][authorizedRoleId]; - } - - - /// @dev Use this method to authorize a specific function authorization - function _authorize(IAccess.FunctionInfo[] storage functions, bytes4 selector, string memory name) internal { - functions.push( - IAccess.FunctionInfo({ - selector: SelectorLib.toSelector(selector), - name: StrLib.toStr(name), - createdAt: TimestampLib.blockTimestamp()})); - } - - - /// @dev role id for targets registry, staking and instance - function _toTargetRoleId(ObjectType targetDomain) - internal - pure - returns (RoleId targetRoleId) - { - return RoleIdLib.roleForType(targetDomain); - } - - - function _toTargetRoleName(string memory targetName) internal pure returns (string memory) { - return string( - abi.encodePacked( - targetName, - ROLE_NAME_SUFFIX)); - } - - - /// @dev creates a role info object from the provided parameters - function _toRoleInfo(RoleId adminRoleId, RoleType roleType, uint32 maxMemberCount, string memory name) internal view returns (RoleInfo memory info) { - return RoleInfo({ - name: StrLib.toStr(name), - adminRoleId: adminRoleId, - roleType: roleType, - maxMemberCount: maxMemberCount, - createdAt: TimestampLib.blockTimestamp(), - pausedAt: TimestampLib.max()}); - } + /// @dev Sets up the relevant service targets for the component. + /// Overwrite this function for use case specific authorizations. + function _setupServiceTargets() internal virtual { } + + /// @dev Sets up the relevant (non-service) targets for the component. + /// Overwrite this function for use case specific authorizations. + function _setupTargets() internal virtual { } + + /// @dev Sets up the relevant roles for the component. + /// Overwrite this function for use case specific authorizations. + function _setupRoles() internal virtual {} + + /// @dev Sets up the relevant component's token handler authorizations. + /// Overwrite this function for use case specific authorizations. + function _setupTokenHandlerAuthorizations() internal virtual {} + + /// @dev Sets up the relevant target authorizations for the component. + /// Overwrite this function for use case specific authorizations. + function _setupTargetAuthorizations() internal virtual {} + + /// @dev Add the service target role for the specified service domain + function _addServiceTargetWithRole(ObjectType serviceDomain) internal { + // add service domain + _serviceDomains.push(serviceDomain); + + // get versioned target name + string memory serviceTargetName = ObjectTypeLib.toVersionedName( + ObjectTypeLib.toName(serviceDomain), + "Service", + getRelease().toInt()); + + _serviceTarget[serviceDomain] = StrLib.toStr(serviceTargetName); + + RoleId serviceRoleId = getServiceRole(serviceDomain); + string memory serviceRoleName = ObjectTypeLib.toVersionedName( + ObjectTypeLib.toName(serviceDomain), + "ServiceRole", + getRelease().toInt()); + + _addTargetWithRole( + serviceTargetName, + serviceRoleId, + serviceRoleName); + } + + + /// @dev Use this method to to add an authorized role. + function _addRole(RoleId roleId, RoleInfo memory info) internal { + _roles.push(roleId); + _roleInfo[roleId] = info; + } + + + /// @dev Add a contract role for the provided role id and name. + function _addContractRole(RoleId roleId, string memory name) internal { + _addRole( + roleId, + _toRoleInfo( + ADMIN_ROLE(), + RoleType.Contract, + 1, + name)); + } + + + /// @dev Add the versioned service role for the specified service domain + function _addServiceRole(ObjectType serviceDomain) internal { + _addContractRole( + getServiceRole(serviceDomain), + ObjectTypeLib.toVersionedName( + ObjectTypeLib.toName(serviceDomain), + SERVICE_ROLE_NAME_SUFFIX, + getRelease().toInt())); + } + + + /// @dev Add a contract role for the provided role id and name. + function _addCustomRole(RoleId roleId, RoleId adminRoleId, uint32 maxMemberCount, string memory name) internal { + _addRole( + roleId, + _toRoleInfo( + adminRoleId, + RoleType.Custom, + maxMemberCount, + name)); + } + + + /// @dev Use this method to to add an authorized target together with its target role. + function _addTargetWithRole( + string memory targetName, + RoleId roleId, + string memory roleName + ) + internal + { + // add target + Str target = StrLib.toStr(targetName); + _targets.push(target); + + _targetExists[target] = true; + + // link role to target if defined + if (roleId != RoleIdLib.zero()) { + // add role if new + if (!roleExists(roleId)) { + _addContractRole(roleId, roleName); + } + + // link target to role + _targetRole[target] = roleId; + } + } + + + /// @dev Use this method to to add an authorized target. + function _addTarget(string memory name) internal { + _addTargetWithRole(name, RoleIdLib.zero(), ""); + } + + + /// @dev Use this method to authorize the specified role to access the target. + function _authorizeForTarget(string memory target, RoleId authorizedRoleId) + internal + returns (IAccess.FunctionInfo[] storage authorizatedFunctions) + { + Str targetStr = StrLib.toStr(target); + _authorizedRoles[targetStr].push(authorizedRoleId); + return _authorizedFunctions[targetStr][authorizedRoleId]; + } + + + /// @dev Use this method to authorize a specific function authorization + function _authorize(IAccess.FunctionInfo[] storage functions, bytes4 selector, string memory name) internal { + functions.push( + IAccess.FunctionInfo({ + selector: SelectorLib.toSelector(selector), + name: StrLib.toStr(name), + createdAt: TimestampLib.blockTimestamp()})); + } + + + /// @dev role id for targets registry, staking and instance + function _toTargetRoleId(ObjectType targetDomain) + internal + pure + returns (RoleId targetRoleId) + { + return RoleIdLib.roleForType(targetDomain); + } + + + function _toTargetRoleName(string memory targetName) internal pure returns (string memory) { + return string( + abi.encodePacked( + targetName, + ROLE_NAME_SUFFIX)); + } + + + /// @dev creates a role info object from the provided parameters + function _toRoleInfo(RoleId adminRoleId, RoleType roleType, uint32 maxMemberCount, string memory name) internal view returns (RoleInfo memory info) { + return RoleInfo({ + name: StrLib.toStr(name), + adminRoleId: adminRoleId, + roleType: roleType, + maxMemberCount: maxMemberCount, + createdAt: TimestampLib.blockTimestamp(), + pausedAt: TimestampLib.max()}); + } } diff --git a/contracts/authorization/IAccessAdmin.sol b/contracts/authorization/IAccessAdmin.sol index 7b1fde120..f26cb47d7 100644 --- a/contracts/authorization/IAccessAdmin.sol +++ b/contracts/authorization/IAccessAdmin.sol @@ -16,10 +16,13 @@ interface IAccessAdmin is IRegistryLinked { - // roles - event LogRoleCreated(RoleId roleId, RoleType roleType, RoleId roleAdminId, string name); - event LogTargetCreated(address target, string name); - event LogFunctionCreated(address target, Selector selector, string name); + // roles, targets and functions + event LogAccessAdminRoleCreated(RoleId roleId, RoleType roleType, RoleId roleAdminId, string name); + event LogAccessAdminTargetCreated(address target, string name); + + event LogAccessAdminRoleGranted(address account, string roleName); + event LogAccessAdminRoleRevoked(address account, string roleName); + event LogAccessAdminFunctionGranted(address target, string functionName, string roleName); // only deployer modifier error ErrorNotDeployer(); diff --git a/contracts/authorization/IAuthorization.sol b/contracts/authorization/IAuthorization.sol index b5e07abae..ba27951e2 100644 --- a/contracts/authorization/IAuthorization.sol +++ b/contracts/authorization/IAuthorization.sol @@ -11,6 +11,9 @@ interface IAuthorization is IAccess { + error ErrorAuthorizationMainTargetNameEmpty(); + error ErrorAuthorizationTargetDomainZero(); + /// @dev Returns the list of service targets. function getServiceDomains() external view returns(ObjectType[] memory serviceDomains); @@ -20,6 +23,9 @@ interface IAuthorization is /// @dev Returns the service target for the specified domain. function getServiceTarget(ObjectType serviceDomain) external view returns(Str serviceTarget); + /// @dev Returns the component role for the specified domain. + function getComponentRole(ObjectType componentDomain) external view returns(RoleId componentRoleId); + /// @dev Returns the list of involved roles. function getRoles() external view returns(RoleId[] memory roles); @@ -32,11 +38,19 @@ interface IAuthorization is /// @dev Returns the main target id name as string. /// This name is used to derive the target id and a corresponding target role name /// Overwrite this function to change the basic pool target name. - function getTargetName() external view returns (string memory name); + function getMainTargetName() external view returns (string memory name); /// @dev Returns the main target. function getMainTarget() external view returns(Str target); + /// @dev Returns the token hander name. + /// Only components have a token handler. + function getTokenHandlerName() external view returns(string memory name); + + /// @dev Returns the token hander target. + /// Only components have a token handler. + function getTokenHandlerTarget() external view returns(Str target); + /// @dev Returns the complete list of targets. function getTargets() external view returns(Str[] memory targets); diff --git a/contracts/distribution/BasicDistributionAuthorization.sol b/contracts/distribution/BasicDistributionAuthorization.sol index b8e45848e..861f969c7 100644 --- a/contracts/distribution/BasicDistributionAuthorization.sol +++ b/contracts/distribution/BasicDistributionAuthorization.sol @@ -1,13 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; +import {IAccess} from "../authorization/IAccess.sol"; +import {IInstanceLinkedComponent} from "../shared/IInstanceLinkedComponent.sol"; + import {Authorization} from "../authorization/Authorization.sol"; import {BasicDistribution} from "./BasicDistribution.sol"; import {Distribution} from "./Distribution.sol"; -import {DISTRIBUTION} from "../type/ObjectType.sol"; -import {IAccess} from "../authorization/IAccess.sol"; -import {IInstanceLinkedComponent} from "../shared/IInstanceLinkedComponent.sol"; -import {PUBLIC_ROLE} from "../../contracts/type/RoleId.sol"; +import {COMPONENT, DISTRIBUTION} from "../type/ObjectType.sol"; +import {RoleId, PUBLIC_ROLE} from "../type/RoleId.sol"; +import {TokenHandler} from "../shared/TokenHandler.sol"; contract BasicDistributionAuthorization @@ -15,16 +17,27 @@ contract BasicDistributionAuthorization { constructor(string memory distributionlName) - Authorization(distributionlName) + Authorization(distributionlName, DISTRIBUTION()) {} - function _setupTargets() + function _setupServiceTargets() internal virtual override { - _addComponentTargetWithRole(DISTRIBUTION()); // basic target + _addServiceTargetWithRole(COMPONENT()); } + function _setupTokenHandlerAuthorizations() internal virtual override { + IAccess.FunctionInfo[] storage functions; + functions = _authorizeForTarget(getTokenHandlerName(), getServiceRole(COMPONENT())); + _authorize(functions, TokenHandler.approve.selector, "approve"); + _authorize(functions, TokenHandler.setWallet.selector, "setWallet"); + _authorize(functions, TokenHandler.pushFeeToken.selector, "pushFeeToken"); + + // authorize token handler functions for pool service role + functions = _authorizeForTarget(getTokenHandlerName(), getServiceRole(DISTRIBUTION())); + _authorize(functions, TokenHandler.pushToken.selector, "pushToken"); + } function _setupTargetAuthorizations() internal @@ -33,7 +46,7 @@ contract BasicDistributionAuthorization IAccess.FunctionInfo[] storage functions; // authorize public role (open access to any account, only allows to lock target) - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); + functions = _authorizeForTarget(getMainTargetName(), PUBLIC_ROLE()); _authorize(functions, BasicDistribution.setFees.selector, "setFees"); _authorize(functions, BasicDistribution.createDistributorType.selector, "createDistributorType"); _authorize(functions, BasicDistribution.createDistributor.selector, "createDistributor"); diff --git a/contracts/distribution/DistributionService.sol b/contracts/distribution/DistributionService.sol index b7b94db9b..a7b5ca0e7 100644 --- a/contracts/distribution/DistributionService.sol +++ b/contracts/distribution/DistributionService.sol @@ -314,7 +314,7 @@ contract DistributionService is { address distributor = getRegistry().ownerOf(distributorNftId); emit LogDistributionServiceCommissionWithdrawn(distributorNftId, distributor, address(distributionInfo.token), withdrawnAmount); - distributionInfo.tokenHandler.distributeTokens(distributionWallet, distributor, withdrawnAmount); + distributionInfo.tokenHandler.pushToken(distributor, withdrawnAmount); } } @@ -346,7 +346,7 @@ contract DistributionService is view returns(IInstance instance) { - NftId instanceNftId = getRegistry().getObjectInfo(distributionNftId).parentNftId; + NftId instanceNftId = getRegistry().getParentNftId(distributionNftId); address instanceAddress = getRegistry().getObjectAddress(instanceNftId); return IInstance(instanceAddress); } diff --git a/contracts/examples/fire/FirePoolAuthorization.sol b/contracts/examples/fire/FirePoolAuthorization.sol index ac7f0bd30..e254877a0 100644 --- a/contracts/examples/fire/FirePoolAuthorization.sol +++ b/contracts/examples/fire/FirePoolAuthorization.sol @@ -25,7 +25,7 @@ contract FirePoolAuthorization IAccess.FunctionInfo[] storage functions; // authorize public role (open access to any account, only allows to lock target) - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); + functions = _authorizeForTarget(getMainTargetName(), PUBLIC_ROLE()); // TODO: FirePool.createBundle must require a custom role (e.g. INVESTOR) instead of PUBLIC_ROLE _authorize(functions, FirePool.approveTokenHandler.selector, "approveTokenHandler"); _authorize(functions, FirePool.createBundle.selector, "createBundle"); diff --git a/contracts/examples/fire/FireProductAuthorization.sol b/contracts/examples/fire/FireProductAuthorization.sol index d7dd1a426..d53f268ce 100644 --- a/contracts/examples/fire/FireProductAuthorization.sol +++ b/contracts/examples/fire/FireProductAuthorization.sol @@ -23,7 +23,7 @@ contract FireProductAuthorization IAccess.FunctionInfo[] storage functions; // authorize public role (open access to any account, only allows to lock target) - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); + functions = _authorizeForTarget(getMainTargetName(), PUBLIC_ROLE()); // fully public functions _authorize(functions, FireProduct.approveTokenHandler.selector, "approveTokenHandler"); _authorize(functions, FireProduct.createApplication.selector, "createApplication"); diff --git a/contracts/examples/unpermissioned/SimpleDistributionAuthorization.sol b/contracts/examples/unpermissioned/SimpleDistributionAuthorization.sol index c746a1eb4..55df7dd52 100644 --- a/contracts/examples/unpermissioned/SimpleDistributionAuthorization.sol +++ b/contracts/examples/unpermissioned/SimpleDistributionAuthorization.sol @@ -21,7 +21,7 @@ contract SimpleDistributionAuthorization // authorize public role (open access to any account, only allows to lock target) IAccess.FunctionInfo[] storage functions; - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); + functions = _authorizeForTarget(getMainTargetName(), PUBLIC_ROLE()); _authorize(functions, SimpleDistribution.approveTokenHandler.selector, "approveTokenHandler"); _authorize(functions, SimpleDistribution.setWallet.selector, "setWallet"); } diff --git a/contracts/examples/unpermissioned/SimplePool.sol b/contracts/examples/unpermissioned/SimplePool.sol index 539977d84..74b54a5d6 100644 --- a/contracts/examples/unpermissioned/SimplePool.sol +++ b/contracts/examples/unpermissioned/SimplePool.sol @@ -5,7 +5,6 @@ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IER import {Amount, AmountLib} from "../../type/Amount.sol"; import {BasicPool} from "../../pool/BasicPool.sol"; -import {BasicPoolAuthorization} from "../../pool/BasicPoolAuthorization.sol"; import {Fee} from "../../type/Fee.sol"; import {IAuthorization} from "../../authorization/IAuthorization.sol"; import {IComponents} from "../../instance/module/IComponents.sol"; diff --git a/contracts/examples/unpermissioned/SimplePoolAuthorization.sol b/contracts/examples/unpermissioned/SimplePoolAuthorization.sol index 76939282f..d449601da 100644 --- a/contracts/examples/unpermissioned/SimplePoolAuthorization.sol +++ b/contracts/examples/unpermissioned/SimplePoolAuthorization.sol @@ -21,7 +21,7 @@ contract SimplePoolAuthorization // authorize public role (open access to any account, only allows to lock target) IAccess.FunctionInfo[] storage functions; - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); + functions = _authorizeForTarget(getMainTargetName(), PUBLIC_ROLE()); _authorize(functions, SimplePool.approveTokenHandler.selector, "approveTokenHandler"); _authorize(functions, SimplePool.setWallet.selector, "setWallet"); } diff --git a/contracts/examples/unpermissioned/SimpleProductAuthorization.sol b/contracts/examples/unpermissioned/SimpleProductAuthorization.solx similarity index 92% rename from contracts/examples/unpermissioned/SimpleProductAuthorization.sol rename to contracts/examples/unpermissioned/SimpleProductAuthorization.solx index 0c632404e..3eaccfae5 100644 --- a/contracts/examples/unpermissioned/SimpleProductAuthorization.sol +++ b/contracts/examples/unpermissioned/SimpleProductAuthorization.solx @@ -21,7 +21,7 @@ contract SimpleProductAuthorization // authorize public role (open access to any account, only allows to lock target) IAccess.FunctionInfo[] storage functions; - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); + functions = _authorizeForTarget(getMainTargetName(), PUBLIC_ROLE()); _authorize(functions, SimpleProduct.approveTokenHandler.selector, "approveTokenHandler"); _authorize(functions, SimpleProduct.setWallet.selector, "setWallet"); } diff --git a/contracts/instance/BundleSet.sol b/contracts/instance/BundleSet.sol index 5a27b1074..ca07c1ce4 100644 --- a/contracts/instance/BundleSet.sol +++ b/contracts/instance/BundleSet.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {IPolicy} from "../instance/module/IPolicy.sol"; import {LibNftIdSet} from "../type/NftIdSet.sol"; import {NftId, NftIdLib} from "../type/NftId.sol"; import {Key32} from "../type/Key32.sol"; diff --git a/contracts/instance/IInstance.sol b/contracts/instance/IInstance.sol index 009dafd15..b7a90e631 100644 --- a/contracts/instance/IInstance.sol +++ b/contracts/instance/IInstance.sol @@ -57,7 +57,11 @@ interface IInstance is /// Permissioned: only the target owner may call this function. function withdrawStakingRewardReserves(Amount dipAmount) external returns (Amount newBalance); - // get instance release and supporting contracts + // get products + function products() external view returns (uint256 productCount); + function getProductNftid(uint256 idx) external view returns (NftId productNftId); + + // get supporting contracts function getInstanceReader() external view returns (InstanceReader); function getBundleSet() external view returns (BundleSet); function getRiskSet() external view returns (RiskSet); diff --git a/contracts/instance/Instance.sol b/contracts/instance/Instance.sol index e76fb08c7..64ffa46f4 100644 --- a/contracts/instance/Instance.sol +++ b/contracts/instance/Instance.sol @@ -26,11 +26,13 @@ contract Instance is IComponentService internal _componentService; IInstanceService internal _instanceService; + InstanceAdmin internal _instanceAdmin; InstanceReader internal _instanceReader; BundleSet internal _bundleSet; RiskSet internal _riskSet; InstanceStore internal _instanceStore; + NftId [] internal _products; modifier onlyChainNft() { if(msg.sender != getRegistry().getChainNftAddress()) { @@ -93,12 +95,14 @@ contract Instance is } //--- ProductRegistration ----------------------------------------------// + function registerProduct(address product) external onlyOwner() returns (NftId productNftId) { - return _componentService.registerProduct(product); + productNftId = _componentService.registerProduct(product); + _products.push(productNftId); } //--- Staking ----------------------------------------------------------// @@ -211,6 +215,14 @@ contract Instance is //--- external view functions -------------------------------------------// + function products() external view returns (uint256 productCount) { + return _products.length; + } + + function getProductNftid(uint256 idx) external view returns (NftId productNftId) { + return _products[idx]; + } + function getInstanceReader() external view returns (InstanceReader) { return _instanceReader; } diff --git a/contracts/instance/InstanceAdmin.sol b/contracts/instance/InstanceAdmin.sol index 0695bb918..facae43bd 100644 --- a/contracts/instance/InstanceAdmin.sol +++ b/contracts/instance/InstanceAdmin.sol @@ -1,14 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; - -import {AccessAdmin} from "../authorization/AccessAdmin.sol"; -import {AccessManagerCloneable} from "../authorization/AccessManagerCloneable.sol"; +import {IAccess} from "../authorization/IAccess.sol"; import {IAuthorization} from "../authorization/IAuthorization.sol"; import {IInstanceLinkedComponent} from "../shared/IInstanceLinkedComponent.sol"; -import {IAuthorization} from "../authorization/IAuthorization.sol"; import {IRegistry} from "../registry/IRegistry.sol"; import {IInstance} from "./IInstance.sol"; + +import {AccessAdmin} from "../authorization/AccessAdmin.sol"; +import {AccessManagerCloneable} from "../authorization/AccessManagerCloneable.sol"; +import {NftId} from "../type/NftId.sol"; import {ObjectType} from "../type/ObjectType.sol"; import {RoleId, RoleIdLib} from "../type/RoleId.sol"; import {Str, StrLib} from "../type/String.sol"; @@ -30,13 +31,21 @@ contract InstanceAdmin is error ErrorInstanceAdminCallerNotInstanceOwner(address caller); error ErrorInstanceAdminInstanceAlreadyLocked(); error ErrorInstanceAdminNotRegistered(address target); + error ErrorInstanceAdminAlreadyAuthorized(address target); + error ErrorInstanceAdminNotComponentRole(RoleId roleId); + error ErrorInstanceAdminRoleAlreadyExists(RoleId roleId); + error ErrorInstanceAdminRoleTypeNotContract(RoleId roleId, IAccess.RoleType roleType); + error ErrorInstanceAdminReleaseMismatch(); error ErrorInstanceAdminExpectedTargetMissing(string targetName); IInstance internal _instance; IRegistry internal _registry; - uint64 internal _idNext; + uint64 internal _customIdNext; + + mapping(address target => RoleId roleId) internal _targetRoleId; + uint64 internal _components; IAuthorization internal _instanceAuthorization; @@ -78,7 +87,6 @@ contract InstanceAdmin is _registry = registry; } - /// @dev Completes the initialization of this instance admin using the provided instance, registry and version. /// Important: Initialization of instance admin is only complete after calling this function. /// Important: The instance MUST be registered and all instance supporting contracts must be wired to this instance. @@ -90,15 +98,11 @@ contract InstanceAdmin is reinitializer(uint64(getRelease().toInt())) onlyDeployer() { - _idNext = CUSTOM_ROLE_ID_MIN; + _components = 0; + _customIdNext = CUSTOM_ROLE_ID_MIN; _instance = IInstance(instance); _instanceAuthorization = IAuthorization(authorization); - // AccessManagerCloneable accessManager = AccessManagerCloneable(authority()); - // accessManager.completeSetup( - // address(_registry), - // release); - _checkTargetIsReadyForAuthorization(instance); // check matching releases @@ -106,9 +110,13 @@ contract InstanceAdmin is revert ErrorInstanceAdminReleaseMismatch(); } + // create instance role and target + _setupInstance(instance); + // add instance authorization _createRoles(_instanceAuthorization); - _createModuleTargetsWithRoles(); + + _setupInstanceHelperTargetsWithRoles(); _createTargetAuthorizations(_instanceAuthorization); } @@ -119,50 +127,53 @@ contract InstanceAdmin is IInstanceLinkedComponent component ) external + restricted() { - // !!! TODO add caller restrictions? - + // checks _checkTargetIsReadyForAuthorization(address(component)); - // get authorization specification + // setup target and role for component (including token handler) + _setupComponentAndTokenHandler(component); + + // create other roles IAuthorization authorization = component.getAuthorization(); - string memory targetName = authorization.getTargetName(); - _checkTargetIsReadyForAuthorization(address(component)); + _createRoles(authorization); + // TODO cleanup + // FunctionInfo[] memory functions = new FunctionInfo[](2); + // functions[0] = toFunction(TokenHandler.pullToken.selector, "pullToken"); + // functions[1] = toFunction(TokenHandler.pushToken.selector, "pushToken"); - // create roles - _createRoles(authorization); + // // FIXME: make this a bit nicer and work with IAuthorization. Use a specific role, not public - access to TokenHandler must be restricted + // _authorizeTargetFunctions( + // address(component.getTokenHandler()), + // getPublicRole(), + // functions); + + _createTargetAuthorizations(authorization); + } + + // create instance role and target + function _setupInstance(address instance) internal { + // create instance role + RoleId instanceRoleId = _instanceAuthorization.getTargetRole( + _instanceAuthorization.getMainTarget()); - // create component target + _createRole( + instanceRoleId, + _instanceAuthorization.getRoleInfo(instanceRoleId)); + + // create instance target _createTarget( - address(component), - targetName, + instance, + _instanceAuthorization.getMainTargetName(), true, // checkAuthority false); // custom - _createTarget( - address(component.getTokenHandler()), - string(abi.encodePacked(targetName, "TH")), - true, - false); - - FunctionInfo[] memory functions = new FunctionInfo[](3); - functions[0] = toFunction(TokenHandler.collectTokens.selector, "collectTokens"); - functions[1] = toFunction(TokenHandler.collectTokensToThreeRecipients.selector, "collectTokensToThreeRecipients"); - functions[2] = toFunction(TokenHandler.distributeTokens.selector, "distributeTokens"); - - // FIXME: make this a bit nicer and work with IAuthorization. Use a specific role, not public - access to TokenHandler must be restricted - _authorizeTargetFunctions( - address(component.getTokenHandler()), - getPublicRole(), - functions); - + // assign instance role to instance _grantRoleToAccount( - authorization.getTargetRole( - authorization.getMainTarget()), - address(component)); - - _createTargetAuthorizations(authorization); + instanceRoleId, + instance); } /// @dev Creates a custom role @@ -217,6 +228,95 @@ contract InstanceAdmin is // ------------------- Internal functions ------------------- // + function _setupComponentAndTokenHandler(IInstanceLinkedComponent component) + internal + { + + IAuthorization authorization = component.getAuthorization(); + string memory targetName = authorization.getMainTargetName(); + + // create component role and target + RoleId componentRoleId = _createComponentRoleId(component, authorization); + + // create component's target + _createTarget( + address(component), + targetName, + true, // checkAuthority + false); // custom + + // create component's token handler target + NftId componentNftId = _registry.getNftIdForAddress(address(component)); + address tokenHandler = address( + _instance.getInstanceReader().getComponentInfo( + componentNftId).tokenHandler); + + _createTarget( + tokenHandler, + authorization.getTokenHandlerName(), + true, + false); + + // assign component role to component + _grantRoleToAccount( + componentRoleId, + address(component)); + + // token handler does not require its own role + // token handler is not calling other components + } + + + function _createComponentRoleId( + IInstanceLinkedComponent component, + IAuthorization authorization + ) + internal + returns (RoleId componentRoleId) + { + // checks + // check component is not yet authorized + if (_targetRoleId[address(component)].gtz()) { + revert ErrorInstanceAdminAlreadyAuthorized(address(component)); + } + + // check generic component role + RoleId genericComponentRoleId = authorization.getTargetRole( + authorization.getMainTarget()); + + if (!genericComponentRoleId.isComponentRole()) { + revert ErrorInstanceAdminNotComponentRole(genericComponentRoleId); + } + + // check component role does not exist + componentRoleId = toComponentRole( + genericComponentRoleId, + _components); + + if (roleExists(componentRoleId)) { + revert ErrorInstanceAdminRoleAlreadyExists(componentRoleId); + } + + // check role info + IAccess.RoleInfo memory roleInfo = authorization.getRoleInfo( + genericComponentRoleId); + + if (roleInfo.roleType != IAccess.RoleType.Contract) { + revert ErrorInstanceAdminRoleTypeNotContract( + componentRoleId, + roleInfo.roleType); + } + + // effects + _targetRoleId[address(component)] = componentRoleId; + _components++; + + _createRole( + componentRoleId, + roleInfo); + } + + function _checkTargetIsReadyForAuthorization(address target) internal view @@ -235,13 +335,18 @@ contract InstanceAdmin is internal { RoleId[] memory roles = authorization.getRoles(); + RoleId mainTargetRoleId = authorization.getTargetRole( + authorization.getMainTarget()); + RoleId roleId; RoleInfo memory roleInfo; for(uint256 i = 0; i < roles.length; i++) { + roleId = roles[i]; - if (!roleExists(roleId)) { + // skip main target role, create role if not exists + if (roleId != mainTargetRoleId && !roleExists(roleId)) { _createRole( roleId, authorization.getRoleInfo(roleId)); @@ -250,6 +355,16 @@ contract InstanceAdmin is } + function toComponentRole(RoleId roleId, uint64 componentIdx) + internal + pure + returns (RoleId) + { + return RoleIdLib.toRoleId( + RoleIdLib.toInt(roleId) + componentIdx); + } + + function _createTargetAuthorizations(IAuthorization authorization) internal { @@ -300,11 +415,12 @@ contract InstanceAdmin is } } - function _createModuleTargetsWithRoles() + function _setupInstanceHelperTargetsWithRoles() internal { + // _checkAndCreateTargetWithRole(address(_instance), INSTANCE_TARGET_NAME); + // create module targets - _checkAndCreateTargetWithRole(address(_instance), INSTANCE_TARGET_NAME); _checkAndCreateTargetWithRole(address(_instance.getInstanceStore()), INSTANCE_STORE_TARGET_NAME); _checkAndCreateTargetWithRole(address(_instance.getInstanceAdmin()), INSTANCE_ADMIN_TARGET_NAME); _checkAndCreateTargetWithRole(address(_instance.getBundleSet()), BUNDLE_SET_TARGET_NAME); diff --git a/contracts/instance/InstanceAuthorizationV3.sol b/contracts/instance/InstanceAuthorizationV3.sol index ef8da1a67..6fce34962 100644 --- a/contracts/instance/InstanceAuthorizationV3.sol +++ b/contracts/instance/InstanceAuthorizationV3.sol @@ -26,27 +26,14 @@ contract InstanceAuthorizationV3 string public constant BUNDLE_SET_TARGET_NAME = "BundleSet"; string public constant RISK_SET_TARGET_NAME = "RiskSet"; - constructor() Authorization(INSTANCE_TARGET_NAME) {} - - - function _setupRoles() - internal - override - { - // empty implementation - } - + constructor() + Authorization(INSTANCE_TARGET_NAME, INSTANCE()) + { } function _setupTargets() internal override { - // instance target - _addTargetWithRole( - INSTANCE_TARGET_NAME, - _toTargetRoleId(INSTANCE()), - INSTANCE_ROLE_NAME); - // instance supporting targets _addTarget(INSTANCE_STORE_TARGET_NAME); _addTarget(INSTANCE_ADMIN_TARGET_NAME); @@ -132,11 +119,12 @@ contract InstanceAuthorizationV3 IAccess.FunctionInfo[] storage functions; // authorize instance role - functions = _authorizeForTarget(INSTANCE_ADMIN_TARGET_NAME, _toTargetRoleId(INSTANCE())); + functions = _authorizeForTarget(INSTANCE_ADMIN_TARGET_NAME, getComponentRole(INSTANCE())); _authorize(functions, InstanceAdmin.grantRole.selector, "grantRole"); // authorize component service role functions = _authorizeForTarget(INSTANCE_ADMIN_TARGET_NAME, getServiceRole(COMPONENT())); + _authorize(functions, InstanceAdmin.initializeComponentAuthorization.selector, "initializeComponentAuthoriz"); _authorize(functions, InstanceAdmin.setTargetLocked.selector, "setTargetLocked"); } diff --git a/contracts/instance/InstanceReader.sol b/contracts/instance/InstanceReader.sol index 9f225f010..04f55bec0 100644 --- a/contracts/instance/InstanceReader.sol +++ b/contracts/instance/InstanceReader.sol @@ -1,19 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Amount} from "../type/Amount.sol"; -import {ClaimId, ClaimIdLib} from "../type/ClaimId.sol"; -import {DistributorType} from "../type/DistributorType.sol"; -import {Key32} from "../type/Key32.sol"; -import {NftId} from "../type/NftId.sol"; -import {COMPONENT, DISTRIBUTOR, DISTRIBUTION, FEE, PREMIUM, PRODUCT, POLICY, POOL, BUNDLE} from "../type/ObjectType.sol"; -import {PayoutId, PayoutIdLib} from "../type/PayoutId.sol"; -import {ReferralId, ReferralStatus, ReferralLib, REFERRAL_OK, REFERRAL_ERROR_UNKNOWN, REFERRAL_ERROR_EXPIRED, REFERRAL_ERROR_EXHAUSTED} from "../type/Referral.sol"; -import {RequestId} from "../type/RequestId.sol"; -import {RiskId} from "../type/RiskId.sol"; -import {RoleId} from "../type/RoleId.sol"; -import {StateId} from "../type/StateId.sol"; -import {UFixed, UFixedLib} from "../type/UFixed.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IBundle} from "../instance/module/IBundle.sol"; import {IComponents} from "../instance/module/IComponents.sol"; @@ -22,12 +10,28 @@ import {IInstance} from "./IInstance.sol"; import {IKeyValueStore} from "../shared/IKeyValueStore.sol"; import {IOracle} from "../oracle/IOracle.sol"; import {IPolicy} from "../instance/module/IPolicy.sol"; +import {IRegistry} from "../registry/IRegistry.sol"; import {IRisk} from "../instance/module/IRisk.sol"; import {TimestampLib} from "../type/Timestamp.sol"; -import {InstanceStore} from "./InstanceStore.sol"; +import {Amount} from "../type/Amount.sol"; import {BundleSet} from "./BundleSet.sol"; +import {BUNDLE, COMPONENT, DISTRIBUTOR, DISTRIBUTION, FEE, PREMIUM, POLICY, POOL, PRODUCT} from "../type/ObjectType.sol"; +import {ClaimId, ClaimIdLib} from "../type/ClaimId.sol"; +import {DistributorType} from "../type/DistributorType.sol"; +import {InstanceStore} from "./InstanceStore.sol"; +import {Key32} from "../type/Key32.sol"; +import {NftId} from "../type/NftId.sol"; +import {PayoutId, PayoutIdLib} from "../type/PayoutId.sol"; +import {ReferralId, ReferralStatus, ReferralLib, REFERRAL_OK, REFERRAL_ERROR_UNKNOWN, REFERRAL_ERROR_EXPIRED, REFERRAL_ERROR_EXHAUSTED} from "../type/Referral.sol"; +import {RequestId} from "../type/RequestId.sol"; +import {RiskId} from "../type/RiskId.sol"; import {RiskSet} from "./RiskSet.sol"; +import {RoleId} from "../type/RoleId.sol"; +import {StateId} from "../type/StateId.sol"; +import {TokenHandler} from "../shared/TokenHandler.sol"; +import {UFixed, UFixedLib} from "../type/UFixed.sol"; + contract InstanceReader { @@ -37,7 +41,9 @@ contract InstanceReader { bool private _initialized = false; + IRegistry internal _registry; IInstance internal _instance; + InstanceStore internal _store; BundleSet internal _bundleSet; RiskSet internal _riskSet; @@ -51,7 +57,6 @@ contract InstanceReader { initializeWithInstance(msg.sender); } - /// @dev This initializer needs to be called from the instance itself. function initializeWithInstance(address instanceAddress) public { @@ -61,12 +66,28 @@ contract InstanceReader { _initialized = true; _instance = IInstance(instanceAddress); + _registry = _instance.getRegistry(); + _store = _instance.getInstanceStore(); _bundleSet = _instance.getBundleSet(); _riskSet = _instance.getRiskSet(); } + // instance level functions + + function getRegistry() public view returns (IRegistry registry) { + return _registry; + } + + function getInstanceNftId() public view returns (NftId instanceNftid) { + return _registry.getNftIdForAddress(address(_instance)); + } + + function getInstance() public view returns (IInstance instance) { + return _instance; + } + // module specific functions function getPolicyInfo(NftId policyNftId) @@ -315,32 +336,43 @@ contract InstanceReader { return _riskSet.getLinkedPolicyNftId(riskId, idx); } - function getWallet(NftId componentNftId) + + function getToken(NftId componentNftId) public view - returns (address tokenHandler) + returns (IERC20Metadata token) { - bytes memory data = _store.getData(toComponentKey(componentNftId)); + TokenHandler tokenHandler = getTokenHandler(componentNftId); + if (address(tokenHandler) != address(0)) { + return tokenHandler.TOKEN(); + } + } - if (data.length > 0) { - IComponents.ComponentInfo memory info = abi.decode(data, (IComponents.ComponentInfo)); - return info.tokenHandler.getWallet(); + + function getWallet(NftId componentNftId) + public + view + returns (address wallet) + { + TokenHandler tokenHandler = getTokenHandler(componentNftId); + if (address(tokenHandler) != address(0)) { + return tokenHandler.getWallet(); } } + function getTokenHandler(NftId componentNftId) public view - returns (address tokenHandler) + returns (TokenHandler tokenHandler) { bytes memory data = _store.getData(toComponentKey(componentNftId)); - if (data.length > 0) { - IComponents.ComponentInfo memory info = abi.decode(data, (IComponents.ComponentInfo)); - return address(info.tokenHandler); + return abi.decode(data, (IComponents.ComponentInfo)).tokenHandler; } } + function getBundleInfo(NftId bundleNftId) public view @@ -570,9 +602,6 @@ contract InstanceReader { } // low level function - function getInstance() external view returns (IInstance instance) { - return _instance; - } function getInstanceStore() external view returns (IKeyValueStore store) { return _store; diff --git a/contracts/instance/module/IAccess.sol b/contracts/instance/module/IAccess.sol deleted file mode 100644 index c7a9fee9d..000000000 --- a/contracts/instance/module/IAccess.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {ShortString, ShortStrings} from "@openzeppelin/contracts/utils/ShortStrings.sol"; - -import {NftId} from "../../type/NftId.sol"; -import {RoleId} from "../../type/RoleId.sol"; -import {Timestamp} from "../../type/Timestamp.sol"; - -interface IAccess { - - enum Type { - NotInitialized, - Core, - Gif, - Custom - } - - struct RoleInfo { - ShortString name; - Type rtype; - //bool isLocked; - RoleId admin; - Timestamp createdAt; - Timestamp updatedAt; - } - - struct TargetInfo { - ShortString name; - Type ttype; - bool isLocked; - Timestamp createdAt; - Timestamp updatedAt; - } - - error ErrorIAccessRoleIdTooBig(RoleId roleId); - error ErrorIAccessRoleIdTooSmall(RoleId roleId); - error ErrorIAccessRoleTypeInvalid(RoleId roleId, Type rtype); - - error ErrorIAccessTargetAddressZero(); - error ErrorIAccessTargetTypeInvalid(address target, Type ttype); - error ErrorIAccessTargetLocked(address target); - error ErrorIAccessTargetNotRegistered(address target); - error ErrorIAccessTargetAuthorityInvalid(address target, address targetAuthority); - error ErrorIAccessTargetInstanceMismatch(address target, NftId targetParentNftId, NftId instanceNftId); -} \ No newline at end of file diff --git a/contracts/oracle/BasicOracleAuthorization.sol b/contracts/oracle/BasicOracleAuthorization.sol index f71424f90..3b8f5cd62 100644 --- a/contracts/oracle/BasicOracleAuthorization.sol +++ b/contracts/oracle/BasicOracleAuthorization.sol @@ -15,18 +15,9 @@ contract BasicOracleAuthorization { constructor(string memory componentName) - Authorization(componentName) + Authorization(componentName, ORACLE()) {} - function _setupTargets() - internal - virtual override - { - // basic component target - _addComponentTargetWithRole(ORACLE()); - } - - function _setupTargetAuthorizations() internal virtual override @@ -34,12 +25,12 @@ contract BasicOracleAuthorization IAccess.FunctionInfo[] storage functions; // authorize public role (open access to any account, only allows to lock target) - functions = _authorizeForTarget(getTargetName(), getServiceRole(ORACLE())); + functions = _authorizeForTarget(getMainTargetName(), getServiceRole(ORACLE())); _authorize(functions, IOracle.request.selector, "request"); _authorize(functions, IOracle.cancel.selector, "cancel"); // authorize public role (open access to any account, only allows to lock target) - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); + functions = _authorizeForTarget(getMainTargetName(), PUBLIC_ROLE()); _authorize(functions, BasicOracle.respond.selector, "respond"); } } diff --git a/contracts/pool/BasicPoolAuthorization.sol b/contracts/pool/BasicPoolAuthorization.sol index 1c7b9103f..a5cf12357 100644 --- a/contracts/pool/BasicPoolAuthorization.sol +++ b/contracts/pool/BasicPoolAuthorization.sol @@ -1,14 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Authorization} from "../authorization/Authorization.sol"; -import {BasicPool} from "./BasicPool.sol"; import {IAccess} from "../authorization/IAccess.sol"; import {IInstanceLinkedComponent} from "../shared/IInstanceLinkedComponent.sol"; import {IPoolComponent} from "./IPoolComponent.sol"; -import {POOL} from "../type/ObjectType.sol"; + +import {Authorization} from "../authorization/Authorization.sol"; +import {BasicPool} from "./BasicPool.sol"; +import {COMPONENT, POOL} from "../type/ObjectType.sol"; import {PUBLIC_ROLE} from "../../contracts/type/RoleId.sol"; import {RoleId} from "../type/RoleId.sol"; +import {TokenHandler} from "../shared/TokenHandler.sol"; contract BasicPoolAuthorization @@ -16,23 +18,30 @@ contract BasicPoolAuthorization { constructor(string memory poolName) - Authorization(poolName) + Authorization(poolName, POOL()) {} function _setupServiceTargets() internal virtual override { + _addServiceTargetWithRole(COMPONENT()); _addServiceTargetWithRole(POOL()); } - function _setupTargets() - internal - virtual override - { - _addComponentTargetWithRole(POOL()); // basic pool target - } + function _setupTokenHandlerAuthorizations() internal virtual override { + // authorize token handler functions for component service role + IAccess.FunctionInfo[] storage functions; + functions = _authorizeForTarget(getTokenHandlerName(), getServiceRole(COMPONENT())); + _authorize(functions, TokenHandler.approve.selector, "approve"); + _authorize(functions, TokenHandler.setWallet.selector, "setWallet"); + _authorize(functions, TokenHandler.pushFeeToken.selector, "pushFeeToken"); + // authorize token handler functions for pool service role + functions = _authorizeForTarget(getTokenHandlerName(), getServiceRole(POOL())); + _authorize(functions, TokenHandler.pullToken.selector, "pullToken"); + _authorize(functions, TokenHandler.pushToken.selector, "pushToken"); + } function _setupTargetAuthorizations() internal @@ -41,7 +50,7 @@ contract BasicPoolAuthorization IAccess.FunctionInfo[] storage functions; // authorize public role (open access to any account, only allows to lock target) - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); + functions = _authorizeForTarget(getMainTargetName(), PUBLIC_ROLE()); _authorize(functions, BasicPool.stake.selector, "stake"); _authorize(functions, BasicPool.unstake.selector, "unstake"); _authorize(functions, BasicPool.extend.selector, "extend"); @@ -57,11 +66,10 @@ contract BasicPoolAuthorization _authorize(functions, BasicPool.extend.selector, "extend"); _authorize(functions, IInstanceLinkedComponent.withdrawFees.selector, "withdrawFees"); - _authorize(functions, BasicPool.withdrawBundleFees.selector, "withdrawBundleFees"); // authorize pool service - functions = _authorizeForTarget(getTargetName(), getServiceRole(POOL())); + functions = _authorizeForTarget(getMainTargetName(), getServiceRole(POOL())); _authorize(functions, IPoolComponent.verifyApplication.selector, "verifyApplication"); } } diff --git a/contracts/pool/BundleService.sol b/contracts/pool/BundleService.sol index bcb2f898d..79955cd9e 100644 --- a/contracts/pool/BundleService.sol +++ b/contracts/pool/BundleService.sol @@ -144,6 +144,7 @@ contract BundleService is virtual restricted() { + // checks _checkNftType(policyNftId, POLICY()); _checkNftType(bundleNftId, BUNDLE()); @@ -175,6 +176,7 @@ contract BundleService is } } + // effects // updated locked amount instanceStore.increaseLocked(bundleNftId, collateralAmount); } @@ -185,6 +187,7 @@ contract BundleService is virtual restricted() { + // checks _checkNftType(bundleNftId, BUNDLE()); (,, IInstance instance) = _getAndVerifyActiveComponent(POOL()); @@ -192,6 +195,7 @@ contract BundleService is // udpate bundle state instance.getInstanceStore().updateBundleState(bundleNftId, PAUSED()); + // effects // update set of active bundles BundleSet bundleManager = instance.getBundleSet(); bundleManager.lock(bundleNftId); @@ -205,10 +209,12 @@ contract BundleService is virtual restricted() { + // checks _checkNftType(bundleNftId, BUNDLE()); (,, IInstance instance) = _getAndVerifyActiveComponent(POOL()); + // effects // udpate bundle state instance.getInstanceStore().updateBundleState(bundleNftId, ACTIVE()); @@ -229,6 +235,7 @@ contract BundleService is restricted() returns (Amount unstakedAmount, Amount feeAmount) { + // checks _checkNftType(bundleNftId, BUNDLE()); InstanceReader instanceReader = instance.getInstanceReader(); @@ -240,6 +247,7 @@ contract BundleService is revert ErrorBundleServiceBundleWithOpenPolicies(bundleNftId, openPolicies); } + // effects { // update bundle state InstanceStore instanceStore = instance.getInstanceStore(); @@ -256,7 +264,8 @@ contract BundleService is /// @inheritdoc IBundleService function stake( - IInstance instance, + InstanceReader instanceReader, + InstanceStore instanceStore, NftId bundleNftId, Amount amount ) @@ -264,10 +273,11 @@ contract BundleService is virtual restricted() { + // checks _checkNftType(bundleNftId, BUNDLE()); - IBundle.BundleInfo memory bundleInfo = instance.getInstanceReader().getBundleInfo(bundleNftId); - StateId bundleState = instance.getInstanceReader().getBundleState(bundleNftId); + IBundle.BundleInfo memory bundleInfo = instanceReader.getBundleInfo(bundleNftId); + StateId bundleState = instanceReader.getBundleState(bundleNftId); if( (bundleState != ACTIVE() && bundleState != PAUSED()) // locked bundles can be staked || bundleInfo.expiredAt < TimestampLib.blockTimestamp() @@ -275,8 +285,9 @@ contract BundleService is revert ErrorBundleServiceBundleNotOpen(bundleNftId, bundleState, bundleInfo.expiredAt); } + // effects _accountingService.increaseBundleBalance( - instance.getInstanceStore(), + instanceStore, bundleNftId, amount, AmountLib.zero()); @@ -284,7 +295,7 @@ contract BundleService is /// @inheritdoc IBundleService function unstake( - IInstance instance, + InstanceStore instanceStore, NftId bundleNftId, Amount amount ) @@ -293,9 +304,9 @@ contract BundleService is restricted() returns (Amount unstakedAmount) { + // checks _checkNftType(bundleNftId, BUNDLE()); - InstanceStore instanceStore = instance.getInstanceStore(); ( Amount balanceAmount, Amount lockedAmount, @@ -315,6 +326,7 @@ contract BundleService is revert ErrorBundleServiceUnstakeAmountExceedsLimit(amount, availableAmount); } + // effects _accountingService.decreaseBundleBalance( instanceStore, bundleNftId, @@ -329,6 +341,7 @@ contract BundleService is restricted() returns (Timestamp extendedExpiredAt) { + // checks _checkNftType(bundleNftId, BUNDLE()); (NftId poolNftId,, IInstance instance) = _getAndVerifyActiveComponent(POOL()); @@ -349,17 +362,18 @@ contract BundleService is revert ErrorBundleServiceExtensionLifetimeIsZero(); } + // effects bundleInfo.expiredAt = bundleInfo.expiredAt.addSeconds(lifetimeExtension); - instance.getInstanceStore().updateBundle(bundleNftId, bundleInfo, KEEP_STATE()); + extendedExpiredAt = bundleInfo.expiredAt; - emit LogBundleServiceBundleExtended(bundleNftId, lifetimeExtension, bundleInfo.expiredAt); + instance.getInstanceStore().updateBundle(bundleNftId, bundleInfo, KEEP_STATE()); - return bundleInfo.expiredAt; + emit LogBundleServiceBundleExtended(bundleNftId, lifetimeExtension, extendedExpiredAt); } function releaseCollateral( - IInstance instance, + InstanceStore instanceStore, NftId policyNftId, NftId bundleNftId, Amount collateralAmount @@ -371,54 +385,10 @@ contract BundleService is _checkNftType(policyNftId, POLICY()); _checkNftType(bundleNftId, BUNDLE()); - instance.getInstanceStore().decreaseLocked(bundleNftId, collateralAmount); + instanceStore.decreaseLocked(bundleNftId, collateralAmount); } - /// @inheritdoc IBundleService - function withdrawBundleFees(NftId bundleNftId, Amount amount) - public - virtual - restricted() - returns (Amount withdrawnAmount) - { - _checkNftType(bundleNftId, BUNDLE()); - - (NftId poolNftId,, IInstance instance) = _getAndVerifyActiveComponent(POOL()); - InstanceReader reader = instance.getInstanceReader(); - - IComponents.ComponentInfo memory poolInfo = reader.getComponentInfo(poolNftId); - address poolWallet = poolInfo.tokenHandler.getWallet(); - - // IBundle.BundleInfo memory bundleInfo = reader.getBundleInfo(bundleNftId); - - // determine withdrawn amount - withdrawnAmount = amount; - if (withdrawnAmount.gte(AmountLib.max())) { - withdrawnAmount = reader.getFeeAmount(bundleNftId); - } else { - if (withdrawnAmount.gt(reader.getFeeAmount(bundleNftId))) { - revert ErrorBundleServiceFeesWithdrawAmountExceedsLimit(withdrawnAmount, reader.getFeeAmount(bundleNftId)); - } - } - - // decrease fee counters by withdrawnAmount - { - InstanceStore store = instance.getInstanceStore(); - // decrease fee amount of the bundle - _accountingService.decreaseBundleBalance(store, bundleNftId, AmountLib.zero(), withdrawnAmount); - // decrease pool balance - _accountingService.decreasePoolBalance(store, poolNftId, withdrawnAmount, AmountLib.zero()); - } - - // transfer amount to bundle owner - { - address owner = getRegistry().ownerOf(bundleNftId); - emit LogBundleServiceFeesWithdrawn(bundleNftId, owner, address(poolInfo.token), withdrawnAmount); - poolInfo.tokenHandler.distributeTokens(poolWallet, owner, withdrawnAmount); - } - } - function _getDomain() internal pure override returns(ObjectType) { return BUNDLE(); } diff --git a/contracts/pool/IBundleService.sol b/contracts/pool/IBundleService.sol index 1e545ded7..417c833d9 100644 --- a/contracts/pool/IBundleService.sol +++ b/contracts/pool/IBundleService.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; +import {IService} from "../shared/IService.sol"; +import {IInstance} from "../instance/IInstance.sol"; + import {Amount} from "../type/Amount.sol"; import {NftId} from "../type/NftId.sol"; import {Fee} from "../type/Fee.sol"; -import {IService} from "../shared/IService.sol"; -import {IInstance} from "../instance/IInstance.sol"; +import {InstanceReader} from "../instance/InstanceReader.sol"; +import {InstanceStore} from "../instance/InstanceStore.sol"; import {Seconds} from "../type/Seconds.sol"; import {StateId} from "../type/StateId.sol"; import {Timestamp} from "../type/Timestamp.sol"; @@ -25,14 +28,11 @@ interface IBundleService is IService { error ErrorBundleServiceBundlePoolMismatch(NftId bundleNftId, NftId expectedPool, NftId actualPool); error ErrorBundleServicePolicyNotCloseable(NftId policyNftId); - - error ErrorBundleServiceFeesWithdrawAmountExceedsLimit(Amount amount, Amount limit); error ErrorBundleServiceUnstakeAmountExceedsLimit(Amount amount, Amount limit); error ErrorBundleServiceExtensionLifetimeIsZero(); - event LogBundleServiceFeesWithdrawn(NftId bundleNftId, address recipient, address tokenAddress, Amount amount); event LogBundleServiceBundleExtended(NftId bundleNftId, Seconds lifetimeExtension, Timestamp extendedExpiredAt); /// @dev Create a new bundle for the specified attributes. @@ -48,15 +48,16 @@ interface IBundleService is IService { /// @dev increase bundle stakes by the specified amount. bundle must not be expired or closed /// may only be called by the pool service - function stake(IInstance instance, NftId bundleNftId, Amount amount) external; + function stake( + InstanceReader instanceReader, + InstanceStore instanceStore, + NftId bundleNftId, + Amount amount + ) external; /// @dev decrease bundle stakes by the specified amount /// may only be called by the pool service - /// @param instance the instance relevant for the bundle - /// @param bundleNftId the bundle nft id - /// @param amount the amount to unstake (set to AmountLib.max() to unstake all available stakes) - /// @return unstakedAmount the effective unstaked amount - function unstake(IInstance instance, NftId bundleNftId, Amount amount) external returns (Amount unstakedAmount); + function unstake(InstanceStore instanceStore, NftId bundleNftId, Amount amount) external returns (Amount unstakedAmount); /// @dev extend the lifetime of the bundle by the specified time in seconds function extend(NftId bundleNftId, Seconds lifetimeExtension) external returns (Timestamp extendedExpiredAt); @@ -106,16 +107,9 @@ interface IBundleService is IService { /// @dev releases the specified collateral in the bundle /// may only be called by pool service function releaseCollateral( - IInstance instance, + InstanceStore instanceStore, NftId policyNftId, NftId bundleNftId, Amount collateralAmount ) external; - - // FIXME: move to pool service - /// @dev Withdraw bundle feeds for the given bundle - /// @param bundleNftId the bundle Nft Id - /// @param amount the amount to withdraw. If set to AMOUNT_MAX, the full commission available is withdrawn - /// @return withdrawnAmount the effective withdrawn amount - function withdrawBundleFees(NftId bundleNftId, Amount amount) external returns (Amount withdrawnAmount); } diff --git a/contracts/pool/IPoolService.sol b/contracts/pool/IPoolService.sol index 71de3f970..7ccf9a4c1 100644 --- a/contracts/pool/IPoolService.sol +++ b/contracts/pool/IPoolService.sol @@ -1,13 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Amount} from "../type/Amount.sol"; -import {ClaimId} from "../type/ClaimId.sol"; import {IInstance} from "../instance/IInstance.sol"; -import {InstanceReader} from "../instance/InstanceReader.sol"; import {IPolicy} from "../instance/module/IPolicy.sol"; import {IService} from "../shared/IService.sol"; + +import {Amount} from "../type/Amount.sol"; +import {ClaimId} from "../type/ClaimId.sol"; +import {InstanceReader} from "../instance/InstanceReader.sol"; +import {InstanceStore} from "../instance/InstanceStore.sol"; import {NftId} from "../type/NftId.sol"; +import {PayoutId} from "../type/PayoutId.sol"; import {UFixed} from "../type/UFixed.sol"; interface IPoolService is IService { @@ -22,6 +25,8 @@ interface IPoolService is IService { event LogPoolServiceBundleStaked(NftId instanceNftId, NftId poolNftId, NftId bundleNftId, Amount amount, Amount netAmount); event LogPoolServiceBundleUnstaked(NftId instanceNftId, NftId poolNftId, NftId bundleNftId, Amount amount, Amount netAmount); + event LogPoolServiceFeesWithdrawn(NftId bundleNftId, address recipient, address tokenAddress, Amount amount); + event LogPoolServiceProcessFundedClaim(NftId policyNftId, ClaimId claimId, Amount availableAmount); error ErrorPoolServicePoolNotExternallyManaged(NftId poolNftId); @@ -30,6 +35,7 @@ interface IPoolService is IService { error ErrorPoolServiceInvalidTransferAmount(Amount expectedAmount, Amount actualAmount); error ErrorPoolServiceBundlePoolMismatch(NftId bundleNftId, NftId poolNftId); error ErrorPoolServiceMaxBalanceAmountExceeded(NftId poolNftId, Amount maxBalanceAmount, Amount currentBalanceAmount, Amount transferAmount); + error ErrorPoolServiceFeesWithdrawAmountExceedsLimit(Amount amount, Amount limit); /// @dev sets the max balance amount for the calling pool function setMaxBalanceAmount(Amount maxBalanceAmount) external; @@ -58,7 +64,6 @@ interface IPoolService is IService { /// may only be called by the policy service for unlocked pool components function releaseCollateral( IInstance instance, - address token, NftId policyNftId, IPolicy.PolicyInfo memory policyInfo ) external; @@ -68,11 +73,14 @@ interface IPoolService is IService { /// every payout of a policy reduces the collateral by the payout amount /// may only be called by the claim service for unlocked pool components function processPayout( - IInstance instance, - address token, + InstanceReader instanceReader, + InstanceStore instanceStore, + NftId productNftId, NftId policyNftId, - IPolicy.PolicyInfo memory policyInfo, - Amount payoutAmount + NftId bundleNftId, + PayoutId payoutId, + Amount payoutAmount, + address payoutBeneficiary ) external; @@ -96,6 +104,10 @@ interface IPoolService is IService { function closeBundle(NftId bundleNftId) external; + /// @dev Withdraw bundle feeds for the specified bundle. + function withdrawBundleFees(NftId bundleNftId, Amount amount) external returns (Amount withdrawnAmount); + + /// @dev Informs product about available funds to process a confirmed claim. /// The function triggers a callback to the product component when the product's property isProcessingFundedClaims is set. function processFundedClaim(NftId policyNftId, ClaimId claimId, Amount availableAmount) external; @@ -119,35 +131,35 @@ interface IPoolService is IService { function processSale(NftId bundleNftId, IPolicy.PremiumInfo memory premium) external; - /// @dev Calulate required collateral for the provided parameters. - function calculateRequiredCollateral( - InstanceReader instanceReader, - NftId productNftId, - Amount sumInsuredAmount - ) - external - view - returns( - NftId poolNftId, - Amount totalCollateralAmount, - Amount localCollateralAmount, - bool poolIsVerifyingApplications - ); - - - /// @dev calulate required collateral for the provided parameters. - /// Collateralization is applied to sum insured. - /// Retention level defines the fraction of the collateral that is required locally. - function calculateRequiredCollateral( - UFixed collateralizationLevel, - UFixed retentionLevel, - Amount sumInsuredAmount - ) - external - pure - returns( - Amount totalCollateralAmount, - Amount localCollateralAmount - ); + // /// @dev Calulate required collateral for the provided parameters. + // function calculateRequiredCollateral( + // InstanceReader instanceReader, + // NftId productNftId, + // Amount sumInsuredAmount + // ) + // external + // view + // returns( + // NftId poolNftId, + // Amount totalCollateralAmount, + // Amount localCollateralAmount, + // bool poolIsVerifyingApplications + // ); + + + // /// @dev calulate required collateral for the provided parameters. + // /// Collateralization is applied to sum insured. + // /// Retention level defines the fraction of the collateral that is required locally. + // function calculateRequiredCollateral( + // UFixed collateralizationLevel, + // UFixed retentionLevel, + // Amount sumInsuredAmount + // ) + // external + // pure + // returns( + // Amount totalCollateralAmount, + // Amount localCollateralAmount + // ); } diff --git a/contracts/pool/Pool.sol b/contracts/pool/Pool.sol index 813f0ea91..0025d291f 100644 --- a/contracts/pool/Pool.sol +++ b/contracts/pool/Pool.sol @@ -240,15 +240,6 @@ abstract contract Pool is } - /// @dev Withdraws the specified amount of fees from the bundle. - function _withdrawBundleFees(NftId bundleNftId, Amount amount) - internal - returns (Amount withdrawnAmount) - { - return _getPoolStorage()._bundleService.withdrawBundleFees(bundleNftId, amount); - } - - /// @dev increases the staked tokens by the specified amount /// bundle MUST be in active or locked state function _stake( @@ -325,6 +316,15 @@ abstract contract Pool is } + /// @dev Withdraws the specified amount of fees from the bundle. + function _withdrawBundleFees(NftId bundleNftId, Amount amount) + internal + returns (Amount withdrawnAmount) + { + return _getPoolStorage()._poolService.withdrawBundleFees(bundleNftId, amount); + } + + function _processFundedClaim( NftId policyNftId, ClaimId claimId, diff --git a/contracts/pool/PoolLib.sol b/contracts/pool/PoolLib.sol new file mode 100644 index 000000000..8124ee048 --- /dev/null +++ b/contracts/pool/PoolLib.sol @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {IComponents} from "../instance/module/IComponents.sol"; +import {IInstance} from "../instance/IInstance.sol"; +import {INftOwnable} from "../shared/INftOwnable.sol"; +import {IPolicyHolder} from "../shared/IPolicyHolder.sol"; +import {IPoolService} from "./IPoolService.sol"; +import {IRegistry} from "../registry/IRegistry.sol"; + +import {Amount, AmountLib} from "../type/Amount.sol"; +import {ContractLib} from "../shared/ContractLib.sol"; +import {Fee, FeeLib} from "../type/Fee.sol"; +import {InstanceReader} from "../instance/InstanceReader.sol"; +import {InstanceStore} from "../instance/InstanceStore.sol"; +import {NftId} from "../type/NftId.sol"; +import {ObjectType, BUNDLE, POOL} from "../type/ObjectType.sol"; +import {UFixed} from "../type/UFixed.sol"; + +library PoolLib { + + /// @dev Calulate required collateral for the provided parameters. + function calculateRequiredCollateral( + InstanceReader instanceReader, + NftId productNftId, + Amount sumInsuredAmount + ) + public + view + returns( + NftId poolNftId, + Amount totalCollateralAmount, + Amount localCollateralAmount, + bool poolIsVerifyingApplications + ) + { + poolNftId = instanceReader.getProductInfo(productNftId).poolNftId; + IComponents.PoolInfo memory poolInfo = instanceReader.getPoolInfo(poolNftId); + poolIsVerifyingApplications = poolInfo.isVerifyingApplications; + + ( + totalCollateralAmount, + localCollateralAmount + ) = calculateRequiredCollateral( + poolInfo.collateralizationLevel, + poolInfo.retentionLevel, + sumInsuredAmount); + } + + + /// @dev calulate required collateral for the provided parameters. + /// Collateralization is applied to sum insured. + /// Retention level defines the fraction of the collateral that is required locally. + function calculateRequiredCollateral( + UFixed collateralizationLevel, + UFixed retentionLevel, + Amount sumInsuredAmount + ) + public + pure + returns( + Amount totalCollateralAmount, + Amount localCollateralAmount + ) + { + // collateralization is applied to sum insured + UFixed totalUFixed = collateralizationLevel * sumInsuredAmount.toUFixed(); + totalCollateralAmount = AmountLib.toAmount(totalUFixed.toInt()); + + // retention level defines how much capital is required locally + localCollateralAmount = AmountLib.toAmount( + (retentionLevel * totalUFixed).toInt()); + } + + + function calculateStakingAmounts( + IRegistry registry, + InstanceReader instanceReader, + NftId poolNftId, + Amount stakingAmount + ) + public + view + returns ( + Amount feeAmount, + Amount netStakingAmount + ) + { + NftId productNftId = registry.getParentNftId(poolNftId); + Fee memory stakingFee = instanceReader.getFeeInfo(productNftId).stakingFee; + ( + feeAmount, + netStakingAmount + ) = FeeLib.calculateFee( + stakingFee, + stakingAmount); + } + + + function calculatePayoutAmounts( + IRegistry registry, + InstanceReader instanceReader, + NftId productNftId, + NftId policyNftId, + Amount payoutAmount, + address payoutBeneficiary + ) + external + view + returns ( + Amount netPayoutAmount, + Amount processingFeeAmount, + address beneficiary + ) + { + // Amount payoutAmount = payoutInfo.amount; + + if(payoutAmount.gtz()) { + netPayoutAmount = payoutAmount; + + if (payoutBeneficiary == address(0)) { + beneficiary = registry.ownerOf(policyNftId); + } else { + beneficiary = payoutBeneficiary; + } + + // calculate processing fees if applicable + IComponents.FeeInfo memory feeInfo = instanceReader.getFeeInfo(productNftId); + if(FeeLib.gtz(feeInfo.processingFee)) { + // TODO calculate and set net payout and processing fees + } + } + } + + + function getPolicyHolder( + IRegistry registry, + NftId policyNftId + ) + internal + view + returns (IPolicyHolder policyHolder) + { + address policyHolderAddress = registry.ownerOf(policyNftId); + policyHolder = IPolicyHolder(policyHolderAddress); + + if (!ContractLib.isPolicyHolder(policyHolderAddress)) { + policyHolder = IPolicyHolder(address(0)); + } + } + + + function checkAndGetPoolInfo( + IRegistry registry, + address sender, + NftId bundleNftId + ) + public + view + returns ( + InstanceReader instanceReader, + InstanceStore instanceStore, + NftId instanceNftId, + NftId poolNftId, + IComponents.PoolInfo memory poolInfo + ) + { + checkNftType(registry, bundleNftId, BUNDLE()); + + IInstance instance; + (poolNftId, instance) = getAndVerifyActivePool(registry, sender); + instanceReader = instance.getInstanceReader(); + instanceStore = instance.getInstanceStore(); + instanceNftId = instance.getNftId(); + poolInfo = instanceReader.getPoolInfo(poolNftId); + + if (registry.getParentNftId(bundleNftId) != poolNftId) { + revert IPoolService.ErrorPoolServiceBundlePoolMismatch(bundleNftId, poolNftId); + } + } + + + function getAndVerifyActivePool( + IRegistry registry, + address sender + ) + public + view + returns ( + NftId poolNftId, + IInstance instance + ) + { + ( + IRegistry.ObjectInfo memory info, + address instanceAddress + ) = ContractLib.getAndVerifyComponent( + registry, + sender, + POOL(), + true); // only active pools + + poolNftId = info.nftId; + instance = IInstance(instanceAddress); + } + + function checkNftType( + IRegistry registry, + NftId nftId, + ObjectType expectedObjectType + ) internal view { + if(!registry.isObjectType(nftId, expectedObjectType)) { + revert INftOwnable.ErrorNftOwnableInvalidType(nftId, expectedObjectType); + } + } +} diff --git a/contracts/pool/PoolService.sol b/contracts/pool/PoolService.sol index 498ac5c29..865d68cac 100644 --- a/contracts/pool/PoolService.sol +++ b/contracts/pool/PoolService.sol @@ -8,6 +8,7 @@ import {IComponents} from "../instance/module/IComponents.sol"; import {IComponentService} from "../shared/IComponentService.sol"; import {IInstance} from "../instance/IInstance.sol"; import {IPolicy} from "../instance/module/IPolicy.sol"; +import {IPolicyHolder} from "../shared/IPolicyHolder.sol"; import {IPoolComponent} from "../pool/IPoolComponent.sol"; import {IPoolService} from "./IPoolService.sol"; import {IProductComponent} from "../product/IProductComponent.sol"; @@ -17,18 +18,20 @@ import {IStaking} from "../staking/IStaking.sol"; import {Amount, AmountLib} from "../type/Amount.sol"; import {ClaimId} from "../type/ClaimId.sol"; import {ContractLib} from "../shared/ContractLib.sol"; -import {Fee, FeeLib} from "../type/Fee.sol"; +import {InstanceReader} from "../instance/InstanceReader.sol"; +import {InstanceStore} from "../instance/InstanceStore.sol"; +import {KEEP_STATE} from "../type/StateId.sol"; import {NftId} from "../type/NftId.sol"; import {ObjectType, ACCOUNTING, POOL, BUNDLE, PRODUCT, POLICY, COMPONENT} from "../type/ObjectType.sol"; -import {Fee, FeeLib} from "../type/Fee.sol"; -import {KEEP_STATE} from "../type/StateId.sol"; -import {UFixed} from "../type/UFixed.sol"; +import {PayoutId} from "../type/PayoutId.sol"; +import {PoolLib} from "./PoolLib.sol"; import {Service} from "../shared/Service.sol"; -import {InstanceReader} from "../instance/InstanceReader.sol"; -import {InstanceStore} from "../instance/InstanceStore.sol"; +import {TokenHandler} from "../shared/TokenHandler.sol"; +import {UFixed} from "../type/UFixed.sol"; string constant POOL_SERVICE_NAME = "PoolService"; + contract PoolService is Service, IPoolService @@ -102,8 +105,7 @@ contract PoolService is if ((unstakedAmount + feeAmount).gtz()){ IComponents.ComponentInfo memory poolComponentInfo = instance.getInstanceReader().getComponentInfo(poolNftId); - poolComponentInfo.tokenHandler.distributeTokens( - poolComponentInfo.tokenHandler.getWallet(), + poolComponentInfo.tokenHandler.pushToken( getRegistry().ownerOf(bundleNftId), unstakedAmount + feeAmount); } @@ -123,7 +125,7 @@ contract PoolService is (NftId poolNftId, IInstance instance) = _getAndVerifyActivePool(); InstanceReader instanceReader = instance.getInstanceReader(); - NftId productNftId = getRegistry().getObjectInfo(poolNftId).parentNftId; + NftId productNftId = getRegistry().getParentNftId(poolNftId); // check policy matches with calling pool IPolicy.PolicyInfo memory policyInfo = instanceReader.getPolicyInfo(policyNftId); @@ -144,23 +146,47 @@ contract PoolService is } + // function _checkAndGetPoolInfo(NftId bundleNftId) + // internal + // view + // returns ( + // InstanceReader instanceReader, + // InstanceStore instanceStore, + // NftId instanceNftId, + // NftId poolNftId, + // IComponents.PoolInfo memory poolInfo + // ) + // { + // _checkNftType(bundleNftId, BUNDLE()); + + // (NftId poolNftId, IInstance instance) = _getAndVerifyActivePool(); + // instanceReader = instance.getInstanceReader(); + // instanceStore = instance.getInstanceStore(); + // instanceNftId = instance.getNftId(); + // poolInfo = instanceReader.getPoolInfo(poolNftId); + + // if (getRegistry().getParentNftId(bundleNftId) != poolNftId) { + // revert ErrorPoolServiceBundlePoolMismatch(bundleNftId, poolNftId); + // } + // } + + /// @inheritdoc IPoolService function stake(NftId bundleNftId, Amount amount) external virtual // TODO: restricted() (once #462 is done) - returns(Amount netAmount) + returns( + Amount netAmount + ) { - _checkNftType(bundleNftId, BUNDLE()); - - (NftId poolNftId, IInstance instance) = _getAndVerifyActivePool(); - InstanceReader instanceReader = instance.getInstanceReader(); - IBundle.BundleInfo memory bundleInfo = instanceReader.getBundleInfo(bundleNftId); - IComponents.PoolInfo memory poolInfo = instanceReader.getPoolInfo(poolNftId); - - if (bundleInfo.poolNftId != poolNftId) { - revert ErrorPoolServiceBundlePoolMismatch(bundleNftId, poolNftId); - } + ( + InstanceReader instanceReader, + InstanceStore instanceStore, + NftId instanceNftId, + NftId poolNftId, + IComponents.PoolInfo memory poolInfo + ) = PoolLib.checkAndGetPoolInfo(getRegistry(), msg.sender, bundleNftId); { Amount currentPoolBalance = instanceReader.getBalanceAmount(poolNftId); @@ -170,37 +196,33 @@ contract PoolService is } // calculate fees - IRegistry registry = getRegistry(); Amount feeAmount; - - { - NftId productNftId = registry.getObjectInfo(poolNftId).parentNftId; - Fee memory stakingFee = instanceReader.getFeeInfo(productNftId).stakingFee; - ( - feeAmount, - netAmount - ) = FeeLib.calculateFee( - stakingFee, - amount); - } + ( + feeAmount, + netAmount + ) = PoolLib.calculateStakingAmounts( + getRegistry(), + instanceReader, + poolNftId, + amount); // do all the book keeping _accountingService.increasePoolBalance( - instance.getInstanceStore(), + instanceStore, poolNftId, netAmount, feeAmount); - _bundleService.stake(instance, bundleNftId, netAmount); + _bundleService.stake(instanceReader, instanceStore, bundleNftId, netAmount); - emit LogPoolServiceBundleStaked(instance.getNftId(), poolNftId, bundleNftId, amount, netAmount); + emit LogPoolServiceBundleStaked(instanceNftId, poolNftId, bundleNftId, amount, netAmount); // only collect staking amount when pool is not externally managed if (!poolInfo.isExternallyManaged) { // collect tokens from bundle owner address bundleOwner = getRegistry().ownerOf(bundleNftId); - _collectStakingAmount( + _pullStakingAmount( instanceReader, poolNftId, bundleOwner, @@ -216,19 +238,15 @@ contract PoolService is // TODO: restricted() (once #462 is done) returns(Amount netAmount) { - _checkNftType(bundleNftId, BUNDLE()); - - (NftId poolNftId, IInstance instance) = _getAndVerifyActivePool(); - InstanceReader instanceReader = instance.getInstanceReader(); - InstanceStore instanceStore = instance.getInstanceStore(); - IBundle.BundleInfo memory bundleInfo = instanceReader.getBundleInfo(bundleNftId); - - if (bundleInfo.poolNftId != poolNftId) { - revert ErrorPoolServiceBundlePoolMismatch(bundleNftId, poolNftId); - } + ( + InstanceReader instanceReader, + InstanceStore instanceStore, + NftId instanceNftId, + NftId poolNftId, + ) = PoolLib.checkAndGetPoolInfo(getRegistry(), msg.sender, bundleNftId); // call bundle service for bookkeeping and additional checks - Amount unstakedAmount = _bundleService.unstake(instance, bundleNftId, amount); + Amount unstakedAmount = _bundleService.unstake(instanceStore, bundleNftId, amount); // Important: from now on work only with unstakedAmount as it is the only reliable amount. // if amount was max, this was set to the available amount @@ -244,14 +262,14 @@ contract PoolService is AmountLib.zero()); - emit LogPoolServiceBundleUnstaked(instance.getNftId(), poolNftId, bundleNftId, unstakedAmount, netAmount); + emit LogPoolServiceBundleUnstaked(instanceNftId, poolNftId, bundleNftId, unstakedAmount, netAmount); // only distribute staking amount when pool is not externally managed if (!instanceReader.getPoolInfo(poolNftId).isExternallyManaged) { // transfer amount to bundle owner address bundleOwner = getRegistry().ownerOf(bundleNftId); - _distributeUnstakingAmount( + _pushUnstakingAmount( instanceReader, poolNftId, bundleOwner, @@ -279,7 +297,7 @@ contract PoolService is address poolOwner = getRegistry().ownerOf(poolNftId); emit LogPoolServiceWalletFunded(poolNftId, poolOwner, amount); - _collectStakingAmount( + _pullStakingAmount( reader, poolNftId, poolOwner, @@ -306,7 +324,7 @@ contract PoolService is address poolOwner = getRegistry().ownerOf(poolNftId); emit LogPoolServiceWalletDefunded(poolNftId, poolOwner, amount); - _distributeUnstakingAmount( + _pushUnstakingAmount( reader, poolNftId, poolOwner, @@ -325,7 +343,7 @@ contract PoolService is _checkNftType(bundleNftId, BUNDLE()); IRegistry registry = getRegistry(); - NftId poolNftId = registry.getObjectInfo(bundleNftId).parentNftId; + NftId poolNftId = registry.getParentNftId(bundleNftId); (, address instanceAddress) = ContractLib.getInfoAndInstance(registry, poolNftId, true); IInstance instance = IInstance(instanceAddress); @@ -375,7 +393,7 @@ contract PoolService is totalCollateralAmount, localCollateralAmount, poolIsVerifyingApplications - ) = calculateRequiredCollateral( + ) = PoolLib.calculateRequiredCollateral( instance.getInstanceReader(), productNftId, sumInsuredAmount); @@ -410,21 +428,24 @@ contract PoolService is } function processPayout( - IInstance instance, - address token, + InstanceReader instanceReader, + InstanceStore instanceStore, + NftId productNftId, NftId policyNftId, - IPolicy.PolicyInfo memory policyInfo, - Amount payoutAmount + NftId bundleNftId, + PayoutId payoutId, + Amount payoutAmount, + address payoutBeneficiary ) external virtual restricted() { + // checks _checkNftType(policyNftId, POLICY()); - NftId bundleNftId = policyInfo.bundleNftId; - NftId poolNftId = getRegistry().getObjectInfo(bundleNftId).parentNftId; - InstanceStore instanceStore = instance.getInstanceStore(); + // effects + NftId poolNftId = getRegistry().getParentNftId(bundleNftId); _accountingService.decreasePoolBalance( instanceStore, @@ -439,16 +460,123 @@ contract PoolService is AmountLib.zero()); _bundleService.releaseCollateral( - instance, + instanceStore, policyNftId, - policyInfo.bundleNftId, + bundleNftId, payoutAmount); // update value locked with staking service + TokenHandler poolTokenHandler = TokenHandler( + instanceReader.getTokenHandler( + poolNftId)); + _staking.decreaseTotalValueLocked( - instance.getNftId(), - token, + instanceReader.getInstanceNftId(), + address(poolTokenHandler.TOKEN()), payoutAmount); + + // interactions + _transferTokenAndNotifyPolicyHolder( + instanceReader, + poolTokenHandler, + productNftId, + policyNftId, + payoutId, + payoutAmount, + payoutBeneficiary); + } + + function _transferTokenAndNotifyPolicyHolder( + InstanceReader instanceReader, + TokenHandler poolTokenHandler, + NftId productNftId, + NftId policyNftId, + PayoutId payoutId, + Amount payoutAmount, + address payoutBeneficiary + ) + internal + { + ( + Amount netPayoutAmount, + Amount processingFeeAmount, + address beneficiary + ) = PoolLib.calculatePayoutAmounts( + getRegistry(), + instanceReader, + productNftId, + policyNftId, + payoutAmount, + payoutBeneficiary); + + // 1st token tx to payout to beneficiary + poolTokenHandler.pushToken( + beneficiary, + netPayoutAmount); + + // 2nd token tx to transfer processing fees to product wallet + // if processingFeeAmount > 0 + if (processingFeeAmount.gtz()) { + poolTokenHandler.pushToken( + instanceReader.getWallet(productNftId), + processingFeeAmount); + } + + // callback to policy holder if applicable + _policyHolderPayoutExecuted( + policyNftId, + payoutId, + beneficiary, + netPayoutAmount); + } + + + /// @inheritdoc IPoolService + function withdrawBundleFees( + NftId bundleNftId, + Amount amount + ) + public + virtual + restricted() + returns (Amount withdrawnAmount) + { + // checks + _checkNftType(bundleNftId, BUNDLE()); + + (NftId poolNftId, IInstance instance) = _getAndVerifyActivePool(); + InstanceReader reader = instance.getInstanceReader(); + + // determine withdrawn amount + withdrawnAmount = amount; + if (withdrawnAmount.gte(AmountLib.max())) { + withdrawnAmount = reader.getFeeAmount(bundleNftId); + } else { + if (withdrawnAmount > reader.getFeeAmount(bundleNftId)) { + revert ErrorPoolServiceFeesWithdrawAmountExceedsLimit(withdrawnAmount, reader.getFeeAmount(bundleNftId)); + } + } + + // effects + // decrease fee counters by withdrawnAmount + { + InstanceStore store = instance.getInstanceStore(); + // decrease fee amount of the bundle + _accountingService.decreaseBundleBalanceForPool(store, bundleNftId, AmountLib.zero(), withdrawnAmount); + // decrease pool balance + _accountingService.decreasePoolBalance(store, poolNftId, withdrawnAmount, AmountLib.zero()); + } + + // interactions + // transfer amount to bundle owner + { + address bundleOwner = getRegistry().ownerOf(bundleNftId); + TokenHandler tokenHandler = reader.getTokenHandler(poolNftId); + address token = address(tokenHandler.TOKEN()); + emit LogPoolServiceFeesWithdrawn(bundleNftId, bundleOwner, token, withdrawnAmount); + + tokenHandler.pushToken(bundleOwner, withdrawnAmount); + } } @@ -456,7 +584,6 @@ contract PoolService is /// may only be called by the policy service for unlocked pool components function releaseCollateral( IInstance instance, - address token, NftId policyNftId, IPolicy.PolicyInfo memory policyInfo ) @@ -469,93 +596,97 @@ contract PoolService is Amount remainingCollateralAmount = policyInfo.sumInsuredAmount - policyInfo.claimAmount; _bundleService.releaseCollateral( - instance, + instance.getInstanceStore(), policyNftId, policyInfo.bundleNftId, remainingCollateralAmount); // update value locked with staking service + InstanceReader instanceReader = instance.getInstanceReader(); _staking.decreaseTotalValueLocked( - instance.getNftId(), - token, + instanceReader.getInstanceNftId(), + address(instanceReader.getToken(policyInfo.productNftId)), remainingCollateralAmount); } - function calculateRequiredCollateral( - InstanceReader instanceReader, - NftId productNftId, - Amount sumInsuredAmount - ) - public - view - returns( - NftId poolNftId, - Amount totalCollateralAmount, - Amount localCollateralAmount, - bool poolIsVerifyingApplications - ) - { - _checkNftType(productNftId, PRODUCT()); - - poolNftId = instanceReader.getProductInfo(productNftId).poolNftId; - IComponents.PoolInfo memory poolInfo = instanceReader.getPoolInfo(poolNftId); - poolIsVerifyingApplications = poolInfo.isVerifyingApplications; - - ( - totalCollateralAmount, - localCollateralAmount - ) = calculateRequiredCollateral( - poolInfo.collateralizationLevel, - poolInfo.retentionLevel, - sumInsuredAmount); - } - - - function calculateRequiredCollateral( - UFixed collateralizationLevel, - UFixed retentionLevel, - Amount sumInsuredAmount - ) - public - pure - returns( - Amount totalCollateralAmount, - Amount localCollateralAmount - ) - { - // collateralization is applied to sum insured - UFixed totalUFixed = collateralizationLevel * sumInsuredAmount.toUFixed(); - totalCollateralAmount = AmountLib.toAmount(totalUFixed.toInt()); - - // retention level defines how much capital is required locally - localCollateralAmount = AmountLib.toAmount( - (retentionLevel * totalUFixed).toInt()); - } - - - function _processStakingFees( - Fee memory stakingFee, - Amount stakingAmount + // function calculateRequiredCollateral( + // InstanceReader instanceReader, + // NftId productNftId, + // Amount sumInsuredAmount + // ) + // public + // view + // returns( + // NftId poolNftId, + // Amount totalCollateralAmount, + // Amount localCollateralAmount, + // bool poolIsVerifyingApplications + // ) + // { + // return CollateralLib.calculateRequiredCollateral( + // instanceReader, + // productNftId, + // sumInsuredAmount); + // } + + // _checkNftType(productNftId, PRODUCT()); + + // poolNftId = instanceReader.getProductInfo(productNftId).poolNftId; + // IComponents.PoolInfo memory poolInfo = instanceReader.getPoolInfo(poolNftId); + // poolIsVerifyingApplications = poolInfo.isVerifyingApplications; + + // ( + // totalCollateralAmount, + // localCollateralAmount + // ) = calculateRequiredCollateral( + // poolInfo.collateralizationLevel, + // poolInfo.retentionLevel, + // sumInsuredAmount); + // } + + + // function calculateRequiredCollateral( + // UFixed collateralizationLevel, + // UFixed retentionLevel, + // Amount sumInsuredAmount + // ) + // public + // pure + // returns( + // Amount totalCollateralAmount, + // Amount localCollateralAmount + // ) + // { + // // collateralization is applied to sum insured + // UFixed totalUFixed = collateralizationLevel * sumInsuredAmount.toUFixed(); + // totalCollateralAmount = AmountLib.toAmount(totalUFixed.toInt()); + + // // retention level defines how much capital is required locally + // localCollateralAmount = AmountLib.toAmount( + // (retentionLevel * totalUFixed).toInt()); + // } + + + + + function _policyHolderPayoutExecuted( + NftId policyNftId, + PayoutId payoutId, + address beneficiary, + Amount payoutAmount ) internal - pure - returns (Amount stakingNetAmount) { - stakingNetAmount = stakingAmount; - - // check if any staking fees apply - if (FeeLib.gtz(stakingFee)) { - (Amount feeAmount, Amount netAmount) = FeeLib.calculateFee(stakingFee, stakingAmount); - stakingNetAmount = netAmount; - - // TODO update fee balance for pool + IPolicyHolder policyHolder = PoolLib.getPolicyHolder(getRegistry(), policyNftId); + if(address(policyHolder) != address(0)) { + policyHolder.payoutExecuted(policyNftId, payoutId, payoutAmount, beneficiary); } } - /// @dev transfers the specified amount from the "from account" to the pool's wallet - function _collectStakingAmount( + /// @dev Transfers the specified amount from the "from account" to the pool's wallet + function _pullStakingAmount( InstanceReader reader, NftId poolNftId, address from, @@ -564,13 +695,13 @@ contract PoolService is internal { IComponents.ComponentInfo memory info = reader.getComponentInfo(poolNftId); - info.tokenHandler.collectTokens( + info.tokenHandler.pullToken( from, amount); } - /// @dev distributes the specified amount from the pool's wallet to the "to account" - function _distributeUnstakingAmount( + /// @dev Transfers the specified amount from the pool's wallet to the "to account" + function _pushUnstakingAmount( InstanceReader reader, NftId poolNftId, address to, @@ -579,8 +710,7 @@ contract PoolService is internal { IComponents.ComponentInfo memory info = reader.getComponentInfo(poolNftId); - info.tokenHandler.distributeTokens( - info.tokenHandler.getWallet(), + info.tokenHandler.pushToken( to, amount); } @@ -595,19 +725,31 @@ contract PoolService is IInstance instance ) { - ( - IRegistry.ObjectInfo memory info, - address instanceAddress - ) = ContractLib.getAndVerifyComponent( - getRegistry(), - msg.sender, - POOL(), - true); // only active pools - - poolNftId = info.nftId; - instance = IInstance(instanceAddress); + return PoolLib.getAndVerifyActivePool(getRegistry(), msg.sender); } + // function _getAndVerifyActivePool() + // internal + // virtual + // view + // returns ( + // NftId poolNftId, + // IInstance instance + // ) + // { + // ( + // IRegistry.ObjectInfo memory info, + // address instanceAddress + // ) = ContractLib.getAndVerifyComponent( + // getRegistry(), + // msg.sender, + // POOL(), + // true); // only active pools + + // poolNftId = info.nftId; + // instance = IInstance(instanceAddress); + // } + function _getDomain() internal pure override returns(ObjectType) { return POOL(); diff --git a/contracts/product/BasicProductAuthorization.sol b/contracts/product/BasicProductAuthorization.sol index 4d434ab4f..63ec54f67 100644 --- a/contracts/product/BasicProductAuthorization.sol +++ b/contracts/product/BasicProductAuthorization.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Authorization} from "../authorization/Authorization.sol"; -import {BasicProduct} from "./BasicProduct.sol"; -import {PRODUCT} from "../type/ObjectType.sol"; import {IAccess} from "../authorization/IAccess.sol"; import {IInstanceLinkedComponent} from "../shared/IInstanceLinkedComponent.sol"; + +import {Authorization} from "../authorization/Authorization.sol"; +import {BasicProduct} from "./BasicProduct.sol"; +import {COMPONENT, PRODUCT, POLICY} from "../type/ObjectType.sol"; import {RoleId, PUBLIC_ROLE} from "../type/RoleId.sol"; +import {TokenHandler} from "../shared/TokenHandler.sol"; contract BasicProductAuthorization @@ -14,17 +16,30 @@ contract BasicProductAuthorization { constructor(string memory componentName) - Authorization(componentName) + Authorization(componentName, PRODUCT()) {} - function _setupTargets() + function _setupServiceTargets() internal virtual override { - // basic component target - _addComponentTargetWithRole(PRODUCT()); + _addServiceTargetWithRole(COMPONENT()); + _addServiceTargetWithRole(POLICY()); } + function _setupTokenHandlerAuthorizations() internal virtual override { + // authorize token handler functions for component service role + IAccess.FunctionInfo[] storage functions; + functions = _authorizeForTarget(getTokenHandlerName(), getServiceRole(COMPONENT())); + _authorize(functions, TokenHandler.approve.selector, "approve"); + _authorize(functions, TokenHandler.setWallet.selector, "setWallet"); + _authorize(functions, TokenHandler.pushFeeToken.selector, "pushFeeToken"); + + // authorize token handler functions for pool service role + functions = _authorizeForTarget(getTokenHandlerName(), getServiceRole(POLICY())); + _authorize(functions, TokenHandler.pullToken.selector, "pullToken"); + _authorize(functions, TokenHandler.pushToken.selector, "pushToken"); + } function _setupTargetAuthorizations() internal @@ -33,7 +48,7 @@ contract BasicProductAuthorization IAccess.FunctionInfo[] storage functions; // authorize public role (open access to any account, only allows to lock target) - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); + functions = _authorizeForTarget(getMainTargetName(), PUBLIC_ROLE()); _authorize(functions, BasicProduct.setFees.selector, "setFees"); _authorize(functions, IInstanceLinkedComponent.withdrawFees.selector, "withdrawFees"); } diff --git a/contracts/product/ClaimService.sol b/contracts/product/ClaimService.sol index 6abd22390..2dff21cd3 100644 --- a/contracts/product/ClaimService.sol +++ b/contracts/product/ClaimService.sol @@ -318,8 +318,8 @@ contract ClaimService is { // checks ( - , - IInstance instance, + NftId productNftId,, + // IInstance instance, InstanceReader instanceReader, InstanceStore instanceStore, IPolicy.PolicyInfo memory policyInfo @@ -347,8 +347,8 @@ contract ClaimService is payoutInfo.paidAt = TimestampLib.blockTimestamp(); instanceStore.updatePayout(policyNftId, payoutId, payoutInfo, PAID()); + // update and save claim info with instance Amount payoutAmount = payoutInfo.amount; - { ClaimId claimId = payoutId.toClaimId(); IPolicy.ClaimInfo memory claimInfo = instanceReader.getClaimInfo(policyNftId, claimId); @@ -371,41 +371,19 @@ contract ClaimService is policyInfo.payoutAmount = policyInfo.payoutAmount + payoutAmount; instanceStore.updatePolicyClaims(policyNftId, policyInfo, KEEP_STATE()); - // inform pool about payout + emit LogClaimServicePayoutProcessed(policyNftId, payoutId, payoutAmount); + + // effects + interactions (push tokens to beneficiary, product) + // delegate to pool to update book keeping and moving tokens payout _poolService.processPayout( - instance, - address(instanceReader.getComponentInfo(policyInfo.productNftId).token), + instanceReader, + instanceStore, + policyInfo.productNftId, // product nft id policyNftId, - policyInfo, - payoutAmount); - - // transfer payout token and fee - { - ( - Amount netPayoutAmount, - Amount processingFeeAmount, - address beneficiary - ) = _calculatePayoutAmount( - instanceReader, - policyNftId, - policyInfo, - payoutInfo); - - emit LogClaimServicePayoutProcessed(policyNftId, payoutId, payoutAmount, beneficiary, netPayoutAmount, processingFeeAmount); - - { - NftId poolNftId = getRegistry().getObjectInfo(policyInfo.bundleNftId).parentNftId; - IComponents.ComponentInfo memory poolInfo = instanceReader.getComponentInfo(poolNftId); - poolInfo.tokenHandler.pushToken( - beneficiary, - netPayoutAmount); - - // TODO add 2nd token tx if processingFeeAmount > 0 - } - - // callback to policy holder if applicable - _policyHolderPayoutExecuted(policyNftId, payoutId, beneficiary, payoutAmount); - } + policyInfo.bundleNftId, + payoutId, + payoutAmount, + payoutInfo.beneficiary); } function cancelPayout( @@ -480,11 +458,10 @@ contract ClaimService is { // checks ( - , - , + ,, InstanceReader instanceReader, InstanceStore instanceStore, - IPolicy.PolicyInfo memory policyInfo + // IPolicy.PolicyInfo memory policyInfo ) = _verifyCallerWithPolicy(policyNftId); IPolicy.ClaimInfo memory claimInfo = instanceReader.getClaimInfo(policyNftId, claimId); @@ -531,45 +508,6 @@ contract ClaimService is } - function _calculatePayoutAmount( - InstanceReader instanceReader, - NftId policyNftId, - IPolicy.PolicyInfo memory policyInfo, - IPolicy.PayoutInfo memory payoutInfo - ) - internal - view - returns ( - Amount netPayoutAmount, - Amount processingFeeAmount, - address beneficiary - ) - { - Amount payoutAmount = payoutInfo.amount; - - if(payoutAmount.gtz()) { - NftId productNftId = policyInfo.productNftId; - - // get pool component info from policy or product - NftId poolNftId = getRegistry().getObjectInfo(policyInfo.bundleNftId).parentNftId; - IComponents.ComponentInfo memory poolInfo = instanceReader.getComponentInfo(poolNftId); - - netPayoutAmount = payoutAmount; - - if (payoutInfo.beneficiary == address(0)) { - beneficiary = getRegistry().ownerOf(policyNftId); - } else { - beneficiary = payoutInfo.beneficiary; - } - - IComponents.FeeInfo memory feeInfo = instanceReader.getFeeInfo(productNftId); - if(FeeLib.gtz(feeInfo.processingFee)) { - // TODO calculate and set net payout and processing fees - } - } - } - - function _verifyCallerWithPolicy( NftId policyNftId ) @@ -597,6 +535,7 @@ contract ClaimService is } } + function _getAndVerifyActiveComponent(ObjectType expectedType) internal view @@ -672,22 +611,7 @@ contract ClaimService is } } - - function _policyHolderPayoutExecuted( - NftId policyNftId, - PayoutId payoutId, - address beneficiary, - Amount payoutAmount - ) - internal - { - IPolicyHolder policyHolder = _getPolicyHolder(policyNftId); - if(address(policyHolder) != address(0)) { - policyHolder.payoutExecuted(policyNftId, payoutId, payoutAmount, beneficiary); - } - } - - + // TODO: move to policy helper lib or something function _getPolicyHolder(NftId policyNftId) internal view diff --git a/contracts/product/IClaimService.sol b/contracts/product/IClaimService.sol index 972d8e134..6bdf89462 100644 --- a/contracts/product/IClaimService.sol +++ b/contracts/product/IClaimService.sol @@ -19,7 +19,6 @@ import {Fee} from "../type/Fee.sol"; interface IClaimService is IService { - event LogClaimServiceClaimSubmitted(NftId policyNftId, ClaimId claimId, Amount claimAmount); event LogClaimServiceClaimConfirmed(NftId policyNftId, ClaimId claimId, Amount confirmedAmount); event LogClaimServiceClaimDeclined(NftId policyNftId, ClaimId claimId); @@ -27,7 +26,7 @@ interface IClaimService is event LogClaimServiceClaimClosed(NftId policyNftId, ClaimId claimId); event LogClaimServicePayoutCreated(NftId policyNftId, PayoutId payoutId, Amount amount, address beneficiary); - event LogClaimServicePayoutProcessed(NftId policyNftId, PayoutId payoutId, Amount amount, address beneficiary, Amount netAmount, Amount processingFeeAmount); + event LogClaimServicePayoutProcessed(NftId policyNftId, PayoutId payoutId, Amount amount); event LogClaimServicePayoutCancelled(NftId policyNftId, PayoutId payoutId); error ErrorClaimServiceBeneficiarySet(NftId policyNftId, PayoutId payoutId, address beneficiary); diff --git a/contracts/product/IPolicyService.sol b/contracts/product/IPolicyService.sol index 24e130a66..a6cbdcd3e 100644 --- a/contracts/product/IPolicyService.sol +++ b/contracts/product/IPolicyService.sol @@ -24,8 +24,9 @@ interface IPolicyService is IService { error ErrorPolicyServicePolicyStateNotCollateralized(NftId applicationNftId); error ErrorPolicyServicePolicyAlreadyActivated(NftId policyNftId); - error ErrorPolicyServiceBalanceInsufficient(address policyOwner, uint256 premiumAmount, uint256 balance); - error ErrorPolicyServiceAllowanceInsufficient(address policyOwner, address tokenHandler, uint256 premiumAmount, uint256 allowance); + // TODO cleanup + // error ErrorPolicyServiceBalanceInsufficient(address policyOwner, uint256 premiumAmount, uint256 balance); + // error ErrorPolicyServiceAllowanceInsufficient(address policyOwner, address tokenHandler, uint256 premiumAmount, uint256 allowance); error ErrorPolicyServiceInsufficientAllowance(address customer, address tokenHandlerAddress, uint256 amount); error ErrorPolicyServicePremiumAlreadyPaid(NftId policyNftId); diff --git a/contracts/product/PolicyService.sol b/contracts/product/PolicyService.sol index 2de520aac..350f27d35 100644 --- a/contracts/product/PolicyService.sol +++ b/contracts/product/PolicyService.sol @@ -119,14 +119,14 @@ contract PolicyService is // actual collateralizaion _poolService.lockCollateral( instance, - address(instanceReader.getComponentInfo(productNftId).token), + address(instanceReader.getToken(productNftId)), productNftId, applicationNftId, applicationInfo.bundleNftId, applicationInfo.sumInsuredAmount); // optional activation of policy - if(activateAt > TimestampLib.zero()) { + if(activateAt.gtz()) { applicationInfo = _activate(applicationNftId, applicationInfo, activateAt); } @@ -165,7 +165,7 @@ contract PolicyService is } // link policy to risk and bundle - NftId poolNftId = getRegistry().getObjectInfo(bundleNftId).parentNftId; + NftId poolNftId = getRegistry().getParentNftId(bundleNftId); instance.getRiskSet().linkPolicy(productNftId, riskId, applicationNftId); instance.getBundleSet().linkPolicy(poolNftId, bundleNftId, applicationNftId); @@ -207,12 +207,18 @@ contract PolicyService is // check funds and allowance of policy holder IPolicy.PremiumInfo memory premium = instanceReader.getPremiumInfo(policyNftId); - TokenHandler tokenHandler = _getTokenHandler(instanceReader, policyInfo.productNftId); - _checkPremiumBalanceAndAllowance( - tokenHandler.TOKEN(), - address(tokenHandler), - getRegistry().ownerOf(policyNftId), - premium.premiumAmount); + instanceReader.getTokenHandler( + productNftId).checkBalanceAndAllowance( + getRegistry().ownerOf(policyNftId), + premium.premiumAmount, + false); + + // ) + // _checkPremiumBalanceAndAllowance( + // tokenHandler.TOKEN(), + // address(tokenHandler), + // getRegistry().ownerOf(policyNftId), + // premium.premiumAmount); // effects _processSale( @@ -235,7 +241,7 @@ contract PolicyService is emit LogPolicyServicePolicyPremiumCollected(policyNftId, premium.premiumAmount); // interactions - _transferFunds(instanceReader, policyNftId, policyInfo.productNftId, premium); + _transferPremiumAmounts(instanceReader, policyNftId, policyInfo.productNftId, premium); } @@ -349,7 +355,6 @@ contract PolicyService is // release (remaining) collateral that was blocked by policy _poolService.releaseCollateral( instance, - address(instanceReader.getComponentInfo(productNftId).token), policyNftId, policyInfo); @@ -360,7 +365,7 @@ contract PolicyService is instance.getInstanceStore().updatePolicy(policyNftId, policyInfo, CLOSED()); // unlink policy from risk and bundle - NftId poolNftId = getRegistry().getObjectInfo(bundleNftId).parentNftId; + NftId poolNftId = getRegistry().getParentNftId(bundleNftId); instance.getRiskSet().unlinkPolicy(productNftId, riskId, policyNftId); instance.getBundleSet().unlinkPolicy(poolNftId, bundleNftId, policyNftId); @@ -441,28 +446,6 @@ contract PolicyService is _policyHolderPolicyExpired(policyNftId, expiredAt); } - // TODO cleanup - // /// @dev Calculates the premium and updates all counters in the other services. - // /// Only book keeping, no token transfers. - // function _processPremium( - // IInstance instance, - // NftId applicationNftId, - // IPolicy.PolicyInfo memory applicationInfo, - // IPolicy.PremiumInfo memory premium - // ) - // internal - // virtual - // { - // // update the counters - // _processSale( - // instanceReader, - // instance.getInstanceStore(), - // productNftId, - // applicationInfo.bundleNftId, - // applicationInfo.referralId, - // premium); - // } - function _activate( NftId policyNftId, @@ -528,7 +511,7 @@ contract PolicyService is /// @dev transfer the premium to the wallets the premium is distributed to - function _transferFunds( + function _transferPremiumAmounts( InstanceReader instanceReader, NftId policyNftId, NftId productNftId, @@ -537,7 +520,6 @@ contract PolicyService is internal virtual { - TokenHandler tokenHandler = _getTokenHandler(instanceReader, productNftId); address policyHolder = getRegistry().ownerOf(policyNftId); ( @@ -549,14 +531,19 @@ contract PolicyService is instanceReader, productNftId); - tokenHandler.collectTokensToThreeRecipients( - policyHolder, - productWallet, - premium.productFeeAmount, - distributionWallet, - premium.distributionFeeAndCommissionAmount, - poolWallet, - premium.poolPremiumAndFeeAmount); + // step 1: collect premium amount from policy holder + TokenHandler tokenHandler = instanceReader.getTokenHandler(productNftId); + tokenHandler.pullToken(policyHolder, premium.premiumAmount); + + // step 2: push distribution fee to distribution wallet + if (premium.distributionFeeAndCommissionAmount.gtz()) { + tokenHandler.pushToken(distributionWallet, premium.distributionFeeAndCommissionAmount); + } + + // step 3: push pool fee, bundle fee and pool premium to pool wallet + if (premium.poolPremiumAndFeeAmount.gtz()) { + tokenHandler.pushToken(poolWallet, premium.poolPremiumAndFeeAmount); + } } @@ -576,29 +563,30 @@ contract PolicyService is } + // TODO cleanup /// @dev checks the balance and allowance of the policy holder - function _checkPremiumBalanceAndAllowance( - IERC20Metadata token, - address tokenHandlerAddress, - address policyHolder, - Amount premiumAmount - ) - internal - virtual - view - { - uint256 premium = premiumAmount.toInt(); - uint256 balance = token.balanceOf(policyHolder); - uint256 allowance = token.allowance(policyHolder, tokenHandlerAddress); + // function _checkPremiumBalanceAndAllowance( + // IERC20Metadata token, + // address tokenHandlerAddress, + // address policyHolder, + // Amount premiumAmount + // ) + // internal + // virtual + // view + // { + // uint256 premium = premiumAmount.toInt(); + // uint256 balance = token.balanceOf(policyHolder); + // uint256 allowance = token.allowance(policyHolder, tokenHandlerAddress); - if (balance < premium) { - revert ErrorPolicyServiceBalanceInsufficient(policyHolder, premium, balance); - } + // if (balance < premium) { + // revert ErrorPolicyServiceBalanceInsufficient(policyHolder, premium, balance); + // } - if (allowance < premium) { - revert ErrorPolicyServiceAllowanceInsufficient(policyHolder, tokenHandlerAddress, premium, allowance); - } - } + // if (allowance < premium) { + // revert ErrorPolicyServiceAllowanceInsufficient(policyHolder, tokenHandlerAddress, premium, allowance); + // } + // } function _policyHolderPolicyActivated( @@ -659,19 +647,20 @@ contract PolicyService is } - function _getTokenHandler( - InstanceReader instanceReader, - NftId productNftId - ) - internal - virtual - view - returns ( - TokenHandler tokenHandler - ) - { - tokenHandler = instanceReader.getComponentInfo(productNftId).tokenHandler; - } + // TODO cleanup + // function _getTokenHandler( + // InstanceReader instanceReader, + // NftId productNftId + // ) + // internal + // virtual + // view + // returns ( + // TokenHandler tokenHandler + // ) + // { + // tokenHandler = instanceReader.getTokenHandler(productNftId).tokenHandler; + // } function _getDistributionNftAndWallets( diff --git a/contracts/product/RiskService.sol b/contracts/product/RiskService.sol index 902ff654b..56ac51772 100644 --- a/contracts/product/RiskService.sol +++ b/contracts/product/RiskService.sol @@ -39,12 +39,6 @@ contract RiskService is ) = abi.decode(data, (address, address)); __Service_init(authority, registry, owner); - - // TODO cleanup - // _instanceService = IInstanceService(_getServiceAddress(INSTANCE())); - // _poolService = IPoolService(getRegistry().getServiceAddress(POOL(), getVersion().toMajorPart())); - // _registryService = IRegistryService(_getServiceAddress(REGISTRY())); - _registerInterface(type(IRiskService).interfaceId); } diff --git a/contracts/registry/IRegistry.sol b/contracts/registry/IRegistry.sol index d5e3ce6b1..496f9e17d 100644 --- a/contracts/registry/IRegistry.sol +++ b/contracts/registry/IRegistry.sol @@ -118,6 +118,8 @@ interface IRegistry is function getObjectInfo(NftId nftId) external view returns (ObjectInfo memory info); + function getParentNftId(NftId nftId) external view returns (NftId parentNftId); + function isObjectType(NftId nftId, ObjectType expectedObjectType) external view returns (bool); function isObjectType(address contractAddress, ObjectType expectedObjectType) external view returns (bool); diff --git a/contracts/registry/Registry.sol b/contracts/registry/Registry.sol index 602ce04f2..36bd93d2c 100644 --- a/contracts/registry/Registry.sol +++ b/contracts/registry/Registry.sol @@ -356,6 +356,10 @@ contract Registry is return _info[nftId]; } + function getParentNftId(NftId nftId) external view returns (NftId parentNftId) { + return _info[nftId].parentNftId; + } + function isObjectType(address contractAddress, ObjectType expectedObjectType) external view returns (bool) { NftId nftId = _nftIdByAddress[contractAddress]; return isObjectType(nftId, expectedObjectType); diff --git a/contracts/registry/RegistryAdmin.sol b/contracts/registry/RegistryAdmin.sol index 55d423af4..39346c604 100644 --- a/contracts/registry/RegistryAdmin.sol +++ b/contracts/registry/RegistryAdmin.sol @@ -4,11 +4,12 @@ pragma solidity ^0.8.20; import {AccessAdmin} from "../authorization/AccessAdmin.sol"; import {AccessManagerCloneable} from "../authorization/AccessManagerCloneable.sol"; import {IAccess} from "../authorization/IAccess.sol"; +import {IComponent} from "../shared/IComponent.sol"; import {IRegistry} from "./IRegistry.sol"; import {IService} from "../shared/IService.sol"; import {IServiceAuthorization} from "../authorization/IServiceAuthorization.sol"; import {IStaking} from "../staking/IStaking.sol"; -import {ObjectType, ObjectTypeLib, ALL, REGISTRY, STAKING, POOL, RELEASE} from "../type/ObjectType.sol"; +import {ObjectType, ObjectTypeLib, ALL, COMPONENT, REGISTRY, STAKING, POOL, RELEASE} from "../type/ObjectType.sol"; import {ReleaseRegistry} from "./ReleaseRegistry.sol"; import {RoleId, RoleIdLib, ADMIN_ROLE, GIF_MANAGER_ROLE, GIF_ADMIN_ROLE, PUBLIC_ROLE} from "../type/RoleId.sol"; import {Staking} from "../staking/Staking.sol"; @@ -45,6 +46,7 @@ contract RegistryAdmin is /// @dev gif roles for external contracts string public constant REGISTRY_SERVICE_ROLE_NAME = "RegistryServiceRole"; + string public constant COMPONENT_SERVICE_ROLE_NAME = "ComponentServiceRole"; string public constant POOL_SERVICE_ROLE_NAME = "PoolServiceRole"; string public constant STAKING_SERVICE_ROLE_NAME = "StakingServiceRole"; @@ -291,8 +293,8 @@ contract RegistryAdmin is // grant permissions to the staking role for token handler contract IStaking staking = IStaking(_staking); functions = new FunctionInfo[](2); - functions[0] = toFunction(TokenHandler.collectTokens.selector, "collectTokens"); - functions[1] = toFunction(TokenHandler.distributeTokens.selector, "distributeTokens"); + functions[0] = toFunction(TokenHandler.pullToken.selector, "pullToken"); + functions[1] = toFunction(TokenHandler.pushToken.selector, "pushToken"); _authorizeTargetFunctions(address(staking.getTokenHandler()), stakingRoleId, functions); // create staking service role @@ -320,6 +322,14 @@ contract RegistryAdmin is functions[10] = toFunction(IStaking.claimRewards.selector, "claimRewards"); _authorizeTargetFunctions(_staking, stakingServiceRoleId, functions); + // grant permissions to the staking service role for staking token handler + functions = new FunctionInfo[](3); + functions[0] = toFunction(TokenHandler.approve.selector, "approve"); + functions[1] = toFunction(TokenHandler.pullToken.selector, "pullToken"); + functions[2] = toFunction(TokenHandler.pushToken.selector, "pushToken"); + _authorizeTargetFunctions( + address(IComponent(_staking).getTokenHandler()), stakingServiceRoleId, functions); + // create pool service role RoleId poolServiceRoleId = RoleIdLib.roleForTypeAndAllVersions(POOL()); _createRole( diff --git a/contracts/registry/ServiceAuthorizationV3.sol b/contracts/registry/ServiceAuthorizationV3.sol index 507d4f483..f4a8707a7 100644 --- a/contracts/registry/ServiceAuthorizationV3.sol +++ b/contracts/registry/ServiceAuthorizationV3.sol @@ -143,6 +143,9 @@ contract ServiceAuthorizationV3 _authorize(functions, IAccountingService.increaseBundleBalance.selector, "increaseBundleBalance"); _authorize(functions, IAccountingService.decreaseBundleBalance.selector, "decreaseBundleBalance"); + functions = _authorizeForService(ACCOUNTING(), POOL()); + _authorize(functions, IAccountingService.decreaseBundleBalanceForPool.selector, "decreaseBundleBalanceForPool"); + functions = _authorizeForService(ACCOUNTING(), COMPONENT()); _authorize(functions, IAccountingService.decreaseComponentFees.selector, "decreaseComponentFees"); @@ -214,6 +217,9 @@ contract ServiceAuthorizationV3 functions = _authorizeForService(POOL(), CLAIM()); _authorize(functions, IPoolService.processPayout.selector, "processPayout"); + + functions = _authorizeForService(POOL(), ALL()); + _authorize(functions, IPoolService.withdrawBundleFees.selector, "withdrawBundleFees"); } @@ -236,7 +242,6 @@ contract ServiceAuthorizationV3 _authorize(functions, IBundleService.lock.selector, "lock"); _authorize(functions, IBundleService.unlock.selector, "unlock"); _authorize(functions, IBundleService.setFee.selector, "setFee"); - _authorize(functions, IBundleService.withdrawBundleFees.selector, "withdrawBundleFees"); } } diff --git a/contracts/shared/Component.sol b/contracts/shared/Component.sol index c61e23a6a..3d1686fe5 100644 --- a/contracts/shared/Component.sol +++ b/contracts/shared/Component.sol @@ -4,13 +4,14 @@ pragma solidity ^0.8.20; import {IAccessManaged} from "@openzeppelin/contracts/access/manager/IAccessManaged.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import {Amount} from "../type/Amount.sol"; -import {ContractLib} from "./ContractLib.sol"; import {IComponent} from "./IComponent.sol"; import {IComponents} from "../instance/module/IComponents.sol"; import {IComponentService} from "./IComponentService.sol"; import {IRegistry} from "../registry/IRegistry.sol"; import {IRelease} from "../registry/IRelease.sol"; + +import {Amount, AmountLib} from "../type/Amount.sol"; +import {ContractLib} from "./ContractLib.sol"; import {NftId} from "../type/NftId.sol"; import {ObjectType, COMPONENT, STAKING} from "../type/ObjectType.sol"; import {Registerable} from "../shared/Registerable.sol"; @@ -110,7 +111,7 @@ abstract contract Component is /// override internal function _nftTransferFrom to implement custom behaviour function nftTransferFrom(address from, address to, uint256 tokenId, address operator) external - onlyChainNft + onlyChainNft() { _nftTransferFrom(from, to, tokenId, operator); } diff --git a/contracts/shared/ComponentService.sol b/contracts/shared/ComponentService.sol index cef10a76c..2d5251fd3 100644 --- a/contracts/shared/ComponentService.sol +++ b/contracts/shared/ComponentService.sol @@ -130,24 +130,6 @@ contract ComponentService is } - function approveStakingTokenHandler( - IERC20Metadata token, - Amount amount - ) - external - virtual - { - // checks - ContractLib.getAndVerifyStaking( - getRegistry(), - msg.sender); // only active - - // effects - TokenHandler tokenHandler = IComponent(msg.sender).getTokenHandler(); - tokenHandler.approve(token, amount); - } - - function setWallet(address newWallet) external virtual @@ -183,35 +165,52 @@ contract ComponentService is _setLocked(instance.getInstanceAdmin(), componentAddress, locked); } + /// @inheritdoc IComponentService function withdrawFees(Amount amount) external virtual returns (Amount withdrawnAmount) { + // checks (NftId componentNftId, IInstance instance) = _getAndVerifyActiveComponent(COMPONENT()); - IComponents.ComponentInfo memory info = instance.getInstanceReader().getComponentInfo(componentNftId); - address componentWallet = info.tokenHandler.getWallet(); + InstanceReader instanceReader = instance.getInstanceReader(); // determine withdrawn amount + Amount maxAvailableAmount = instanceReader.getFeeAmount(componentNftId); withdrawnAmount = amount; - if (withdrawnAmount.gte(AmountLib.max())) { - withdrawnAmount = instance.getInstanceReader().getFeeAmount(componentNftId); - } else if (withdrawnAmount.eqz()) { + + // max amount -> withraw all available fees + if (amount == AmountLib.max()) { + withdrawnAmount = maxAvailableAmount; + } + + // check modified withdrawn amount + if (withdrawnAmount.eqz()) { revert ErrorComponentServiceWithdrawAmountIsZero(); - } else { - Amount withdrawLimit = instance.getInstanceReader().getFeeAmount(componentNftId); - if (withdrawnAmount.gt(withdrawLimit)) { - revert ErrorComponentServiceWithdrawAmountExceedsLimit(withdrawnAmount, withdrawLimit); - } + } else if (withdrawnAmount > maxAvailableAmount) { + revert ErrorComponentServiceWithdrawAmountExceedsLimit(withdrawnAmount, maxAvailableAmount); } + // effects // decrease fee counters by withdrawnAmount - _accountingService.decreaseComponentFees(instance.getInstanceStore(), componentNftId, withdrawnAmount); + _accountingService.decreaseComponentFees( + instance.getInstanceStore(), + componentNftId, + withdrawnAmount); - // transfer amount to component owner address componentOwner = getRegistry().ownerOf(componentNftId); - emit LogComponentServiceComponentFeesWithdrawn(componentNftId, componentOwner, address(info.token), withdrawnAmount); - info.tokenHandler.distributeTokens(componentWallet, componentOwner, withdrawnAmount); + TokenHandler tokenHandler = instanceReader.getTokenHandler(componentNftId); + emit LogComponentServiceComponentFeesWithdrawn( + componentNftId, + componentOwner, + address(tokenHandler.TOKEN()), + withdrawnAmount); + + // interactions + // transfer amount to component owner + tokenHandler.pushFeeToken( + componentOwner, + withdrawnAmount); } @@ -245,12 +244,10 @@ contract ComponentService is instanceStore.createProduct( productNftId, initialProductInfo); + instanceStore.createFee( productNftId, product.getInitialFeeInfo()); - - // authorize - instanceAdmin.initializeComponentAuthorization(product); } @@ -316,10 +313,6 @@ contract ComponentService is // set distribution in product info productInfo.distributionNftId = distributionNftId; instanceStore.updateProduct(productNftId, productInfo, KEEP_STATE()); - - // authorize - instanceAdmin.initializeComponentAuthorization( - IInstanceLinkedComponent(distributioAddress)); } @@ -385,10 +378,6 @@ contract ComponentService is productInfo.oracleNftId[productInfo.numberOfOracles] = oracleNftId; productInfo.numberOfOracles++; instanceStore.updateProduct(productNftId, productInfo, KEEP_STATE()); - - // authorize - instanceAdmin.initializeComponentAuthorization( - IInstanceLinkedComponent(oracleAddress)); } //-------- pool ---------------------------------------------------------// @@ -423,9 +412,6 @@ contract ComponentService is // update pool in product info productInfo.poolNftId = poolNftId; instanceStore.updateProduct(productNftId, productInfo, KEEP_STATE()); - - // authorize - instanceAdmin.initializeComponentAuthorization(pool); } @@ -532,6 +518,9 @@ contract ComponentService is // link component contract to nft id component.linkToRegisteredNftId(); + // authorize + instanceAdmin.initializeComponentAuthorization(component); + emit LogComponentServiceRegistered(instanceNftId, componentNftId, requiredType, address(component), address(token), initialOwner); } @@ -562,7 +551,7 @@ contract ComponentService is IComponents.FeeInfo memory info ) { - productNftId = getRegistry().getObjectInfo(componentNftId).parentNftId; + productNftId = getRegistry().getParentNftId(componentNftId); info = instanceReader.getFeeInfo(productNftId); } @@ -611,9 +600,12 @@ contract ComponentService is revert ErrorComponentServiceAlreadyRegistered(componentAddress); } - // check release matches + // component release matches servie release address parentAddress = registry.getObjectAddress(parentNftId); - if (component.getRelease() != IRegisterable(parentAddress).getRelease()) { + if (component.getRelease() != getRelease()) { + revert ErrorComponentServiceReleaseMismatch(componentAddress, component.getRelease(), getRelease()); + // component release matches parent release + } else if (component.getRelease() != IRegisterable(parentAddress).getRelease()){ revert ErrorComponentServiceReleaseMismatch(componentAddress, component.getRelease(), IRegisterable(parentAddress).getRelease()); } diff --git a/contracts/shared/ComponentVerifyingService.sol b/contracts/shared/ComponentVerifyingService.sol index 35a66825b..c88ca2dac 100644 --- a/contracts/shared/ComponentVerifyingService.sol +++ b/contracts/shared/ComponentVerifyingService.sol @@ -116,7 +116,7 @@ abstract contract ComponentVerifyingService is /// @dev returns the product nft id from the registry. /// assumes the component nft id is valid and represents a product linked component. function _getProductNftId(NftId componentNftId) internal view returns (NftId productNftId) { - productNftId = getRegistry().getObjectInfo(componentNftId).parentNftId; + productNftId = getRegistry().getParentNftId(componentNftId); } diff --git a/contracts/shared/ContractLib.sol b/contracts/shared/ContractLib.sol index a18d78244..169036350 100644 --- a/contracts/shared/ContractLib.sol +++ b/contracts/shared/ContractLib.sol @@ -135,11 +135,12 @@ library ContractLib { view returns (address instance) { - NftId productNftId = registry.getObjectInfo(componentNftId).parentNftId; - NftId instanceNftId = registry.getObjectInfo(productNftId).parentNftId; + NftId productNftId = registry.getParentNftId(componentNftId); + NftId instanceNftId = registry.getParentNftId(productNftId); return registry.getObjectInfo(instanceNftId).objectAddress; } + function isActiveToken( address tokenRegistryAddress, address token, diff --git a/contracts/shared/IComponentService.sol b/contracts/shared/IComponentService.sol index 77db16a53..85f215c14 100644 --- a/contracts/shared/IComponentService.sol +++ b/contracts/shared/IComponentService.sol @@ -67,10 +67,6 @@ interface IComponentService is /// Reverts if the component's token handler wallet is not the token handler itself. function approveTokenHandler(IERC20Metadata token, Amount amount) external; - /// @dev Approves the staking token handler. - /// Reverts if the staking token handler wallet is not the token handler itself. - function approveStakingTokenHandler(IERC20Metadata token, Amount amount) external; - /// @dev Sets the components associated wallet address function setWallet(address newWallet) external; diff --git a/contracts/shared/InstanceLinkedComponent.sol b/contracts/shared/InstanceLinkedComponent.sol index 7f8d2b973..44b124b41 100644 --- a/contracts/shared/InstanceLinkedComponent.sol +++ b/contracts/shared/InstanceLinkedComponent.sol @@ -20,7 +20,7 @@ import {NftId} from "../type/NftId.sol"; import {ObjectType, COMPONENT, INSTANCE, PRODUCT} from "../type/ObjectType.sol"; import {VersionPart} from "../type/Version.sol"; import {RoleId, RoleIdLib} from "../type/RoleId.sol"; -import {IAccess} from "../instance/module/IAccess.sol"; +import {IAccess} from "../authorization/IAccess.sol"; import {TokenHandler} from "../shared/TokenHandler.sol"; import {VersionPart} from "../type/Version.sol"; @@ -134,7 +134,7 @@ abstract contract InstanceLinkedComponent is // if not product parent is product, and parent of product is instance IRegistry registry = _checkAndGetRegistry(registryAddress, parentNftId, PRODUCT()); - return registry.getObjectInfo(parentNftId).parentNftId; + return registry.getParentNftId(parentNftId); } /// @dev checks the and gets registry. diff --git a/contracts/shared/TokenHandler.sol b/contracts/shared/TokenHandler.sol index 750f2a091..edc0df6a7 100644 --- a/contracts/shared/TokenHandler.sol +++ b/contracts/shared/TokenHandler.sol @@ -12,6 +12,10 @@ import {NftId} from "../type/NftId.sol"; import {SERVICE} from "../type/ObjectType.sol"; +/// @dev Token specific transfer helper base contract. +/// A default token contract is provided via contract constructor. +/// Relies internally on OpenZeppelin SafeERC20.safeTransferFrom. +/// This base contract simplifies writing tests. contract TokenHandlerBase { // _setWallet @@ -36,10 +40,6 @@ contract TokenHandlerBase { // _approveTokenHandler error ErrorTokenHandlerNotWallet(NftId nftId, address tokenHandler, address wallet); - // _pullAndPullToken - error ErrorTokenHandlerWalletsNotDistinct(address from, address to1, address to2); - error ErrorTokenHandlerPushAmountsTooLarge(Amount pushAmount, Amount pullAmount); - // _checkPreconditions error ErrorTokenHandlerBalanceTooLow(address token, address from, uint256 balance, uint256 expectedBalance); error ErrorTokenHandlerAllowanceTooSmall(address token, address from, address spender, uint256 allowance, uint256 expectedAllowance); @@ -75,6 +75,23 @@ contract TokenHandlerBase { } TOKEN = IERC20Metadata(token); + + // self approval of token handler to max amount + _approve(TOKEN, AmountLib.max()); + } + + + /// @dev Checks the balance and allowance for the from address and amount. + /// When requiring amount > 0 set checkAmount to true. + function checkBalanceAndAllowance( + address from, + Amount amount, + bool checkAmount + ) + external + view + { + _checkBalanceAndAllowance(from, amount, checkAmount); } @@ -159,33 +176,6 @@ contract TokenHandlerBase { } - function _pullAndPushToken( - address from, - Amount pullAmount, - address to1, - Amount amount1, - address to2, - Amount amount2 - ) - internal - { - address wallet = getWallet(); - - if (wallet == to1 || wallet == to2 || to1 == to2) { - revert ErrorTokenHandlerWalletsNotDistinct(wallet, to1, to2); - } - - if (amount1 + amount2 > pullAmount) { - revert ErrorTokenHandlerPushAmountsTooLarge(amount1 + amount2, pullAmount); - } - - _pullToken(from, pullAmount); - - if (amount1.gtz()) { _pushToken(to1, amount1); } - if (amount2.gtz()) { _pushToken(to2, amount2); } - } - - function _pullToken(address from, Amount amount) internal { @@ -209,8 +199,8 @@ contract TokenHandlerBase { internal { if (checkPreconditions) { - // check amount > 0, balance >= amount and allowance >= amount - _checkPreconditions(from, amount); + bool checkAmount = true; + _checkBalanceAndAllowance(from, amount, checkAmount); } // transfer the tokens @@ -224,15 +214,16 @@ contract TokenHandlerBase { } - function _checkPreconditions( + function _checkBalanceAndAllowance( address from, - Amount amount + Amount amount, + bool checkAmount ) internal view { // amount must be greater than zero - if (amount.eqz()) { + if (checkAmount && amount.eqz()) { revert ErrorTokenHandlerAmountIsZero(); } @@ -251,9 +242,9 @@ contract TokenHandlerBase { } -/// @dev Token specific transfer helper -/// a default token contract is provided via contract constructor -/// relies internally on oz SafeERC20.safeTransferFrom +/// @dev Token specific transfer helper. +/// Contract is derived from TokenHandlerBase and adds +/// authorization based on OpenZeppelin AccessManaged. contract TokenHandler is AccessManaged, TokenHandlerBase @@ -262,9 +253,6 @@ contract TokenHandler is // onlyService error ErrorTokenHandlerNotService(address service); - // TODO delete - error ErrorTokenHandlerRecipientWalletsMustBeDistinct(address to, address to2, address to3); - modifier onlyService() { if (!REGISTRY.isObjectType(msg.sender, SERVICE())) { revert ErrorTokenHandlerNotService(msg.sender); @@ -289,7 +277,7 @@ contract TokenHandler is /// covers the current component balance must exist function setWallet(address newWallet) external - // restricted() // TODO re-activate + restricted() onlyService() { _setWallet(newWallet); @@ -305,7 +293,7 @@ contract TokenHandler is Amount amount ) external - // restricted() // TODO re-activate + restricted() onlyService() { _approve(token, amount); @@ -313,69 +301,34 @@ contract TokenHandler is /// @dev Collect tokens from outside of GIF and transfer them to the wallet. /// This method also checks balance and allowance and makes sure the amount is greater than zero. - function collectTokens( + function pullToken( address from, Amount amount ) external - // restricted() // TODO re-activate + restricted() onlyService() { _pullToken(from, amount); } - /// @dev Collect tokens from outside of GIF and transfer them to the wallet. - /// This method also checks balance and allowance and makes sure the amount is greater than zero. + /// @dev Distribute tokens from a wallet within the scope of gif to some address. function pushToken( address to, Amount amount - ) - external - // restricted() // TODO re-activate - onlyService() - { - _pushToken(to, amount); - } - - - /// @dev collect tokens from outside of the gif and transfer them to three distinct wallets within the scope of gif - /// This method also checks balance and allowance and makes sure the amount is greater than zero. - function collectTokensToThreeRecipients( - address from, - address to, - Amount amount, - address to2, - Amount amount2, - address to3, - Amount amount3 ) external restricted() onlyService() { - if (to == to2 || to == to3 || to2 == to3) { - revert ErrorTokenHandlerRecipientWalletsMustBeDistinct(to, to2, to3); - } - - _checkPreconditions(from, amount + amount2 + amount3); - - if (amount.gtz()) { - _transfer(from, to, amount, false); - } - if (amount2.gtz()) { - _transfer(from, to2, amount2, false); - } - if (amount3.gtz()) { - _transfer(from, to3, amount3, false); - } + _pushToken(to, amount); } - /// @dev distribute tokens from a wallet within the scope of gif to an external address. - /// This method also checks balance and allowance and makes sure the amount is greater than zero. - function distributeTokens( - address from, + /// @dev Distribute fee tokens from a wallet within the scope of gif to some address. + /// Separate push function for component service. + function pushFeeToken( address to, Amount amount ) @@ -383,7 +336,6 @@ contract TokenHandler is restricted() onlyService() { - // _transfer(from, to, amount, true); _pushToken(to, amount); } } diff --git a/contracts/staking/IStaking.sol b/contracts/staking/IStaking.sol index bc4fd0b9f..7696f22bd 100644 --- a/contracts/staking/IStaking.sol +++ b/contracts/staking/IStaking.sol @@ -158,14 +158,6 @@ interface IStaking is Amount rewardsClaimedAmount ); - //--- helper functions --------------------------------------------------// - - // /// @dev transfers the specified amount of dips from the from address to the staking wallet. - // function collectDipAmount(address from, Amount dipAmount) external; - - // /// @dev transfers the specified amount of dips from the staking wallet to the to addess. - // function transferDipAmount(address to, Amount dipAmount) external; - //--- view and pure functions -------------------------------------------// function getStakingStore() external view returns (StakingStore stakingStore); diff --git a/contracts/staking/IStakingService.sol b/contracts/staking/IStakingService.sol index 59174bfeb..a79064d0a 100644 --- a/contracts/staking/IStakingService.sol +++ b/contracts/staking/IStakingService.sol @@ -31,6 +31,7 @@ interface IStakingService is IService event LogStakingServiceRewardsClaimed(NftId stakeNftId, address stakeOwner, Amount rewardsClaimedAmount); // modifiers + error ErrorStakingServiceNotStakingOwner(address account); error ErrorStakingServiceNotStaking(address stakingAddress); error ErrorStakingServiceNotSupportingIStaking(address stakingAddress); @@ -48,6 +49,13 @@ interface IStakingService is IService // function setProtocolLockingPeriod(Seconds lockingPeriod) external; // TODO also make sure that protocol rewards can be refilled and withdrawn + /// @dev Approves the staking token handler. + /// Reverts if the staking token handler wallet is not the token handler itself. + function approveTokenHandler( + IERC20Metadata token, + Amount amount + ) external; + /// @dev creates/registers an on-chain instance staking target. /// function granted to instance service function createInstanceTarget( diff --git a/contracts/staking/StakeManagerLib.sol b/contracts/staking/StakeManagerLib.sol index 5a8dde132..eb1454f4c 100644 --- a/contracts/staking/StakeManagerLib.sol +++ b/contracts/staking/StakeManagerLib.sol @@ -51,7 +51,7 @@ library StakeManagerLib { // TODO check that additional dip, rewards and rewards increment // are still ok with max target staking amount - NftId targetNftId = registry.getObjectInfo(stakeNftId).parentNftId; + NftId targetNftId = registry.getParentNftId(stakeNftId); stakingStore.restakeRewards( stakeNftId, diff --git a/contracts/staking/Staking.sol b/contracts/staking/Staking.sol index df13953f5..fe533e35c 100644 --- a/contracts/staking/Staking.sol +++ b/contracts/staking/Staking.sol @@ -468,7 +468,7 @@ contract Staking is internal virtual override { - IComponentService(_getServiceAddress(COMPONENT())).approveStakingTokenHandler( + IComponentService(_getServiceAddress(STAKING())).approveTokenHandler( token, amount); } diff --git a/contracts/staking/StakingReader.sol b/contracts/staking/StakingReader.sol index 49d7d38c7..542c394f5 100644 --- a/contracts/staking/StakingReader.sol +++ b/contracts/staking/StakingReader.sol @@ -93,7 +93,7 @@ contract StakingReader is function getTargetNftId(NftId stakeNftId) public view returns (NftId targetNftId) { - return _registry.getObjectInfo(stakeNftId).parentNftId; + return _registry.getParentNftId(stakeNftId); } diff --git a/contracts/staking/StakingService.sol b/contracts/staking/StakingService.sol index b52436e10..ba7034c78 100644 --- a/contracts/staking/StakingService.sol +++ b/contracts/staking/StakingService.sol @@ -32,6 +32,28 @@ contract StakingService is TokenHandler _tokenHandler; } + + modifier onlyStaking() { + if (msg.sender != address(_getStakingServiceStorage()._staking)) { + revert ErrorStakingServiceNotStaking(msg.sender); + } + _; + } + + + function approveTokenHandler( + IERC20Metadata token, + Amount amount + ) + external + virtual + onlyStaking() + { + _getStakingServiceStorage()._tokenHandler.approve( + token, amount); + } + + function createInstanceTarget( NftId targetNftId, Seconds initialLockingPeriod, @@ -113,8 +135,7 @@ contract StakingService is // transfer withdrawal amount to target owner address instanceOwner = getRegistry().ownerOf(instanceNftId); emit LogStakingServiceRewardReservesDecreased(instanceNftId, instanceOwner, dipAmount, newBalance); - _distributeToken( - $._staking.getTokenHandler(), + $._tokenHandler.pushToken( instanceOwner, dipAmount); } @@ -162,38 +183,11 @@ contract StakingService is emit LogStakingServiceStakeCreated(stakeNftId, targetNftId, stakeOwner, dipAmount); // collect staked dip by staking - _collectToken( - $._staking.getTokenHandler(), + $._tokenHandler.pullToken( stakeOwner, dipAmount); } - function _collectToken( - TokenHandler tokenHandler, - address from, - Amount amount - ) - internal - virtual - { - tokenHandler.collectTokens( - from, - amount); - } - - function _distributeToken( - TokenHandler tokenHandler, - address to, - Amount amount - ) - internal - virtual - { - tokenHandler.pushToken( - to, - amount); - } - function stake( NftId stakeNftId, @@ -217,8 +211,7 @@ contract StakingService is // collect staked dip by staking if (dipAmount.gtz()) { emit LogStakingServiceStakeIncreased(stakeNftId, stakeOwner, dipAmount, stakeBalance); - _collectToken( - $._staking.getTokenHandler(), + $._tokenHandler.pullToken( stakeOwner, dipAmount); } @@ -273,8 +266,7 @@ contract StakingService is Amount rewardsClaimedAmount = $._staking.claimRewards(stakeNftId); emit LogStakingServiceRewardsClaimed(stakeNftId, stakeOwner, rewardsClaimedAmount); - _distributeToken( - $._staking.getTokenHandler(), + $._tokenHandler.pushToken( stakeOwner, rewardsClaimedAmount); } @@ -299,7 +291,7 @@ contract StakingService is Amount totalAmount = unstakedAmount + rewardsClaimedAmount; emit LogStakingServiceUnstaked(stakeNftId, stakeOwner, totalAmount); - $._staking.getTokenHandler().pushToken( + $._tokenHandler.pushToken( stakeOwner, totalAmount); } @@ -358,15 +350,15 @@ contract StakingService is { ( address authority, - address registryAddress, - address stakingAddress + address registry, + address staking ) = abi.decode(data, (address, address, address)); - __Service_init(authority, registryAddress, owner); + __Service_init(authority, registry, owner); StakingServiceStorage storage $ = _getStakingServiceStorage(); $._registryService = RegistryService(_getServiceAddress(REGISTRY())); - $._staking = _registerStaking(stakingAddress); + $._staking = _registerStaking(staking); $._dip = $._staking.getToken(); $._tokenHandler = $._staking.getTokenHandler(); @@ -419,8 +411,7 @@ contract StakingService is emit LogStakingServiceRewardReservesIncreased(targetNftId, rewardProvider, dipAmount, newBalance); // collect reward dip from provider - _collectToken( - $._staking.getTokenHandler(), + $._tokenHandler.pullToken( rewardProvider, dipAmount); } diff --git a/contracts/staking/StakingServiceManager.sol b/contracts/staking/StakingServiceManager.sol index d5db6a19d..3b474e299 100644 --- a/contracts/staking/StakingServiceManager.sol +++ b/contracts/staking/StakingServiceManager.sol @@ -15,17 +15,18 @@ contract StakingServiceManager is /// @dev initializes proxy manager with service implementation constructor( address authority, - address registryAddress, + address registry, bytes32 salt ) { StakingService svc = new StakingService(); bytes memory data = abi.encode( authority, - registryAddress, - IRegistry(registryAddress).getStakingAddress()); + registry, + IRegistry(registry).getStakingAddress()); + IVersionable versionable = initialize( - registryAddress, + registry, address(svc), data, salt); diff --git a/contracts/type/ObjectType.sol b/contracts/type/ObjectType.sol index d71866342..c5fc67d0a 100644 --- a/contracts/type/ObjectType.sol +++ b/contracts/type/ObjectType.sol @@ -44,88 +44,101 @@ function INSTANCE() pure returns (ObjectType) { return ObjectType.wrap(10); } +/// @dev Generic component object type. +/// Component role id range is 11-19. +/// Stick to this range for new component object types. function COMPONENT() pure returns (ObjectType) { return ObjectType.wrap(11); } -function ACCOUNTING() pure returns (ObjectType) { +/// @dev Product object type. +/// IMPORTANT the actual value has an influence on the corresponding role id (RoleIdLib.sol). +/// Do not change this value without updating the corresponding role id calculation. +function PRODUCT() pure returns (ObjectType) { return ObjectType.wrap(12); } -function PRODUCT() pure returns (ObjectType) { +function ORACLE() pure returns (ObjectType) { return ObjectType.wrap(13); } -function FEE() pure returns (ObjectType) { +function DISTRIBUTION() pure returns (ObjectType) { return ObjectType.wrap(14); } -function ORACLE() pure returns (ObjectType) { +function POOL() pure returns (ObjectType) { return ObjectType.wrap(15); } -function DISTRIBUTION() pure returns (ObjectType) { - return ObjectType.wrap(16); +/// @dev Application object type. +/// Range for NFT objects created thorugh components is 20-29. +function APPLICATION() pure returns (ObjectType) { + return ObjectType.wrap(20); } -function POOL() pure returns (ObjectType) { - return ObjectType.wrap(17); +function POLICY() pure returns (ObjectType) { + return ObjectType.wrap(21); } -function APPLICATION() pure returns (ObjectType) { - return ObjectType.wrap(18); +function BUNDLE() pure returns (ObjectType) { + return ObjectType.wrap(22); } -function POLICY() pure returns (ObjectType) { - return ObjectType.wrap(19); +function DISTRIBUTOR() pure returns (ObjectType) { + return ObjectType.wrap(23); } -function PREMIUM() pure returns (ObjectType) { - return ObjectType.wrap(20); +/// @dev Stake object type. +/// NFT object type is 30 +function STAKE() pure returns (ObjectType) { + return ObjectType.wrap(30); } -function CLAIM() pure returns (ObjectType) { - return ObjectType.wrap(21); +/// @dev Staking target object type. +function TARGET() pure returns (ObjectType) { + return ObjectType.wrap(31); } -function PAYOUT() pure returns (ObjectType) { - return ObjectType.wrap(22); +/// @dev Accounting object type. +/// Range for non-NFT types created through components is 40+ +function ACCOUNTING() pure returns (ObjectType) { + return ObjectType.wrap(40); } -function RISK() pure returns (ObjectType) { - return ObjectType.wrap(23); +function FEE() pure returns (ObjectType) { + return ObjectType.wrap(41); } function PRICE() pure returns (ObjectType) { - return ObjectType.wrap(24); + return ObjectType.wrap(42); } -function REQUEST() pure returns (ObjectType) { - return ObjectType.wrap(25); +function PREMIUM() pure returns (ObjectType) { + return ObjectType.wrap(43); } -function DISTRIBUTOR_TYPE() pure returns (ObjectType) { - return ObjectType.wrap(26); +function RISK() pure returns (ObjectType) { + return ObjectType.wrap(44); } -function DISTRIBUTOR() pure returns (ObjectType) { - return ObjectType.wrap(27); +function CLAIM() pure returns (ObjectType) { + return ObjectType.wrap(45); } -function REFERRAL() pure returns (ObjectType) { - return ObjectType.wrap(28); +function PAYOUT() pure returns (ObjectType) { + return ObjectType.wrap(46); } -function BUNDLE() pure returns (ObjectType) { - return ObjectType.wrap(29); +function REQUEST() pure returns (ObjectType) { + return ObjectType.wrap(47); } -function TARGET() pure returns (ObjectType) { - return ObjectType.wrap(30); +function DISTRIBUTOR_TYPE() pure returns (ObjectType) { + return ObjectType.wrap(48); } -function STAKE() pure returns (ObjectType) { - return ObjectType.wrap(31); +function REFERRAL() pure returns (ObjectType) { + return ObjectType.wrap(49); } diff --git a/contracts/type/RoleId.sol b/contracts/type/RoleId.sol index a292e0551..7ca224bdf 100644 --- a/contracts/type/RoleId.sol +++ b/contracts/type/RoleId.sol @@ -11,9 +11,11 @@ type RoleId is uint64; using { eqRoleId as ==, neRoleId as !=, + RoleIdLib.toInt, RoleIdLib.eqz, RoleIdLib.gtz, - RoleIdLib.toInt + RoleIdLib.isComponentRole, + RoleIdLib.isCustomRole // RoleIdLib.toKey32 } for RoleId global; @@ -75,6 +77,8 @@ function RELEASE_REGISTRY_ROLE() pure returns (RoleId) { return RoleIdLib.toRole // - application service role (version 3): 2003 //--- GIF component contract roles (range 12'001 - 19'099) ------------------// +// the min value of 12'001 is based on the following calculation: +// object type * 1000 + 1 where the lowest object type is 12 (product) // assigned at component registration time // object type * 1000 + instane specific component counter // on any instance a maximum number of 999 components may be deployed @@ -95,7 +99,9 @@ library RoleIdLib { uint64 public constant ALL_VERSIONS = 99; uint64 public constant SERVICE_DOMAIN_ROLE_FACTOR = 100; - uint64 public constant COMPONENT_ROLE_FACTOR = 100; + uint64 public constant COMPONENT_ROLE_FACTOR = 1000; + uint64 public constant COMPONENT_ROLE_MIN_INT = 12000; + uint64 public constant COMPONENT_ROLE_MAX_INT = 19000; uint64 public constant CUSTOM_ROLE_MIN_INT = 1000000; /// @dev Converts the RoleId to a uint. @@ -153,6 +159,17 @@ library RoleIdLib { return RoleId.unwrap(a) == 0; } + /// @dev Returns true iff the role id is a component role. + function isComponentRole(RoleId roleId) public pure returns (bool) { + uint64 roleIdInt = RoleId.unwrap(roleId); + return roleIdInt >= COMPONENT_ROLE_MIN_INT && roleIdInt <= COMPONENT_ROLE_MAX_INT; + } + + /// @dev Returns true iff the role id is a custom role. + function isCustomRole(RoleId roleId) public pure returns (bool) { + return RoleId.unwrap(roleId) >= CUSTOM_ROLE_MIN_INT; + } + /// @dev Returns the key32 value for the specified id and object type. function toKey32(RoleId a) public pure returns (Key32 key) { return Key32Lib.toKey32(ROLE(), toKeyId(a)); diff --git a/contracts/type/Selector.sol b/contracts/type/Selector.sol index dfa5fe59f..1ab85afe3 100644 --- a/contracts/type/Selector.sol +++ b/contracts/type/Selector.sol @@ -8,6 +8,7 @@ using { eqSelector as ==, neSelector as !=, SelectorLib.toBytes4, + SelectorLib.toString, SelectorLib.eqz } for Selector global; @@ -46,6 +47,10 @@ library SelectorLib { function toBytes4(Selector s) public pure returns (bytes4) { return Selector.unwrap(s); } + + function toString(Selector s) public pure returns (string memory) { + return string(abi.encode(Selector.unwrap(s))); + } } // selector specific set library diff --git a/scripts/deploy_fire_components.ts b/scripts/deploy_fire_components.ts index 7c171206b..d4e80c9dc 100644 --- a/scripts/deploy_fire_components.ts +++ b/scripts/deploy_fire_components.ts @@ -90,6 +90,7 @@ export async function deployFireComponentContracts(libraries: LibraryAddresses, [fireProductName], { libraries: { + ObjectTypeLib: objectTypeLibAddress, RoleIdLib: roleIdLibAddress, SelectorLib: selectorLibAddress, StrLib: strLibAddress, diff --git a/scripts/libs/libraries.ts b/scripts/libs/libraries.ts index f732cab93..f540fb5f4 100644 --- a/scripts/libs/libraries.ts +++ b/scripts/libs/libraries.ts @@ -23,6 +23,7 @@ export type LibraryAddresses = { key32LibAddress: AddressLike; libKey32SetAddress: AddressLike; feeLibAddress: AddressLike; + poolLibAddress: AddressLike; stateIdLibAddress: AddressLike; roleIdLibAddress: AddressLike; riskIdLibAddress: AddressLike; @@ -234,6 +235,21 @@ export async function deployLibraries(owner: Signer): Promise }); LIBRARY_ADDRESSES.set("FeeLib", feeLibAddress); + const { address: poolLibAddress } = await deployContract( + "PoolLib", + owner, + undefined, + { + libraries: { + AmountLib: amountLibAddress, + ContractLib: contractLibAddress, + FeeLib: feeLibAddress, + NftIdLib: nftIdLibAddress, + UFixedLib: uFixedLibAddress, + } + }); + LIBRARY_ADDRESSES.set("PoolLib", poolLibAddress); + const { address: distributorTypeLibAddress } = await deployContract( "DistributorTypeLib", owner, @@ -339,6 +355,7 @@ export async function deployLibraries(owner: Signer): Promise key32LibAddress, libKey32SetAddress, feeLibAddress, + poolLibAddress, stateIdLibAddress, roleIdLibAddress, riskIdLibAddress, diff --git a/scripts/libs/services.ts b/scripts/libs/services.ts index 9e2cd67dd..03cabcfdc 100644 --- a/scripts/libs/services.ts +++ b/scripts/libs/services.ts @@ -452,11 +452,10 @@ export async function deployAndRegisterServices(owner: Signer, registry: Registr { libraries: { AmountLib: libraries.amountLibAddress, ContractLib: libraries.contractLibAddress, - FeeLib: libraries.feeLibAddress, + PoolLib: libraries.poolLibAddress, NftIdLib: libraries.nftIdLibAddress, RoleIdLib: libraries.roleIdLibAddress, TimestampLib: libraries.timestampLibAddress, - UFixedLib: libraries.uFixedLibAddress, VersionLib: libraries.versionLibAddress, }}); @@ -618,11 +617,10 @@ export async function deployAndRegisterServices(owner: Signer, registry: Registr AmountLib: libraries.amountLibAddress, ClaimIdLib: libraries.claimIdLibAddress, ContractLib: libraries.contractLibAddress, - FeeLib: libraries.feeLibAddress, NftIdLib: libraries.nftIdLibAddress, + PayoutIdLib: libraries.payoutIdLibAddress, RoleIdLib: libraries.roleIdLibAddress, TimestampLib: libraries.timestampLibAddress, - PayoutIdLib: libraries.payoutIdLibAddress, VersionLib: libraries.versionLibAddress, }}); diff --git a/test/Component.t.sol b/test/Component.t.sol index 7e730df31..ac664409d 100644 --- a/test/Component.t.sol +++ b/test/Component.t.sol @@ -7,7 +7,7 @@ import {IAccessManaged} from "@openzeppelin/contracts/access/manager/IAccessMana import {BasicDistributionAuthorization} from "../contracts/distribution/BasicDistributionAuthorization.sol"; import {Fee, FeeLib} from "../contracts/type/Fee.sol"; import {GifTest} from "./base/GifTest.sol"; -import {IAccess} from "../contracts/instance/module/IAccess.sol"; +import {IAccess} from "../contracts/authorization/IAccess.sol"; import {IComponent} from "../contracts/shared/IComponent.sol"; import {IComponents} from "../contracts/instance/module/IComponents.sol"; import {IComponentService} from "../contracts/shared/IComponentService.sol"; @@ -24,6 +24,8 @@ contract TestComponent is GifTest { function setUp() public override { super.setUp(); _prepareProduct(); // also deploys and registers distribution + + _printAuthz(instance.getInstanceAdmin(), "instance (including components)"); } function test_componentGetComponentInfo() public { diff --git a/test/TestBundle.t.sol b/test/TestBundle.t.sol index 4488e2997..44629a251 100644 --- a/test/TestBundle.t.sol +++ b/test/TestBundle.t.sol @@ -32,7 +32,7 @@ contract TestBundle is GifTest { /// @dev test staking of an existing bundle - function test_Bundle_stakeBundle() public { + function test_bundle_stakeBundle() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -86,7 +86,7 @@ contract TestBundle is GifTest { } /// @dev test staking of an existing locked bundle - function test_Bundle_stakeBundle_lockedBundle() public { + function test_bundle_stakeBundle_lockedBundle() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -139,7 +139,7 @@ contract TestBundle is GifTest { assertEq(instanceReader.getFeeAmount(bundleNftId).toInt(), 0, "bundle fees 0"); } - function test_Bundle_stakeBundle_maxBalanceExceeded() public { + function test_bundle_stakeBundle_maxBalanceExceeded() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -181,7 +181,7 @@ contract TestBundle is GifTest { } /// @dev test staking when the allowance is too small - function test_Bundle_stakeBundle_allowanceTooSmall() public { + function test_bundle_stakeBundle_allowanceTooSmall() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -216,7 +216,7 @@ contract TestBundle is GifTest { } /// @dev test staking amount of zero - function test_Bundle_stakeBundle_amountIsZero() public { + function test_bundle_stakeBundle_amountIsZero() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -245,7 +245,7 @@ contract TestBundle is GifTest { } /// @dev test staking into an expired bundle - function test_Bundle_stakeBundle_bundleExpired() public { + function test_bundle_stakeBundle_bundleExpired() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -281,7 +281,7 @@ contract TestBundle is GifTest { } /// @dev test staking into a closed bundle - function test_Bundle_stakeBundle_bundleClosed() public { + function test_bundle_stakeBundle_bundleClosed() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -316,7 +316,7 @@ contract TestBundle is GifTest { } /// @dev test unstaking of a bundle - function test_Bundle_unstakeBundle() public { + function test_bundle_unstakeBundle() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -367,7 +367,7 @@ contract TestBundle is GifTest { } /// @dev test unstaking of all available staked tokens - function test_Bundle_unstakeBundle_maxAmount() public { + function test_bundle_unstakeBundle_maxAmount() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -419,7 +419,7 @@ contract TestBundle is GifTest { } /// @dev test unstaking of an amount that exceeds the available balance - function test_Bundle_unstakeBundle_exceedsAvailable() public { + function test_bundle_unstakeBundle_exceedsAvailable() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -451,7 +451,7 @@ contract TestBundle is GifTest { } /// @dev test unstaking of an amount that exceeds the available balance - function test_Bundle_unstakeBundle_amountZero() public { + function test_bundle_unstakeBundle_amountZero() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -480,7 +480,7 @@ contract TestBundle is GifTest { } /// @dev test unstaking of an amount when allowance is too small - function test_Bundle_unstakeBundle_allowanceTooSmall() public { + function test_bundle_unstakeBundle_allowanceTooSmall() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(false); @@ -523,7 +523,7 @@ contract TestBundle is GifTest { } /// @dev test extension of a bundle - function test_Bundle_extend() public { + function test_bundle_extend() public { // GIVEN _prepareProduct(false); @@ -563,7 +563,7 @@ contract TestBundle is GifTest { } /// @dev test extension of an expired bundle - function test_Bundle_extend_bundleExpired() public { + function test_bundle_extend_bundleExpired() public { // GIVEN _prepareProduct(false); @@ -602,7 +602,7 @@ contract TestBundle is GifTest { } /// @dev test extension of a closed bundle - function test_Bundle_extend_bundleClosed() public { + function test_bundle_extend_bundleClosed() public { // GIVEN _prepareProduct(false); @@ -634,7 +634,7 @@ contract TestBundle is GifTest { } /// @dev test extension with lifetime is zero - function test_Bundle_extend_lifetimeIsZero() public { + function test_bundle_extend_lifetimeIsZero() public { // GIVEN _prepareProduct(false); @@ -658,7 +658,7 @@ contract TestBundle is GifTest { } /// @dev test closing of a bundle - function test_Bundle_closeBundle() public { + function test_bundleCloseBundleHappyCase() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(true); @@ -698,7 +698,7 @@ contract TestBundle is GifTest { } /// @dev test closing of a bundle with a closed policy (no staking fee) - function test_Bundle_closeBundle_withClosedPolicy() public { + function test_bundleCloseBundle_withClosedPolicy() public { // GIVEN - pool (no staking fee), a bundle with 3% fee and a closed policy initialBundleFee = FeeLib.percentageFee(3); _prepareProduct(true); @@ -741,7 +741,7 @@ contract TestBundle is GifTest { } /// @dev test closing of a bundle without any balance (full unstake before) - function test_Bundle_closeBundle_unstakeFullAmountBeforeClose() public { + function test_bundleCloseBundle_unstakeFullAmountBeforeClose() public { // GIVEN - pool (3% staking fee), a bundle and no policy initialStakingFee = FeeLib.percentageFee(3); _prepareProduct(true); @@ -784,7 +784,7 @@ contract TestBundle is GifTest { } /// @dev test closing of a bundle with a closed policy and no balance (full unstake before close) - function test_Bundle_closeBundle_withClosedPolicyUnstakeFullAmountBeforeClose() public { + function test_bundleCloseBundle_withClosedPolicyUnstakeFullAmountBeforeClose() public { // GIVEN - pool (3% staking fee), a bundle (6% bundle fee) and a closed policy initialStakingFee = FeeLib.percentageFee(3); initialBundleFee = FeeLib.percentageFee(6); @@ -831,7 +831,7 @@ contract TestBundle is GifTest { } /// @dev test closing of a bundle with a closed policy and no fees (fee withdrawal before close) - function test_Bundle_closeBundle_withClosedPolicyWithdrawFeesBeforeClose() public { + function test_bundleCloseBundle_withClosedPolicyWithdrawFeesBeforeClose() public { // GIVEN - pool (3% staking fee), a bundle (6% fee) and a closed policy initialStakingFee = FeeLib.percentageFee(3); initialBundleFee = FeeLib.percentageFee(6); @@ -878,7 +878,7 @@ contract TestBundle is GifTest { } /// @dev test closing of a bundle with a closed policy and no fees (fee withdrawal before close) - function test_Bundle_closeBundle_withClosedPolicyUnstakedAndWithdrawFeesBeforeClose() public { + function test_bundleCloseBundle_withClosedPolicyUnstakedAndWithdrawFeesBeforeClose() public { // GIVEN - pool (3% staking fee), a bundle (6% fee) and a closed policy initialStakingFee = FeeLib.percentageFee(3); initialBundleFee = FeeLib.percentageFee(6); @@ -926,7 +926,7 @@ contract TestBundle is GifTest { } /// @dev test closing of a bundle with an open policy - function test_Bundle_closeBundle_openPolicy() public { + function test_bundleCloseBundle_openPolicy() public { // GIVEN initialBundleFee = FeeLib.percentageFee(3); _prepareProduct(true); @@ -963,7 +963,7 @@ contract TestBundle is GifTest { } /// @dev test closing of a bundle when allowance is too small - function test_Bundle_closeBundle_allowanceTooSmall() public { + function test_bundleCloseBundle_allowanceTooSmall() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(true); @@ -994,7 +994,7 @@ contract TestBundle is GifTest { } /// @dev test closing of a bundle when caller is not the owner - function test_Bundle_closeBundle_notOwner() public { + function test_bundleCloseBundle_notOwner() public { // GIVEN initialStakingFee = FeeLib.percentageFee(4); _prepareProduct(true); diff --git a/test/TestDeployAll.t.sol b/test/TestDeployAll.t.sol index 3a854fb45..cb1be8982 100644 --- a/test/TestDeployAll.t.sol +++ b/test/TestDeployAll.t.sol @@ -18,6 +18,7 @@ contract TestDeployAll is GifTest { } function test_deployAllSetup() public { + _printAuthz(instanceAdmin, "instance with components"); assertTrue(true); } diff --git a/test/TestDistribution.t.sol b/test/TestDistribution.t.sol index f2a0b8d97..287eccd48 100644 --- a/test/TestDistribution.t.sol +++ b/test/TestDistribution.t.sol @@ -6,7 +6,7 @@ import {console} from "../lib/forge-std/src/Test.sol"; import {BasicDistributionAuthorization} from "../contracts/distribution/BasicDistributionAuthorization.sol"; import {Fee, FeeLib} from "../contracts/type/Fee.sol"; import {GifTest} from "./base/GifTest.sol"; -import {IAccess} from "../contracts/instance/module/IAccess.sol"; +import {IAccess} from "../contracts/authorization/IAccess.sol"; import {IComponent} from "../contracts/shared/IComponent.sol"; import {IComponents} from "../contracts/instance/module/IComponents.sol"; import {IComponentService} from "../contracts/shared/IComponentService.sol"; diff --git a/test/TestFees.t.sol b/test/TestFees.t.sol index ccf897949..a70d95b77 100644 --- a/test/TestFees.t.sol +++ b/test/TestFees.t.sol @@ -2,22 +2,25 @@ pragma solidity ^0.8.20; import {console} from "../lib/forge-std/src/Test.sol"; -import {COLLATERALIZED} from "../contracts/type/StateId.sol"; -import {DistributorType} from "../contracts/type/DistributorType.sol"; -import {IDistributionService} from "../contracts/distribution/IDistributionService.sol"; -import {GifTest} from "./base/GifTest.sol"; -import {NftId, NftIdLib} from "../contracts/type/NftId.sol"; + import {IBundleService} from "../contracts/pool/IBundleService.sol"; import {IComponentService} from "../contracts/shared/IComponentService.sol"; import {IComponents} from "../contracts/instance/module/IComponents.sol"; +import {IDistributionService} from "../contracts/distribution/IDistributionService.sol"; import {IPolicy} from "../contracts/instance/module/IPolicy.sol"; import {IPoolComponent} from "../contracts/pool/IPoolComponent.sol"; +import {IPoolService} from "../contracts/pool/IPoolService.sol"; + +import {COLLATERALIZED} from "../contracts/type/StateId.sol"; +import {DistributorType} from "../contracts/type/DistributorType.sol"; +import {GifTest} from "./base/GifTest.sol"; +import {NftId} from "../contracts/type/NftId.sol"; import {FeeLib} from "../contracts/type/Fee.sol"; import {RiskId, RiskIdLib} from "../contracts/type/RiskId.sol"; import {ReferralId, ReferralLib} from "../contracts/type/Referral.sol"; import {Seconds, SecondsLib} from "../contracts/type/Seconds.sol"; import {Timestamp, TimestampLib} from "../contracts/type/Timestamp.sol"; -import {TokenHandler, TokenHandlerBase} from "../contracts/shared/TokenHandler.sol"; +import {TokenHandlerBase} from "../contracts/shared/TokenHandler.sol"; import {Amount, AmountLib} from "../contracts/type/Amount.sol"; import {INftOwnable} from "../contracts/shared/INftOwnable.sol"; @@ -182,7 +185,7 @@ contract TestFees is GifTest { } /// @dev test withdraw fees from pool component as pool owner - function test_feesWithdrawPoolFees() public { + function test_feesWithdrawPoolFeesHappyCase() public { // GIVEN _setupWithActivePolicy(false); @@ -213,7 +216,7 @@ contract TestFees is GifTest { } /// @dev test withdraw fees from pool component as not the pool owner - function test_feesWithdrawPoolFees_notOwner() public { + function test_feesWithdrawPoolFeesNotOwner() public { // GIVEN _setupWithActivePolicy(false); @@ -278,7 +281,7 @@ contract TestFees is GifTest { } /// @dev test withdraw of distributor commission - function test_feesWithdrawCommission() public { + function test_feesWithdrawCommissionHappyCase() public { // GIVEN _setupWithActivePolicy(true); @@ -329,7 +332,7 @@ contract TestFees is GifTest { } /// @dev test withdraw of distributor commission as not the distributor - function test_feesWithdrawCommission_notDistributor() public { + function test_feesWithdrawCommissionNotDistributor() public { // GIVEN _setupWithActivePolicy(true); @@ -344,7 +347,7 @@ contract TestFees is GifTest { } /// @dev test withdraw of distributor commission with max value - function test_feesWithdrawCommission_maxAmount() public { + function test_feesWithdrawCommissionMaxAmount() public { // GIVEN _setupWithActivePolicy(true); @@ -393,7 +396,7 @@ contract TestFees is GifTest { } /// @dev test withdraw of distributor commission with a too large amount - function test_feesWithdrawCommission_amountTooLarge() public { + function test_feesWithdrawCommissionAmountTooLarge() public { // GIVEN _setupWithActivePolicy(true); @@ -411,7 +414,7 @@ contract TestFees is GifTest { } /// @dev test withdraw of distributor commission with a zero amount - function test_feesWithdrawCommission_amountIsZero() public { + function test_feesWithdrawCommissionAmountIsZero() public { // GIVEN _setupWithActivePolicy(true); @@ -427,7 +430,7 @@ contract TestFees is GifTest { } /// @dev test withdraw of distributor commission when allowance is too small - function test_feesWithdrawCommission_allowanceTooSmall() public { + function test_feesWithdrawCommissionAllowanceTooSmall() public { // GIVEN _setupWithActivePolicy(true); @@ -452,7 +455,7 @@ contract TestFees is GifTest { distribution.withdrawCommission(distributorNftId, withdrawAmount); } - function test_feesWithdrawBundleFees() public { + function test_feesWithdrawBundleFeesHappyCase() public { // GIVEN _setupWithActivePolicy(false); @@ -472,7 +475,7 @@ contract TestFees is GifTest { // THEN - expect a log entry for the fee withdrawal vm.expectEmit(); - emit IBundleService.LogBundleServiceFeesWithdrawn( + emit IPoolService.LogPoolServiceFeesWithdrawn( bundleNftId, investor, address(token), @@ -514,7 +517,7 @@ contract TestFees is GifTest { // THEN - expect a log entry for the fee withdrawal vm.expectEmit(); - emit IBundleService.LogBundleServiceFeesWithdrawn( + emit IPoolService.LogPoolServiceFeesWithdrawn( bundleNftId, investor, address(token), @@ -570,7 +573,7 @@ contract TestFees is GifTest { // THEN - expect a log entry for the fee withdrawal vm.expectEmit(); - emit IBundleService.LogBundleServiceFeesWithdrawn( + emit IPoolService.LogPoolServiceFeesWithdrawn( bundleNftId, investor, address(token), @@ -610,7 +613,7 @@ contract TestFees is GifTest { // THEN - expect a revert vm.expectRevert(abi.encodeWithSelector( - IBundleService.ErrorBundleServiceFeesWithdrawAmountExceedsLimit.selector, + IPoolService.ErrorPoolServiceFeesWithdrawAmountExceedsLimit.selector, withdrawAmount, 10)); diff --git a/test/TestProduct.t.sol b/test/TestProduct.t.sol index 522880cc3..56de3d4db 100644 --- a/test/TestProduct.t.sol +++ b/test/TestProduct.t.sol @@ -1014,7 +1014,7 @@ contract TestProduct is GifTest { } /// @dev test that policy expiration works - function test_productPolicyExpire() public { + function test_productPolicyExpireHappyCase() public { // GIVEN vm.startPrank(registryOwner); token.transfer(customer, 1000); diff --git a/test/authorization/InstanceAdmin.t.sol b/test/authorization/InstanceAdmin.t.sol index 2dad81e86..fb41941ab 100644 --- a/test/authorization/InstanceAdmin.t.sol +++ b/test/authorization/InstanceAdmin.t.sol @@ -16,49 +16,41 @@ import {BUNDLE, COMPONENT, DISTRIBUTION, ORACLE, POOL, PRODUCT, POLICY, RISK, RE import {RoleId} from "../../contracts/type/RoleId.sol"; import {VersionPartLib} from "../../contracts/type/Version.sol"; + +contract TestInstanceAdminMockInstance { + address public authority; + + constructor(address auth) { + authority = auth; + } +} + + contract TestInstanceAdmin is GifTest { + // address public someInstanceAuthz; + InstanceAdmin public someInstanceAdmin; AccessManagerCloneable public someAccessManager; - address public someInstanceAuthz; - InstanceAdmin public someInstanceAdminMaster; function setUp() public override { super.setUp(); - someAccessManager = new AccessManagerCloneable(); - someInstanceAuthz = address(new InstanceAuthorizationV3()); - someInstanceAdminMaster = new InstanceAdmin(someInstanceAuthz); - someAccessManager = AccessManagerCloneable(someInstanceAdminMaster.authority()); + someAccessManager = AccessManagerCloneable( + Clones.clone(instance.authority())); + + someInstanceAdmin = InstanceAdmin( + Clones.clone( + address(instance.getInstanceAdmin()))); + + someInstanceAdmin.initialize( + someAccessManager, + instance.getRegistry(), + instance.getRelease()); } function test_instanceAdminSetup() public { - vm.startPrank(instanceOwner); - InstanceAdmin clonedAdmin = _cloneNewInstanceAdmin(); - vm.stopPrank(); - - _printAuthz(clonedAdmin, "instance"); + _printAuthz(someInstanceAdmin, "instance"); assertTrue(true, "something is wrong"); } - - function _cloneNewInstanceAdmin() - internal - returns (InstanceAdmin clonedInstanceAdmin) - { - clonedInstanceAdmin = InstanceAdmin( - Clones.clone( - address(someInstanceAdminMaster))); - - // create AccessManager and assign admin role to clonedInstanceAdmin - AccessManagerCloneable clonedAccessMananger = new AccessManagerCloneable(); - - clonedInstanceAdmin.initialize( - clonedAccessMananger, - registry, - VersionPartLib.toVersionPart(3)); - - clonedInstanceAdmin.completeSetup( - address(instance), - someInstanceAuthz); - } } \ No newline at end of file diff --git a/test/base/GifTest.sol b/test/base/GifTest.sol index 922c42e2d..770de9abf 100644 --- a/test/base/GifTest.sol +++ b/test/base/GifTest.sol @@ -16,12 +16,15 @@ import {Timestamp} from "../../contracts/type/Timestamp.sol"; import {IAccess} from "../../contracts/authorization/IAccess.sol"; import {IAccessAdmin} from "../../contracts/authorization/IAccessAdmin.sol"; +import {IServiceAuthorization} from "../../contracts/authorization/IServiceAuthorization.sol"; import {SimpleDistributionAuthorization} from "../../contracts/examples/unpermissioned/SimpleDistributionAuthorization.sol"; import {BasicOracleAuthorization} from "../../contracts/oracle/BasicOracleAuthorization.sol"; +import {BasicProductAuthorization} from "../../contracts/product/BasicProductAuthorization.sol"; +// TODO cleanup +// import {BasicPoolAuthorization} from "../../contracts/pool/BasicPoolAuthorization.sol"; import {SimplePoolAuthorization} from "../../contracts/examples/unpermissioned/SimplePoolAuthorization.sol"; -import {SimpleProductAuthorization} from "../../contracts/examples/unpermissioned/SimpleProductAuthorization.sol"; -import {IServiceAuthorization} from "../../contracts/authorization/IServiceAuthorization.sol"; +// import {SimpleProductAuthorization} from "../../contracts/examples/unpermissioned/SimpleProductAuthorization.sol"; import {RegistryAdmin} from "../../contracts/registry/RegistryAdmin.sol"; import {ReleaseRegistry} from "../../contracts/registry/ReleaseRegistry.sol"; import {ServiceAuthorizationV3} from "../../contracts/registry/ServiceAuthorizationV3.sol"; @@ -487,7 +490,7 @@ contract GifTest is GifDeployer { address(token), _getSimpleProductInfo(), _getSimpleFeeInfo(), - new SimpleProductAuthorization(name), + new BasicProductAuthorization(name), productOwner // initial owner ); vm.stopPrank(); @@ -500,13 +503,9 @@ contract GifTest is GifDeployer { newNftId = instance.registerProduct(address(newProduct)); vm.stopPrank(); - // token handler only becomes available after registration - vm.startPrank(productOwner); - newProduct.approveTokenHandler(token, AmountLib.max()); - vm.stopPrank(); - // solhint-disable-next-line console.log("product nft id", newNftId.toInt()); + console.log("product parent nft id", registry.getParentNftId(newNftId).toInt()); } @@ -561,11 +560,6 @@ contract GifTest is GifDeployer { vm.stopPrank(); poolNftId = _registerComponent(product, address(pool), "pool"); - - // token handler only becomes available after registration - vm.startPrank(poolOwner); - pool.approveTokenHandler(token, AmountLib.max()); - vm.stopPrank(); } function _getDefaultSimplePoolInfo() internal view returns (IComponents.PoolInfo memory) { @@ -594,11 +588,6 @@ contract GifTest is GifDeployer { vm.stopPrank(); distributionNftId = _registerComponent(product, address(distribution), "distribution"); - - // token handler only becomes available after registration - vm.startPrank(distributionOwner); - distribution.approveTokenHandler(token, AmountLib.max()); - vm.stopPrank(); } @@ -633,8 +622,10 @@ contract GifTest is GifDeployer { componentNftId = prd.registerComponent(component); vm.stopPrank(); - // solhint-disable-next-line + // solhint-disable console.log(componentName, "nft id", componentNftId.toInt()); + console.log(componentName, "parent nft id", registry.getParentNftId(componentNftId).toInt()); + // solhint-enable } diff --git a/test/component/product/ProductPayout.t.sol b/test/component/product/ProductPayout.t.sol index a7ea25434..a048307c5 100644 --- a/test/component/product/ProductPayout.t.sol +++ b/test/component/product/ProductPayout.t.sol @@ -201,29 +201,17 @@ contract TestProductClaim is GifTest { , // IPolicy.PayoutInfo memory payoutInfo ) = _createClaimAndPayout(policyNftId, claimAmountInt, payoutAmountInt, payoutData, false); - // WHEN - vm.recordLogs(); - product.processPayout(policyNftId, payoutId); - Vm.Log[] memory entries = vm.getRecordedLogs(); + // WHEN + THEN + Amount payoutAmount = AmountLib.toAmount(payoutAmountInt); + vm.expectEmit(address(claimService)); + emit IClaimService.LogClaimServicePayoutProcessed( + policyNftId, + payoutId, + payoutAmount); - // search for right index - // solhint-disable - console.log("logs", entries.length); - for(uint i = 0; i < entries.length; i++) { - console.log(i, vm.toString(entries[i].topics[0]), vm.toString(keccak256("LogClaimServicePayoutProcessed(uint96,uint24,uint96,address,uint96)"))); - } - // solhint-enable + product.processPayout(policyNftId, payoutId); // THEN - // checking last of 11 logs - assertEq(entries.length, 11, "unexpected number of logs"); - assertEq(entries[8].topics[0], keccak256("LogClaimServicePayoutProcessed(uint96,uint40,uint96,address,uint96,uint96)"), "unexpected log signature"); - assertEq(entries[8].emitter, address(claimService), "unexpected emitter"); - (uint96 nftIdInt, uint40 payoutIdInt, uint96 payoutAmntInt) = abi.decode(entries[8].data, (uint96,uint40,uint96)); - assertEq(nftIdInt, policyNftId.toInt(), "unexpected policy nft id"); - assertEq(payoutIdInt, payoutId.toInt(), "unexpected payout id"); - assertEq(payoutAmntInt, payoutAmountInt, "unexpected payout amount"); - // check policy { IPolicy.PolicyInfo memory policyInfo = instanceReader.getPolicyInfo(policyNftId); diff --git a/test/examples/composeability/DistributionWithReinsuranceAuthorization.sol b/test/examples/composeability/DistributionWithReinsuranceAuthorization.sol deleted file mode 100644 index f2d09b032..000000000 --- a/test/examples/composeability/DistributionWithReinsuranceAuthorization.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {Authorization} from "../../../contracts/authorization/Authorization.sol"; -// import {Distribution} from "../../../contracts/distribution/Distribution.sol"; -import {DISTRIBUTION} from "../../../contracts/type/ObjectType.sol"; -import {IAccess} from "../../../contracts/authorization/IAccess.sol"; -import {IInstanceLinkedComponent} from "../../../contracts/shared/IInstanceLinkedComponent.sol"; -import {PUBLIC_ROLE} from "../../../contracts/type/RoleId.sol"; - - -contract DistributionWithReinsuranceAuthorization - is Authorization -{ - - constructor() - Authorization("DistributionWithReinsurance") - {} - - function _setupTargets() - internal - virtual override - { - uint64 index = 1; // 0 is default - _addComponentTargetWithRole(DISTRIBUTION(), index); - } - - - function _setupTargetAuthorizations() - internal - virtual override - { - // we don't need distribution, empty implementation is sufficient - } -} - diff --git a/test/examples/composeability/PoolWithReinsuranceAuthorization.sol b/test/examples/composeability/PoolWithReinsuranceAuthorization.sol index 350fe9e72..b17e94a6f 100644 --- a/test/examples/composeability/PoolWithReinsuranceAuthorization.sol +++ b/test/examples/composeability/PoolWithReinsuranceAuthorization.sol @@ -1,62 +1,40 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Authorization} from "../../../contracts/authorization/Authorization.sol"; import {IAccess} from "../../../contracts/authorization/IAccess.sol"; import {IInstanceLinkedComponent} from "../../../contracts/shared/IInstanceLinkedComponent.sol"; import {IPolicyHolder} from "../../../contracts/shared/IPolicyHolder.sol"; +import {IPoolComponent} from "../../../contracts/pool/IPoolComponent.sol"; + +import {Authorization} from "../../../contracts/authorization/Authorization.sol"; import {BasicPool} from "../../../contracts/pool/BasicPool.sol"; +import {BasicPoolAuthorization} from "../../../contracts/pool/BasicPoolAuthorization.sol"; import {SimplePool} from "../../../contracts/examples/unpermissioned/SimplePool.sol"; -import {IPoolComponent} from "../../../contracts/pool/IPoolComponent.sol"; import {CLAIM, POOL, POLICY} from "../../../contracts/type/ObjectType.sol"; import {PUBLIC_ROLE} from "../../../contracts/type/RoleId.sol"; import {RoleId} from "../../../contracts/type/RoleId.sol"; contract PoolWithReinsuranceAuthorization - is Authorization + is BasicPoolAuthorization { constructor() - Authorization("PoolWithReinsurance") + BasicPoolAuthorization("PoolWithReinsurance") {} - function _setupTargets() - internal - virtual override - { - uint64 index = 1; // 0 is default - _addComponentTargetWithRole(POOL(), index); - } - - function _setupTargetAuthorizations() internal virtual override { - IAccess.FunctionInfo[] storage functions; - - // authorize public role (open access to any account, only allows to lock target) - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); - _authorize(functions, BasicPool.stake.selector, "stake"); - _authorize(functions, BasicPool.unstake.selector, "unstake"); - _authorize(functions, BasicPool.extend.selector, "extend"); - _authorize(functions, BasicPool.lockBundle.selector, "lockBundle"); - _authorize(functions, BasicPool.unlockBundle.selector, "unlockBundle"); - _authorize(functions, BasicPool.closeBundle.selector, "closeBundle"); - _authorize(functions, BasicPool.setBundleFee.selector, "setBundleFee"); - _authorize(functions, BasicPool.setMaxBalanceAmount.selector, "setMaxBalanceAmount"); - _authorize(functions, BasicPool.setFees.selector, "setFees"); - _authorize(functions, BasicPool.stake.selector, "stake"); - _authorize(functions, BasicPool.unstake.selector, "unstake"); - _authorize(functions, BasicPool.extend.selector, "extend"); - _authorize(functions, BasicPool.withdrawBundleFees.selector, "withdrawBundleFees"); - _authorize(functions, SimplePool.approveTokenHandler.selector, "approveTokenHandler"); - _authorize(functions, IInstanceLinkedComponent.withdrawFees.selector, "withdrawFees"); + super._setupTargetAuthorizations(); // authorize claim service for callback - functions = _authorizeForTarget(getTargetName(), getServiceRole(CLAIM())); + IAccess.FunctionInfo[] storage functions; + functions = _authorizeForTarget(getMainTargetName(), getServiceRole(CLAIM())); _authorize(functions, IPoolComponent.processConfirmedClaim.selector, "processConfirmedClaim"); + + functions = _authorizeForTarget(getMainTargetName(), getServiceRole(POOL())); _authorize(functions, IPolicyHolder.payoutExecuted.selector, "payoutExecuted"); } } diff --git a/test/examples/composeability/ProductWithReinsurance.t.sol b/test/examples/composeability/ProductWithReinsurance.t.sol index c485ebdac..06925be45 100644 --- a/test/examples/composeability/ProductWithReinsurance.t.sol +++ b/test/examples/composeability/ProductWithReinsurance.t.sol @@ -5,7 +5,6 @@ import {Vm, console} from "../../../lib/forge-std/src/Test.sol"; import {GifTest} from "../../base/GifTest.sol"; import {Amount, AmountLib} from "../../../contracts/type/Amount.sol"; -import {DistributionWithReinsuranceAuthorization} from "./DistributionWithReinsuranceAuthorization.sol"; import {PoolWithReinsuranceAuthorization} from "./PoolWithReinsuranceAuthorization.sol"; import {ProductWithReinsuranceAuthorization} from "./ProductWithReinsuranceAuthorization.sol"; import {NftId, NftIdLib} from "../../../contracts/type/NftId.sol"; @@ -13,7 +12,6 @@ import {ClaimId, ClaimIdLib} from "../../../contracts/type/ClaimId.sol"; import {ContractLib} from "../../../contracts/shared/ContractLib.sol"; import {ProductWithReinsurance} from "./ProductWithReinsurance.sol"; import {PoolWithReinsurance} from "./PoolWithReinsurance.sol"; -import {SimpleDistribution} from "../../../contracts/examples/unpermissioned/SimpleDistribution.sol"; import {SimpleProduct} from "../../../contracts/examples/unpermissioned/SimpleProduct.sol"; import {SimplePool} from "../../../contracts/examples/unpermissioned/SimplePool.sol"; import {IComponents} from "../../../contracts/instance/module/IComponents.sol"; @@ -43,11 +41,9 @@ contract ProductWithReinsuranceTest is uint256 public constant SUM_INSURED = 1000; uint256 public constant CUSTOMER_FUNDS = 400; - SimpleDistribution public distributionRe; PoolWithReinsurance public poolRe; ProductWithReinsurance public productRe; - NftId public distributionReNftId; NftId public poolReNftId; NftId public productReNftId; NftId public policyReNftId; @@ -69,6 +65,7 @@ contract ProductWithReinsuranceTest is // reinsurance product _prepareProduct(); + _printAuthz(instanceAdmin, "instance with simple product setup"); // setup product with reinsurance _prepareProductWithReinsurance(); @@ -322,7 +319,9 @@ contract ProductWithReinsuranceTest is // create allowance to pay for premium uint256 maxPremiumAmount = SUM_INSURED * 10**token.decimals() / 4; vm.startPrank(policyHolder); - token.approve(instanceReader.getTokenHandler(productReNftId), maxPremiumAmount); + token.approve( + address(instanceReader.getTokenHandler(productReNftId)), + maxPremiumAmount); vm.stopPrank(); newPolicyNftId = productRe.createApplication( @@ -372,7 +371,7 @@ contract ProductWithReinsuranceTest is vm.startPrank(instanceOwner); productReNftId = instance.registerProduct(address(productRe)); vm.stopPrank(); - + // solhint-disable console.log("product nft id", productReNftId.toInt()); console.log("product component at", address(productRe)); @@ -393,10 +392,7 @@ contract ProductWithReinsuranceTest is poolReNftId = _registerComponent(productRe, address(poolRe), "pool re"); - // token handler only becomes available after registration - vm.startPrank(poolOwner); - poolRe.approveTokenHandler(token, AmountLib.max()); - vm.stopPrank(); + _printAuthz(instanceAdmin, "instance with reinsurance setup"); // solhint-disable-next-line console.log("--- fund investor and customer"); @@ -407,7 +403,9 @@ contract ProductWithReinsuranceTest is vm.stopPrank(); vm.startPrank(investor); - token.approve(instanceReader.getTokenHandler(poolReNftId), DEFAULT_BUNDLE_CAPITALIZATION * 10**token.decimals()); + token.approve( + address(instanceReader.getTokenHandler(poolReNftId)), + DEFAULT_BUNDLE_CAPITALIZATION * 10**token.decimals()); // solhint-disable-next-line console.log("--- create bundle"); diff --git a/test/examples/composeability/ProductWithReinsuranceAuthorization.sol b/test/examples/composeability/ProductWithReinsuranceAuthorization.sol index 00c226ad3..47866ec72 100644 --- a/test/examples/composeability/ProductWithReinsuranceAuthorization.sol +++ b/test/examples/composeability/ProductWithReinsuranceAuthorization.sol @@ -1,46 +1,36 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Authorization} from "../../../contracts/authorization/Authorization.sol"; -import {BasicProduct} from "../../../contracts/product/BasicProduct.sol"; -import {PRODUCT, POOL} from "../../../contracts/type/ObjectType.sol"; import {IAccess} from "../../../contracts/authorization/IAccess.sol"; import {IInstanceLinkedComponent} from "../../../contracts/shared/IInstanceLinkedComponent.sol"; import {IProductComponent} from "../../../contracts/product/IProductComponent.sol"; + +import {Authorization} from "../../../contracts/authorization/Authorization.sol"; +import {BasicProduct} from "../../../contracts/product/BasicProduct.sol"; +import {BasicProductAuthorization} from "../../../contracts/product/BasicProductAuthorization.sol"; +import {SimpleProduct} from "../../../contracts/examples/unpermissioned/SimpleProduct.sol"; +import {PRODUCT, POOL} from "../../../contracts/type/ObjectType.sol"; import {PUBLIC_ROLE} from "../../../contracts/type/RoleId.sol"; import {RoleId} from "../../../contracts/type/RoleId.sol"; contract ProductWithReinsuranceAuthorization - is Authorization + is BasicProductAuthorization { constructor() - Authorization("ProductWithReinsurance") + BasicProductAuthorization("ProductWithReinsurance") {} - function _setupTargets() - internal - virtual override - { - uint64 index = 1; // 0 is default - _addComponentTargetWithRole(PRODUCT(), index); - } - - function _setupTargetAuthorizations() internal virtual override { - IAccess.FunctionInfo[] storage functions; - - // authorize public role (open access to any account, only allows to lock target) - functions = _authorizeForTarget(getTargetName(), PUBLIC_ROLE()); - _authorize(functions, BasicProduct.setFees.selector, "setFees"); - _authorize(functions, IInstanceLinkedComponent.withdrawFees.selector, "withdrawFees"); + super._setupTargetAuthorizations(); // authorize pool service for callback - functions = _authorizeForTarget(getTargetName(), getServiceRole(POOL())); + IAccess.FunctionInfo[] storage functions; + functions = _authorizeForTarget(getMainTargetName(), getServiceRole(POOL())); _authorize(functions, IProductComponent.processFundedClaim.selector, "processFundedClaim"); } } diff --git a/test/examples/external/ExternallyManagedPool.t.sol b/test/examples/external/ExternallyManagedPool.t.sol index d54bc5bad..65609a497 100644 --- a/test/examples/external/ExternallyManagedPool.t.sol +++ b/test/examples/external/ExternallyManagedPool.t.sol @@ -28,7 +28,10 @@ contract ExternallyManagedPoolTest is GifTest { function setUp() public override { super.setUp(); + _prepareExternallyManagedSetup(); + } + function _prepareExternallyManagedSetup() internal { _deployProduct(); // simple product setup vm.startPrank(instanceOwner); @@ -44,7 +47,6 @@ contract ExternallyManagedPoolTest is GifTest { vm.stopPrank(); } - function test_externallyManagedPoolSetUp() public { // GIVEN just setUp diff --git a/test/examples/external/ExternallyManagedPoolAuthorization.solx b/test/examples/external/ExternallyManagedPoolAuthorization.solx new file mode 100644 index 000000000..8ad970f00 --- /dev/null +++ b/test/examples/external/ExternallyManagedPoolAuthorization.solx @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {IAccess} from "../../../contracts/authorization/IAccess.sol"; +import {IInstanceLinkedComponent} from "../../../contracts/shared/IInstanceLinkedComponent.sol"; +import {IPolicyHolder} from "../../../contracts/shared/IPolicyHolder.sol"; + +import {Authorization} from "../../../contracts/authorization/Authorization.sol"; +import {BasicPool} from "../../../contracts/pool/BasicPool.sol"; +import {BasicPoolAuthorization} from "../../../contracts/pool/BasicPoolAuthorization.sol"; +import {SimplePool} from "../../../contracts/examples/unpermissioned/SimplePool.sol"; +import {IPoolComponent} from "../../../contracts/pool/IPoolComponent.sol"; +import {CLAIM, POOL, POLICY} from "../../../contracts/type/ObjectType.sol"; +import {PUBLIC_ROLE} from "../../../contracts/type/RoleId.sol"; +import {RoleId} from "../../../contracts/type/RoleId.sol"; + + +contract ExternallyManagedPoolAuthorization + is BasicPoolAuthorization +{ + constructor() + BasicPoolAuthorization("ExternallyManagedPool") + { } + + // function _setupTargetAuthorizations() + // internal + // virtual override + // { + // IAccess.FunctionInfo[] storage functions; + + // // authorize public role (open access to any account, only allows to lock target) + // functions = _authorizeForTarget(getMainTargetName(), PUBLIC_ROLE()); + // _authorize(functions, BasicPool.stake.selector, "stake"); + // _authorize(functions, BasicPool.unstake.selector, "unstake"); + // _authorize(functions, BasicPool.extend.selector, "extend"); + // _authorize(functions, BasicPool.lockBundle.selector, "lockBundle"); + // _authorize(functions, BasicPool.unlockBundle.selector, "unlockBundle"); + // _authorize(functions, BasicPool.closeBundle.selector, "closeBundle"); + // _authorize(functions, BasicPool.setBundleFee.selector, "setBundleFee"); + // _authorize(functions, BasicPool.setMaxBalanceAmount.selector, "setMaxBalanceAmount"); + // _authorize(functions, BasicPool.setFees.selector, "setFees"); + // _authorize(functions, BasicPool.stake.selector, "stake"); + // _authorize(functions, BasicPool.unstake.selector, "unstake"); + // _authorize(functions, BasicPool.extend.selector, "extend"); + // _authorize(functions, BasicPool.withdrawBundleFees.selector, "withdrawBundleFees"); + // _authorize(functions, SimplePool.approveTokenHandler.selector, "approveTokenHandler"); + // _authorize(functions, IInstanceLinkedComponent.withdrawFees.selector, "withdrawFees"); + + // // authorize claim service for callback + // functions = _authorizeForTarget(getMainTargetName(), getServiceRole(CLAIM())); + // _authorize(functions, IPoolComponent.processConfirmedClaim.selector, "processConfirmedClaim"); + // _authorize(functions, IPolicyHolder.payoutExecuted.selector, "payoutExecuted"); + // } +} + diff --git a/test/examples/external/ExternallyManagedProduct.sol b/test/examples/external/ExternallyManagedProduct.sol index 0e7b31f8f..5aba13d6e 100644 --- a/test/examples/external/ExternallyManagedProduct.sol +++ b/test/examples/external/ExternallyManagedProduct.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.20; import {Amount, AmountLib} from "../../../contracts/type/Amount.sol"; -import {BasicProductAuthorization} from "../../../contracts/product/BasicProductAuthorization.sol"; +import {BasicProductAuthorization} from "../../../contracts/product/BasicProductAuthorization.sol"; +// import {ExternallyManagedProductAuthorization} from "./ExternallyManagedProductAuthorization.sol"; import {SimpleProduct} from "../../../contracts/examples/unpermissioned/SimpleProduct.sol"; import {ClaimId} from "../../../contracts/type/ClaimId.sol"; import {FeeLib} from "../../../contracts/type/Fee.sol"; diff --git a/test/examples/external/ExternallyManagedProductAuthorization.solx b/test/examples/external/ExternallyManagedProductAuthorization.solx new file mode 100644 index 000000000..fc0a41594 --- /dev/null +++ b/test/examples/external/ExternallyManagedProductAuthorization.solx @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {Authorization} from "../../../contracts/authorization/Authorization.sol"; +import {BasicProduct} from "../../../contracts/product/BasicProduct.sol"; +import {BasicProductAuthorization} from "../../../contracts/product/BasicProductAuthorization.sol"; +import {SimpleProduct} from "../../../contracts/examples/unpermissioned/SimpleProduct.sol"; +import {PRODUCT, POOL} from "../../../contracts/type/ObjectType.sol"; +import {IAccess} from "../../../contracts/authorization/IAccess.sol"; +import {IInstanceLinkedComponent} from "../../../contracts/shared/IInstanceLinkedComponent.sol"; +import {IProductComponent} from "../../../contracts/product/IProductComponent.sol"; +import {PUBLIC_ROLE} from "../../../contracts/type/RoleId.sol"; +import {RoleId} from "../../../contracts/type/RoleId.sol"; + + +contract ExternallyManagedProductAuthorization + is BasicProductAuthorization +{ + constructor() + BasicProductAuthorization("ExternallyManagedProduct") + { } +} + diff --git a/test/examples/fire/FireTestBase.t.sol b/test/examples/fire/FireTestBase.t.sol index 29c3b6da1..02a3ced21 100644 --- a/test/examples/fire/FireTestBase.t.sol +++ b/test/examples/fire/FireTestBase.t.sol @@ -64,10 +64,6 @@ contract FireTestBase is GifTest { vm.startPrank(instanceOwner); fireProductNftId = instance.registerProduct(address(fireProduct)); vm.stopPrank(); - - vm.startPrank(fireProduct.getOwner()); - fireProduct.approveTokenHandler(fireUSD, AmountLib.max()); - vm.stopPrank(); } function _deployFirePool() internal { @@ -83,11 +79,6 @@ contract FireTestBase is GifTest { vm.stopPrank(); firePoolNftId = _registerComponent(fireProductOwner, fireProduct, address(firePool), "firePool"); - - // token handler only becomes available after registration - vm.startPrank(firePool.getOwner()); - firePool.approveTokenHandler(fireUSD, AmountLib.max()); - vm.stopPrank(); } function _initialFundAccounts() internal { diff --git a/test/instance/InstanceReader.t.sol b/test/instance/InstanceReader.t.sol new file mode 100644 index 000000000..f465ab5e5 --- /dev/null +++ b/test/instance/InstanceReader.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: APACHE-2.0 +pragma solidity ^0.8.20; + +import {IAccessManaged} from "@openzeppelin/contracts/access/manager/IAccessManaged.sol"; + +import {Fee, FeeLib} from "../../contracts/type/Fee.sol"; +import {GifTest} from "../base/GifTest.sol"; +import {IAccessAdmin} from "../../contracts/authorization/IAccessAdmin.sol"; +import {IInstance} from "../../contracts/instance/IInstance.sol"; +import {IInstanceService} from "../../contracts/instance/IInstanceService.sol"; +import {InstanceAdmin} from "../../contracts/instance/InstanceAdmin.sol"; +import {InstanceReader} from "../../contracts/instance/InstanceReader.sol"; +import {NftId} from "../../contracts/type/NftId.sol"; +import {UFixed, UFixedLib} from "../../contracts/type/UFixed.sol"; + +contract TestInstance is GifTest { + + function setUp() public override { + super.setUp(); + } + + function test_instanceReader() public { + // GIVEN just set up + // THEN + assertEq(address(instanceReader.getRegistry()), address(registry), "unexpected registry address"); + assertEq(address(instanceReader.getInstance()), address(instance), "unexpected instance address"); + assertEq(instanceReader.getInstanceNftId().toInt(), instanceNftId.toInt(), "unexpected instance nft id"); + } +} diff --git a/test/shared/TokenHandler.t.sol b/test/shared/TokenHandler.t.sol index f092e71a9..751d370e7 100644 --- a/test/shared/TokenHandler.t.sol +++ b/test/shared/TokenHandler.t.sol @@ -34,10 +34,6 @@ contract TokenHandlerEx is TokenHandlerBase { _setWallet(newWallet); } - function pullAndPushToken(address from, Amount pullAmount, address to1, Amount pushAmount1, address to2, Amount pushAmount2) external { - _pullAndPushToken(from, pullAmount, to1, pushAmount1, to2, pushAmount2); - } - function pullToken(address from, Amount amount) external { _pullToken(from, amount); } @@ -235,213 +231,6 @@ contract TokenHandlerTest is GifTest { tokenHandlerEx.pullToken(sender, amount); } - function test_tokenHandlerPullAndPushHappyCase() public { - // GIVEN - uint256 amountInt = 100; - ( - Amount pullAmount, - Amount amount, - address sender, - address wallet, - address recipient1, - address recipient2 - ) = _preparePullAndPushTokenTest(amountInt); - - // THEN - vm.expectEmit(); - emit TokenHandlerBase.LogTokenHandlerTokenTransfer(address(dip), sender, wallet, pullAmount); - emit TokenHandlerBase.LogTokenHandlerTokenTransfer(address(dip), wallet, recipient1, amount); - emit TokenHandlerBase.LogTokenHandlerTokenTransfer(address(dip), wallet, recipient2, amount); - - // WHEN - tokenHandlerEx.pullAndPushToken(sender, pullAmount, recipient1, amount, recipient2, amount); - - // THEN - assertEq(dip.balanceOf(sender), 0, "unexpected sender balance" ); - assertEq(dip.balanceOf(wallet), amountInt, "unexpected wallet balance"); - assertEq(dip.balanceOf(recipient1), amountInt, "unexpected recipient1 balance"); - assertEq(dip.balanceOf(recipient2), amountInt, "unexpected recipient2 balance"); - } - - function test_tokenHandlerPullAndPushRecipient1Zero() public { - uint256 amountInt = 100; - ( - Amount pullAmount, - Amount amount, - address sender, - address wallet, - address recipient1, - address recipient2 - ) = _preparePullAndPushTokenTest(amountInt); - - // THEN - vm.expectEmit(); - emit TokenHandlerBase.LogTokenHandlerTokenTransfer(address(dip), sender, wallet, pullAmount); - emit TokenHandlerBase.LogTokenHandlerTokenTransfer(address(dip), wallet, recipient2, amount); - - // WHEN - tokenHandlerEx.pullAndPushToken(sender, pullAmount, recipient1, amountZero, recipient2, amount); - - // THEN - assertEq(dip.balanceOf(sender), 0, "unexpected sender balance" ); - assertEq(dip.balanceOf(wallet), 2 * amountInt, "unexpected wallet balance"); - assertEq(dip.balanceOf(recipient1), 0, "unexpected recipient1 balance"); - assertEq(dip.balanceOf(recipient2), amountInt, "unexpected recipient2 balance"); - } - - - function test_tokenHandlerPullAndPushRecipient2Zero() public { - // GIVEN - uint256 amountInt = 100; - ( - Amount pullAmount, - Amount amount, - address sender, - address wallet, - address recipient1, - address recipient2 - ) = _preparePullAndPushTokenTest(amountInt); - - // THEN - vm.expectEmit(); - emit TokenHandlerBase.LogTokenHandlerTokenTransfer(address(dip), sender, wallet, pullAmount); - emit TokenHandlerBase.LogTokenHandlerTokenTransfer(address(dip), wallet, recipient1, amount); - - // WHEN - tokenHandlerEx.pullAndPushToken(sender, pullAmount, recipient1, amount, recipient2, amountZero); - - // THEN - assertEq(dip.balanceOf(sender), 0, "unexpected sender balance" ); - assertEq(dip.balanceOf(wallet), 2 * amountInt, "unexpected wallet balance"); - assertEq(dip.balanceOf(recipient1), amountInt, "unexpected recipient1 balance"); - assertEq(dip.balanceOf(recipient2), 0, "unexpected recipient2 balance"); - } - - function test_tokenHandlerPullAndPushWalletZero() public { - // GIVEN - uint256 amountInt = 100; - Amount amount1 = AmountLib.toAmount(2 * amountInt); - ( - Amount pullAmount, - Amount amount, - address sender, - address wallet, - address recipient1, - address recipient2 - ) = _preparePullAndPushTokenTest(amountInt); - - // THEN - vm.expectEmit(); - emit TokenHandlerBase.LogTokenHandlerTokenTransfer(address(dip), sender, wallet, pullAmount); - emit TokenHandlerBase.LogTokenHandlerTokenTransfer(address(dip), wallet, recipient1, amount1); - emit TokenHandlerBase.LogTokenHandlerTokenTransfer(address(dip), wallet, recipient2, amount); - - // WHEN - tokenHandlerEx.pullAndPushToken(sender, pullAmount, recipient1, amount1, recipient2, amount); - - // THEN - assertEq(dip.balanceOf(sender), 0, "unexpected sender balance" ); - assertEq(dip.balanceOf(wallet), 0, "unexpected wallet balance"); - assertEq(dip.balanceOf(recipient1), 2 * amountInt, "unexpected recipient1 balance"); - assertEq(dip.balanceOf(recipient2), amountInt, "unexpected recipient2 balance"); - } - - function test_tokenHandlerPullAndPushWalletAllowanceTooSmall() public { - // GIVEN - uint256 amountInt = 100; - ( - Amount pullAmount, - Amount amount, - address sender, - address wallet, - address recipient1, - address recipient2 - ) = _preparePullAndPushTokenTest(amountInt); - - _approveTokenHandlerFor(sender, amountInt * 2); - - vm.expectRevert( - abi.encodeWithSelector( - TokenHandlerBase.ErrorTokenHandlerAllowanceTooSmall.selector, - address(dip), - sender, - address(tokenHandlerEx), - 200, - 300)); - - // WHEN - tokenHandlerEx.pullAndPushToken(sender, pullAmount, recipient1, amount, recipient2, amount); - } - - function test_tokenHandlerPullAndPushWalletWalletsNotSame() public { - // GIVEN - uint256 amountInt = 100; - ( - Amount pullAmount, - Amount amount, - address sender, - address wallet, - address recipient1, - address recipient2 - ) = _preparePullAndPushTokenTest(amountInt); - - // WHEN + THEN 1 - vm.expectRevert( - abi.encodeWithSelector( - TokenHandlerBase.ErrorTokenHandlerWalletsNotDistinct.selector, - wallet, - wallet, - recipient2)); - - tokenHandlerEx.pullAndPushToken(sender, pullAmount, wallet, amount, recipient2, amount); - - // WHEN + THEN 2 - vm.expectRevert( - abi.encodeWithSelector( - TokenHandlerBase.ErrorTokenHandlerWalletsNotDistinct.selector, - wallet, - recipient1, - recipient1)); - - tokenHandlerEx.pullAndPushToken(sender, pullAmount, recipient1, amount, recipient1, amount); - - // WHEN + THEN 3 - vm.expectRevert( - abi.encodeWithSelector( - TokenHandlerBase.ErrorTokenHandlerWalletsNotDistinct.selector, - wallet, - recipient2, - recipient2)); - - tokenHandlerEx.pullAndPushToken(sender, pullAmount, recipient2, amount, recipient2, amount); - } - - function test_tokenHandlerPullAndPushPushAmountTooLarge() public { - // GIVEN - uint256 amountInt = 100; - Amount amountTooLarge = AmountLib.toAmount(3 * amountInt); - Amount pushAmount = AmountLib.toAmount(4 * amountInt); - ( - Amount pullAmount, - Amount amount, - address sender, - address wallet, - address recipient1, - address recipient2 - ) = _preparePullAndPushTokenTest(amountInt); - - _approveTokenHandlerFor(sender, amountInt * 2); - - vm.expectRevert( - abi.encodeWithSelector( - TokenHandlerBase.ErrorTokenHandlerPushAmountsTooLarge.selector, - pushAmount, - pullAmount)); - - // WHEN - tokenHandlerEx.pullAndPushToken(sender, pullAmount, recipient1, amountTooLarge, recipient2, amount); - } - function test_tokenHandlerPushToken() public { // GIVEN uint256 amountInt = 100;