From eb09eb2448568c78e1e94df682373a23afce623c Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Mon, 7 Nov 2022 08:29:37 +0000 Subject: [PATCH 1/4] next step for depeg --- contracts/examples/DepegProduct.sol | 129 +++++++ contracts/examples/DepegRiskpool.sol | 210 ++++++++++++ scripts/depeg_product.py | 275 +++++++++++++++ scripts/deploy_depeg.py | 484 +++++++++++++++++++++++++++ 4 files changed, 1098 insertions(+) create mode 100644 contracts/examples/DepegProduct.sol create mode 100644 contracts/examples/DepegRiskpool.sol create mode 100644 scripts/depeg_product.py create mode 100644 scripts/deploy_depeg.py diff --git a/contracts/examples/DepegProduct.sol b/contracts/examples/DepegProduct.sol new file mode 100644 index 0000000..71fa05e --- /dev/null +++ b/contracts/examples/DepegProduct.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.2; + +import "@etherisc/gif-interface/contracts/components/IComponent.sol"; +import "@etherisc/gif-interface/contracts/components/Product.sol"; + +import "./DepegRiskpool.sol"; + +contract DepegProduct is + Product +{ + + bytes32 public constant NAME = "DepegProduct"; + bytes32 public constant VERSION = "0.1"; + bytes32 public constant POLICY_FLOW = "PolicyDefaultFlow"; + + bytes32 [] private _applications; // useful for debugging, might need to get rid of this + bytes32 [] private _policies; + + event LogDepegApplicationCreated(bytes32 policyId, address policyHolder, uint256 premiumAmount, uint256 sumInsuredAmount); + event LogDepegPolicyCreated(bytes32 policyId, address policyHolder, uint256 premiumAmount, uint256 sumInsuredAmount); + event LogDepegPolicyProcessed(bytes32 policyId); + + event LogDepegOracleTriggered(uint256 exchangeRate); + + DepegRiskpool private _riskPool; + + constructor( + bytes32 productName, + address registry, + address token, + uint256 riskpoolId + ) + Product(productName, token, POLICY_FLOW, riskpoolId, registry) + { + IComponent poolComponent = _instanceService.getComponent(riskpoolId); + address poolAddress = address(poolComponent); + _riskPool = DepegRiskpool(poolAddress); + } + + + function applyForPolicy( + uint256 sumInsured, + uint256 duration, + uint256 maxPremium + ) + external + returns(bytes32 processId) + { + address policyHolder = msg.sender; + bytes memory metaData = ""; + bytes memory applicationData = _riskPool.encodeApplicationParameterAsData( + duration, + maxPremium + ); + + // TODO proper mechanism to decide premium + // maybe hook after policy creation with adjustpremiumsuminsured? + uint256 premium = maxPremium; + + processId = _newApplication( + policyHolder, + premium, + sumInsured, + metaData, + applicationData); + + _applications.push(processId); + + emit LogDepegApplicationCreated( + processId, + policyHolder, + premium, + sumInsured); + + bool success = _underwrite(processId); + + if (success) { + _policies.push(processId); + + emit LogDepegPolicyCreated( + processId, + policyHolder, + premium, + sumInsured); + } + } + + function triggerOracle() + external + { + + uint256 exchangeRate = 10**6; + + emit LogDepegOracleTriggered( + exchangeRate + ); + } + + function processPolicy(bytes32 processId) + public + { + + _expire(processId); + _close(processId); + + emit LogDepegPolicyProcessed(processId); + } + + function applications() external view returns(uint256 applicationCount) { + return _applications.length; + } + + function getApplicationId(uint256 applicationIdx) external view returns(bytes32 processId) { + return _applications[applicationIdx]; + } + + function policies() external view returns(uint256 policyCount) { + return _policies.length; + } + + function getPolicyId(uint256 policyIdx) external view returns(bytes32 processId) { + return _policies[policyIdx]; + } + + function getApplicationDataStructure() external override pure returns(string memory dataStructure) { + return "TODO"; + } +} \ No newline at end of file diff --git a/contracts/examples/DepegRiskpool.sol b/contracts/examples/DepegRiskpool.sol new file mode 100644 index 0000000..e6945e4 --- /dev/null +++ b/contracts/examples/DepegRiskpool.sol @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.2; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +import "@etherisc/gif-interface/contracts/components/BasicRiskpool.sol"; +import "@etherisc/gif-interface/contracts/modules/IBundle.sol"; +import "@etherisc/gif-interface/contracts/modules/IPolicy.sol"; + +contract DepegRiskpool is + BasicRiskpool +{ + + uint256 public constant USD_RISK_CAPITAL_CAP = 1 * 10**6; + + uint256 public constant MAX_BUNDLE_DURATION = 180 * 24 * 3600; + uint256 public constant MAX_POLICY_DURATION = 180 * 24 * 3600; + uint256 public constant ONE_YEAR_DURATION = 365 * 24 * 3600; + + uint256 public constant APR_100_PERCENTAGE = 10**6; + uint256 public constant MAX_APR = APR_100_PERCENTAGE / 5; + + uint256 private _poolRiskCapitalCap; + uint256 private _bundleRiskCapitalCap; + + + constructor( + bytes32 name, + uint256 sumOfSumInsuredCap, + address erc20Token, + address wallet, + address registry + ) + BasicRiskpool(name, getFullCollateralizationLevel(), sumOfSumInsuredCap, erc20Token, wallet, registry) + { + ERC20 token = ERC20(erc20Token); + _poolRiskCapitalCap = USD_RISK_CAPITAL_CAP * 10 ** token.decimals(); + + // HACK this needs to be determined according to max active bundles + // setMaxActiveBundles in Riskpool needs to become virtual. alternatively + // Riskpool could call a virtual postprocessing hook + _bundleRiskCapitalCap = _poolRiskCapitalCap / 10; + + require(sumOfSumInsuredCap <= _poolRiskCapitalCap, "ERROR:DRP-010:SUM_OF_SUM_INSURED_CAP_TOO_LARGE"); + require(sumOfSumInsuredCap > 0, "ERROR:DRP-011:SUM_OF_SUM_INSURED_CAP_ZERO"); + + } + + function createBundle( + uint256 policyMinSumInsured, + uint256 policyMaxSumInsured, + uint256 policyMinDuration, + uint256 policyMaxDuration, + uint256 annualPercentageReturn, + uint256 initialAmount + ) + public + returns(uint256 bundleId) + { + require(policyMaxSumInsured <= _bundleRiskCapitalCap, "ERROR:DRP-020:MAX_SUM_INSURED_TOO_LARGE"); + require(policyMaxSumInsured > 0, "ERROR:DRP-020:MAX_SUM_INSURED_ZERO"); + require(policyMinSumInsured <= policyMaxSumInsured, "ERROR:DRP-020:MIN_SUM_INSURED_TOO_LARGE"); + + require(policyMaxDuration <= MAX_POLICY_DURATION, "ERROR:DRP-020:POLICY_MAX_DURATION_TOO_LARGE"); + require(policyMaxDuration > 0, "ERROR:DRP-020:POLICY_MAX_DURATION_ZERO"); + require(policyMinDuration <= policyMaxDuration, "ERROR:DRP-020:POLICY_MIN_DURATION_TOO_LARGE"); + + require(annualPercentageReturn <= MAX_APR, "ERROR:DRP-020:APR_TOO_LARGE"); + require(annualPercentageReturn > 0, "ERROR:DRP-020:APR_ZERO"); + + require(initialAmount <= _bundleRiskCapitalCap, "ERROR:DRP-020:RISK_CAPITAL_TOO_LARGE"); + + bytes memory filter = encodeBundleParamsAsFilter( + policyMinSumInsured, + policyMaxSumInsured, + policyMinDuration, + policyMaxDuration, + annualPercentageReturn + ); + + bundleId = super.createBundle(filter, initialAmount); + } + + // TODO s + // - add application params to depeg product contract + // - get toy works up as quickly as possible + // + .1 create application + // + .2 figure out if there's a matching bundle + // + .3 add function that let's user query conditions based on active bundles + // TODO function defined in base class Riskpool -> needs to be virtual! + function getFilterDataStructureTemp() external pure returns(string memory) { + return "(uint256 minSumInsured,uint256 maxSumInsured,uint256 minDuration,uint256 maxDuration,uint256 annualPercentageReturn)"; + } + + function encodeBundleParamsAsFilter( + uint256 minSumInsured, + uint256 maxSumInsured, + uint256 minDuration, + uint256 maxDuration, + uint256 annualPercentageReturn + ) + public pure + returns (bytes memory filter) + { + filter = abi.encode( + minSumInsured, + maxSumInsured, + minDuration, + maxDuration, + annualPercentageReturn + ); + } + + function decodeBundleParamsFromFilter( + bytes calldata filter + ) + public pure + returns ( + uint256 minSumInsured, + uint256 maxSumInsured, + uint256 minDuration, + uint256 maxDuration, + uint256 annualPercentageReturn + ) + { + ( + minSumInsured, + maxSumInsured, + minDuration, + maxDuration, + annualPercentageReturn + ) = abi.decode(filter, (uint256, uint256, uint256, uint256, uint256)); + } + + + function encodeApplicationParameterAsData( + uint256 duration, + uint256 maxPremium + ) + public pure + returns (bytes memory data) + { + data = abi.encode( + duration, + maxPremium + ); + } + + + function decodeApplicationParameterFromData( + bytes calldata data + ) + public pure + returns ( + uint256 duration, + uint256 maxPremium + ) + { + ( + duration, + maxPremium + ) = abi.decode(data, (uint256, uint256)); + } + + + function bundleMatchesApplication( + IBundle.Bundle calldata bundle, + IPolicy.Application calldata application + ) + public override + pure + returns(bool isMatching) + { + ( + uint256 minSumInsured, + uint256 maxSumInsured, + uint256 minDuration, + uint256 maxDuration, + uint256 annualPercentageReturn + ) = decodeBundleParamsFromFilter(bundle.filter); + + ( + uint256 duration, + uint256 maxPremium + ) = decodeApplicationParameterFromData(application.data); + + uint256 sumInsured = application.sumInsuredAmount; + + if(sumInsured < minSumInsured) { return false; } + if(sumInsured > maxSumInsured) { return false; } + + if(duration < minDuration) { return false; } + if(duration > maxDuration) { return false; } + + uint256 policyDurationReturn = annualPercentageReturn * duration / ONE_YEAR_DURATION; + uint256 premium = sumInsured * policyDurationReturn / APR_100_PERCENTAGE; + + if(premium > maxPremium) { return false; } + + return true; + } + + function getBundleRiskCapitalCap() public view returns (uint256 bundleRiskCapitalCap) { + return _bundleRiskCapitalCap; + } + + function getApr100PercentLevel() public pure returns(uint256 apr100PercentLevel) { + return APR_100_PERCENTAGE; + } +} \ No newline at end of file diff --git a/scripts/depeg_product.py b/scripts/depeg_product.py new file mode 100644 index 0000000..0974b00 --- /dev/null +++ b/scripts/depeg_product.py @@ -0,0 +1,275 @@ +from web3 import Web3 + +from brownie import Contract +from brownie.convert import to_bytes +from brownie.network import accounts +from brownie.network.account import Account + +from brownie import ( + Wei, + Contract, + PolicyController, + ComponentOwnerService, + InstanceOperatorService, + DepegProduct, + DepegRiskpool, +) + +from scripts.util import ( + get_account, + encode_function_data, + # s2h, + s2b32, + deployGifModule, + deployGifService, +) + +from scripts.instance import GifInstance + + +RISKPOOL_NAME = 'DepgeRiskpool' +PRODUCT_NAME = 'DepegProduct' + +class GifDepegRiskpool(object): + + def __init__(self, + instance: GifInstance, + erc20Token: Account, + riskpoolKeeper: Account, + riskpoolWallet: Account, + investor: Account, + collateralization:int, + name=RISKPOOL_NAME, + publishSource=False + ): + instanceService = instance.getInstanceService() + instanceOperatorService = instance.getInstanceOperatorService() + componentOwnerService = instance.getComponentOwnerService() + riskpoolService = instance.getRiskpoolService() + + print('------ setting up riskpool ------') + + riskpoolKeeperRole = instanceService.getRiskpoolKeeperRole() + print('1) grant riskpool keeper role {} to riskpool keeper {}'.format( + riskpoolKeeperRole, riskpoolKeeper)) + + instanceOperatorService.grantRole( + riskpoolKeeperRole, + riskpoolKeeper, + {'from': instance.getOwner()}) + + print('2) deploy riskpool by riskpool keeper {}'.format( + riskpoolKeeper)) + + sumOfSumInsuredCap = 1000000 + self.riskpool = DepegRiskpool.deploy( + s2b32(name), + sumOfSumInsuredCap, + erc20Token, + riskpoolWallet, + instance.getRegistry(), + {'from': riskpoolKeeper}, + publish_source=publishSource) + + print('3) riskpool {} proposing to instance by riskpool keeper {}'.format( + self.riskpool, riskpoolKeeper)) + + componentOwnerService.propose( + self.riskpool, + {'from': riskpoolKeeper}) + + print('4) approval of riskpool id {} by instance operator {}'.format( + self.riskpool.getId(), instance.getOwner())) + + instanceOperatorService.approve( + self.riskpool.getId(), + {'from': instance.getOwner()}) + + maxActiveBundles = 10 + print('5) set max number of bundles to {} by riskpool keeper {}'.format( + maxActiveBundles, riskpoolKeeper)) + + self.riskpool.setMaximumNumberOfActiveBundles( + maxActiveBundles, + {'from': riskpoolKeeper}) + + print('6) riskpool wallet {} set for riskpool id {} by instance operator {}'.format( + riskpoolWallet, self.riskpool.getId(), instance.getOwner())) + + instanceOperatorService.setRiskpoolWallet( + self.riskpool.getId(), + riskpoolWallet, + {'from': instance.getOwner()}) + + # 7) setup capital fees + fixedFee = 42 + fractionalFee = instanceService.getFeeFractionFullUnit() / 20 # corresponds to 5% + print('7) creating capital fee spec (fixed: {}, fractional: {}) for riskpool id {} by instance operator {}'.format( + fixedFee, fractionalFee, self.riskpool.getId(), instance.getOwner())) + + feeSpec = instanceOperatorService.createFeeSpecification( + self.riskpool.getId(), + fixedFee, + fractionalFee, + b'', + {'from': instance.getOwner()}) + + print('8) setting capital fee spec by instance operator {}'.format( + instance.getOwner())) + + instanceOperatorService.setCapitalFees( + feeSpec, + {'from': instance.getOwner()}) + + def getId(self) -> int: + return self.riskpool.getId() + + def getContract(self) -> DepegRiskpool: + return self.riskpool + + +# TODO adapt after building product contract +class GifDepegProduct(object): + + def __init__(self, + instance: GifInstance, + erc20Token, + productOwner: Account, + riskpool: GifDepegRiskpool, + name=PRODUCT_NAME, + publishSource=False + ): + self.policy = instance.getPolicy() + self.riskpool = riskpool + self.token = erc20Token + + instanceService = instance.getInstanceService() + instanceOperatorService = instance.getInstanceOperatorService() + componentOwnerService = instance.getComponentOwnerService() + registry = instance.getRegistry() + + print('------ setting up product ------') + + productOwnerRole = instanceService.getProductOwnerRole() + print('1) grant product owner role {} to product owner {}'.format( + productOwnerRole, productOwner)) + + instanceOperatorService.grantRole( + productOwnerRole, + productOwner, + {'from': instance.getOwner()}) + + print('2) deploy product by product owner {}'.format( + productOwner)) + + self.product = DepegProduct.deploy( + s2b32(name), + registry, + erc20Token.address, + riskpool.getId(), + {'from': productOwner}, + publish_source=publishSource) + + print('3) product {} proposing to instance by product owner {}'.format( + self.product, productOwner)) + + componentOwnerService.propose( + self.product, + {'from': productOwner}) + + print('4) approval of product id {} by instance operator {}'.format( + self.product.getId(), instance.getOwner())) + + instanceOperatorService.approve( + self.product.getId(), + {'from': instance.getOwner()}) + + print('5) setting erc20 product token {} for product id {} by instance operator {}'.format( + erc20Token, self.product.getId(), instance.getOwner())) + + instanceOperatorService.setProductToken( + self.product.getId(), + erc20Token, + {'from': instance.getOwner()}) + + fixedFee = 3 + fractionalFee = instanceService.getFeeFractionFullUnit() / 10 # corresponds to 10% + print('6) creating premium fee spec (fixed: {}, fractional: {}) for product id {} by instance operator {}'.format( + fixedFee, fractionalFee, self.product.getId(), instance.getOwner())) + + feeSpec = instanceOperatorService.createFeeSpecification( + self.product.getId(), + fixedFee, + fractionalFee, + b'', + {'from': instance.getOwner()}) + + print('7) setting premium fee spec by instance operator {}'.format( + instance.getOwner())) + + instanceOperatorService.setPremiumFees( + feeSpec, + {'from': instance.getOwner()}) + + + def getId(self) -> int: + return self.product.getId() + + def getToken(self): + return self.token + + def getRiskpool(self) -> GifDepegRiskpool: + return self.riskpool + + def getContract(self) -> DepegProduct: + return self.product + + def getPolicy(self, policyId: str): + return self.policy.getPolicy(policyId) + + +class GifDepegProductComplete(object): + + def __init__(self, + instance: GifInstance, + productOwner: Account, + investor: Account, + erc20Token: Account, + riskpoolKeeper: Account, + riskpoolWallet: Account, + baseName='Depeg', + publishSource=False + ): + instanceService = instance.getInstanceService() + instanceOperatorService = instance.getInstanceOperatorService() + componentOwnerService = instance.getComponentOwnerService() + registry = instance.getRegistry() + + self.token = erc20Token + + self.riskpool = GifDepegRiskpool( + instance, + erc20Token, + riskpoolKeeper, + riskpoolWallet, + investor, + instanceService.getFullCollateralizationLevel(), + '{}Riskpool'.format(baseName), + publishSource) + + self.product = GifDepegProduct( + instance, + erc20Token, + productOwner, + self.riskpool, + '{}Product'.format(baseName), + publishSource) + + def getToken(self): + return self.token + + def getRiskpool(self) -> GifDepegRiskpool: + return self.riskpool + + def getProduct(self) -> GifDepegProduct: + return self.product diff --git a/scripts/deploy_depeg.py b/scripts/deploy_depeg.py new file mode 100644 index 0000000..ae8382c --- /dev/null +++ b/scripts/deploy_depeg.py @@ -0,0 +1,484 @@ +from brownie import web3 + +from brownie.network import accounts +from brownie.network.account import Account + +from brownie import ( + interface, + network, + TestCoin, + InstanceService, + InstanceOperatorService, + ComponentOwnerService, + DepegProduct, + DepegRiskpool +) + +from scripts.depeg_product import GifDepegProductComplete +from scripts.instance import GifInstance +from scripts.util import contract_from_address, s2b32 + +INSTANCE_OPERATOR = 'instanceOperator' +INSTANCE_WALLET = 'instanceWallet' +RISKPOOL_KEEPER = 'riskpoolKeeper' +RISKPOOL_WALLET = 'riskpoolWallet' +INVESTOR = 'investor' +PRODUCT_OWNER = 'productOwner' +CUSTOMER1 = 'customer1' +CUSTOMER2 = 'customer2' + +ERC20_TOKEM = 'erc20Token' +INSTANCE = 'instance' +INSTANCE_SERVICE = 'instanceService' +INSTANCE_OPERATOR_SERVICE = 'instanceOperatorService' +COMPONENT_OWNER_SERVICE = 'componentOwnerService' +PRODUCT = 'product' +RISKPOOL = 'riskpool' + +PROCESS_ID1 = 'processId1' +PROCESS_ID2 = 'processId2' + +GAS_PRICE = web3.eth.gas_price +GAS_PRICE_SAFETY_FACTOR = 1.25 + +GAS_S = 2000000 +GAS_M = 3 * GAS_S +GAS_L = 10 * GAS_M + +REQUIRED_FUNDS_S = int(GAS_PRICE * GAS_PRICE_SAFETY_FACTOR * GAS_S) +REQUIRED_FUNDS_M = int(GAS_PRICE * GAS_PRICE_SAFETY_FACTOR * GAS_M) +REQUIRED_FUNDS_L = int(GAS_PRICE * GAS_PRICE_SAFETY_FACTOR * GAS_L) + +INITIAL_ERC20_BUNDLE_FUNDING = 100000 + +REQUIRED_FUNDS = { + INSTANCE_OPERATOR: REQUIRED_FUNDS_L, + INSTANCE_WALLET: REQUIRED_FUNDS_S, + PRODUCT_OWNER: REQUIRED_FUNDS_M, + RISKPOOL_KEEPER: REQUIRED_FUNDS_M, + RISKPOOL_WALLET: REQUIRED_FUNDS_S, + INVESTOR: REQUIRED_FUNDS_S, + CUSTOMER1: REQUIRED_FUNDS_S, + CUSTOMER2: REQUIRED_FUNDS_S, +} + + +def stakeholders_accounts_ganache(): + # define stakeholder accounts + instanceOperator=accounts[0] + instanceWallet=accounts[1] + riskpoolKeeper=accounts[2] + riskpoolWallet=accounts[3] + investor=accounts[4] + productOwner=accounts[5] + customer=accounts[6] + customer2=accounts[7] + + return { + INSTANCE_OPERATOR: instanceOperator, + INSTANCE_WALLET: instanceWallet, + RISKPOOL_KEEPER: riskpoolKeeper, + RISKPOOL_WALLET: riskpoolWallet, + INVESTOR: investor, + PRODUCT_OWNER: productOwner, + CUSTOMER1: customer, + CUSTOMER2: customer2, + } + + +def check_funds(stakeholders_accounts, erc20_token): + _print_constants() + + a = stakeholders_accounts + + native_token_success = True + fundsMissing = 0 + for accountName, requiredAmount in REQUIRED_FUNDS.items(): + if a[accountName].balance() >= REQUIRED_FUNDS[accountName]: + print('{} funding ok'.format(accountName)) + else: + fundsMissing += REQUIRED_FUNDS[accountName] - a[accountName].balance() + print('{} needs {} but has {}'.format( + accountName, + REQUIRED_FUNDS[accountName], + a[accountName].balance() + )) + + if fundsMissing > 0: + native_token_success = False + + if a[INSTANCE_OPERATOR].balance() >= REQUIRED_FUNDS[INSTANCE_OPERATOR] + fundsMissing: + print('{} sufficiently funded with native token to cover missing funds'.format(INSTANCE_OPERATOR)) + else: + additionalFunds = REQUIRED_FUNDS[INSTANCE_OPERATOR] + fundsMissing - a[INSTANCE_OPERATOR].balance() + print('{} needs additional funding of {} ({} ETH) with native token to cover missing funds'.format( + INSTANCE_OPERATOR, + additionalFunds, + additionalFunds/10**18 + )) + else: + native_token_success = True + + erc20_success = False + if erc20_token: + erc20_success = check_erc20_funds(a, erc20_token) + else: + print('WARNING: no erc20 token defined, skipping erc20 funds checking') + + return native_token_success & erc20_success + + +def check_erc20_funds(a, erc20_token): + if erc20_token.balanceOf(a[INSTANCE_OPERATOR]) >= INITIAL_ERC20_BUNDLE_FUNDING: + print('{} ERC20 funding ok'.format(INSTANCE_OPERATOR)) + return True + else: + print('{} needs additional ERC20 funding of {} to cover missing funds'.format( + INSTANCE_OPERATOR, + INITIAL_ERC20_BUNDLE_FUNDING - erc20_token.balanceOf(a[INSTANCE_OPERATOR]))) + print('IMPORTANT: manual transfer needed to ensure ERC20 funding') + return False + + +def amend_funds(stakeholders_accounts): + a = stakeholders_accounts + for accountName, requiredAmount in REQUIRED_FUNDS.items(): + if a[accountName].balance() < REQUIRED_FUNDS[accountName]: + missingAmount = REQUIRED_FUNDS[accountName] - a[accountName].balance() + print('funding {} with {}'.format(accountName, missingAmount)) + a[INSTANCE_OPERATOR].transfer(a[accountName], missingAmount) + + print('re-run check_funds() to verify funding before deploy') + + +def _print_constants(): + print('chain id: {}'.format(web3.eth.chain_id)) + print('gas price [Mwei]: {}'.format(GAS_PRICE/10**6)) + print('gas price safety factor: {}'.format(GAS_PRICE_SAFETY_FACTOR)) + + print('gas S: {}'.format(GAS_S)) + print('gas M: {}'.format(GAS_M)) + print('gas L: {}'.format(GAS_L)) + + print('required S [ETH]: {}'.format(REQUIRED_FUNDS_S / 10**18)) + print('required M [ETH]: {}'.format(REQUIRED_FUNDS_M / 10**18)) + print('required L [ETH]: {}'.format(REQUIRED_FUNDS_L / 10**18)) + + +def _get_balances(stakeholders_accounts): + balance = {} + + for accountName, account in stakeholders_accounts.items(): + balance[accountName] = account.balance() + + return balance + + +def _get_balances_delta(balances_before, balances_after): + balance_delta = { 'total': 0 } + + for accountName, account in balances_before.items(): + balance_delta[accountName] = balances_before[accountName] - balances_after[accountName] + balance_delta['total'] += balance_delta[accountName] + + return balance_delta + + +def _pretty_print_delta(title, balances_delta): + + print('--- {} ---'.format(title)) + + gasPrice = network.gas_price() + print('gas price: {}'.format(gasPrice)) + + for accountName, amount in balances_delta.items(): + if accountName != 'total': + if gasPrice != 'auto': + print('account {}: gas {}'.format(accountName, amount / gasPrice)) + else: + print('account {}: amount {}'.format(accountName, amount)) + + print('-----------------------------') + if gasPrice != 'auto': + print('account total: gas {}'.format(balances_delta['total'] / gasPrice)) + else: + print('account total: amount {}'.format(balances_delta['total'])) + print('=============================') + + +def deploy_setup_including_token( + stakeholders_accounts, + erc20_token, +): + return deploy(stakeholders_accounts, erc20_token, None) + + +def deploy( + stakeholders_accounts, + erc20_token, + publishSource=False +): + + # define stakeholder accounts + a = stakeholders_accounts + instanceOperator=a[INSTANCE_OPERATOR] + instanceWallet=a[INSTANCE_WALLET] + riskpoolKeeper=a[RISKPOOL_KEEPER] + riskpoolWallet=a[RISKPOOL_WALLET] + investor=a[INVESTOR] + productOwner=a[PRODUCT_OWNER] + customer=a[CUSTOMER1] + customer2=a[CUSTOMER2] + + if not check_funds(a, erc20_token): + print('ERROR: insufficient funding, aborting deploy') + return + + # assess balances at beginning of deploy + balances_before = _get_balances(stakeholders_accounts) + + if not erc20_token: + print('ERROR: no erc20 defined, aborting deploy') + return + + print('====== setting erc20 token to {} ======'.format(erc20_token)) + erc20Token = erc20_token + + print('====== deploy gif instance ======') + instance = GifInstance(instanceOperator, instanceWallet=instanceWallet, publishSource=publishSource) + instanceService = instance.getInstanceService() + instanceOperatorService = instance.getInstanceOperatorService() + componentOwnerService = instance.getComponentOwnerService() + + print('====== deploy depeg product ======') + depegDeploy = GifDepegProductComplete(instance, productOwner, investor, erc20Token, riskpoolKeeper, riskpoolWallet, publishSource=publishSource) + + # assess balances at beginning of deploy + balances_after_deploy = _get_balances(stakeholders_accounts) + + depegProduct = depegDeploy.getProduct() + depegRiskpool = depegDeploy.getRiskpool() + + product = depegProduct.getContract() + riskpool = depegRiskpool.getContract() + + print('====== create initial setup ======') + + bundleInitialFunding = INITIAL_ERC20_BUNDLE_FUNDING + print('1) investor {} funding (transfer/approve) with {} token for erc20 {}'.format( + investor, bundleInitialFunding, erc20Token)) + + erc20Token.transfer(investor, bundleInitialFunding, {'from': instanceOperator}) + erc20Token.approve(instance.getTreasury(), bundleInitialFunding, {'from': investor}) + + print('2) riskpool wallet {} approval for instance treasury {}'.format( + riskpoolWallet, instance.getTreasury())) + + erc20Token.approve(instance.getTreasury(), bundleInitialFunding, {'from': riskpoolWallet}) + + print('3) riskpool bundle creation by investor {}'.format( + investor)) + + policyMinSumInsured = 1000 + policyMaxSumInsured = 50000 + policyMinDuration = 30 * 24 * 3600 + policyMaxDuration = 90 * 24 * 3600 + annualPercentageReturn = riskpool.getApr100PercentLevel() / 20; + applicationFilter = bytes(0) + riskpool.createBundle( + policyMinSumInsured, + policyMaxSumInsured, + policyMinDuration, + policyMaxDuration, + annualPercentageReturn, + bundleInitialFunding, + {'from': investor}) + + customerFunding=1000 + print('5) customer {} funding (transfer/approve) with {} token for erc20 {}'.format( + customer, customerFunding, erc20Token)) + + erc20Token.transfer(customer, customerFunding, {'from': instanceOperator}) + erc20Token.approve(instance.getTreasury(), customerFunding, {'from': customer}) + + # policy creation + premium = [300, 400] + sumInsured = [2000, 3000] + print('6) policy creation (2x) for customers {}, {}'.format( + customer, customer2)) + + tx = [None, None] + tx[0] = product.applyForPolicy(premium[0], sumInsured[0], {'from': customer}) + tx[1] = product.applyForPolicy(premium[1], sumInsured[1], {'from': customer2}) + + processId1 = tx[0].events['LogDepegPolicyCreated']['policyId'] + processId2 = tx[1].events['LogDepegPolicyCreated']['policyId'] + + deploy_result = { + INSTANCE_OPERATOR: instanceOperator, + INSTANCE_WALLET: instanceWallet, + RISKPOOL_KEEPER: riskpoolKeeper, + RISKPOOL_WALLET: riskpoolWallet, + INVESTOR: investor, + PRODUCT_OWNER: productOwner, + CUSTOMER1: customer, + CUSTOMER2: customer2, + ERC20_TOKEM: contract_from_address(interface.ERC20, erc20Token), + INSTANCE: instance, + INSTANCE_SERVICE: contract_from_address(InstanceService, instanceService), + INSTANCE_OPERATOR_SERVICE: contract_from_address(InstanceOperatorService, instanceOperatorService), + COMPONENT_OWNER_SERVICE: contract_from_address(ComponentOwnerService, componentOwnerService), + PRODUCT: contract_from_address(DepegProduct, product), + RISKPOOL: contract_from_address(DepegRiskpool, riskpool), + PROCESS_ID1: processId1, + PROCESS_ID2: processId2, + } + + print('deploy_result: {}'.format(deploy_result)) + + print('====== deploy and setup creation complete ======') + print('') + + # check balances at end of setup + balances_after_setup = _get_balances(stakeholders_accounts) + + print('--------------------------------------------------------------------') + print('inital balances: {}'.format(balances_before)) + print('after deploy balances: {}'.format(balances_after_deploy)) + print('end of setup balances: {}'.format(balances_after_setup)) + + delta_deploy = _get_balances_delta(balances_before, balances_after_deploy) + delta_setup = _get_balances_delta(balances_after_deploy, balances_after_setup) + delta_total = _get_balances_delta(balances_before, balances_after_setup) + + print('--------------------------------------------------------------------') + print('total deploy {}'.format(delta_deploy['total'])) + print('deploy {}'.format(delta_deploy)) + + print('--------------------------------------------------------------------') + print('total setup after deploy {}'.format(delta_setup['total'])) + print('setup after deploy {}'.format(delta_setup)) + + print('--------------------------------------------------------------------') + print('total deploy + setup{}'.format(delta_total['total'])) + print('deploy + setup{}'.format(delta_total)) + + print('--------------------------------------------------------------------') + + _pretty_print_delta('gas usage deploy', delta_deploy) + _pretty_print_delta('gas usage total', delta_total) + + return deploy_result + + +def help(): + print('from scripts.deploy_depeg import allIn1, inspect_bundle') + print('(customer, product, riskpool, riskpoolWallet, usd1, instanceService, d) = allIn1()') + print('inspect_bundle(d, 1)') + + +def allIn1(): + a = stakeholders_accounts_ganache() + usd1 = TestCoin.deploy({'from':a[INSTANCE_OPERATOR]}) + d = deploy_setup_including_token(a, usd1) + + customer = d[CUSTOMER1] + instanceService = d[INSTANCE_SERVICE] + product = d[PRODUCT] + riskpool = d[RISKPOOL] + riskpoolWallet = d[RISKPOOL_WALLET] + + return (customer, product, riskpool, riskpoolWallet, usd1, instanceService, d) + + +def inspect_bundle(d, bundleId): + instanceService = d[INSTANCE_SERVICE] + riskpool = d[RISKPOOL] + + bundle = instanceService.getBundle(bundleId) + filter = bundle[4] + ( + minSumInsured, + maxSumInsured, + minDuration, + maxDuration, + annualPercentageReturn + + ) = riskpool.decodeBundleParamsFromFilter(filter) + + sPerD = 24 * 3600 + print('bundle {} riskpool {}'.format(bundleId, bundle[1])) + print('- nft {}'.format(bundle[2])) + print('- state {}'.format(bundle[3])) + print('- filter') + print(' + sum insured {}-{} [USD1]'.format(minSumInsured, maxSumInsured)) + print(' + coverage duration {}-{} [days]'.format(minDuration/sPerD, maxDuration/sPerD)) + print(' + apr {} [%]'.format(100 * annualPercentageReturn/riskpool.getApr100PercentLevel())) + print('- financials') + print(' + capital {}'.format(bundle[5])) + print(' + locked {}'.format(bundle[6])) + print(' + capacity {}'.format(bundle[5]-bundle[6])) + print(' + balance {}'.format(bundle[7])) + +def from_component(componentAddress): + component = contract_from_address(interface.IComponent, componentAddress) + return from_registry(component.getRegistry()) + + +def from_registry( + registryAddress, + productId=0, + riskpoolId=0 +): + instance = GifInstance(registryAddress=registryAddress) + instanceService = instance.getInstanceService() + + products = instanceService.products() + riskpools = instanceService.riskpools() + + product = None + riskpool = None + + if products >= 1: + if productId > 0: + componentId = productId + else: + componentId = instanceService.getProductId(products-1) + + if products > 1: + print('1 product expected, {} products available'.format(products)) + print('returning last product available') + + componentAddress = instanceService.getComponent(componentId) + product = contract_from_address(AyiiProduct, componentAddress) + + if product.getType() != 1: + product = None + print('component (type={}) with id {} is not product'.format(product.getType(), componentId)) + print('no product returned (None)') + else: + print('1 product expected, no product available') + print('no product returned (None)') + + if riskpools >= 1: + if riskpoolId > 0: + componentId = riskpoolId + else: + componentId = instanceService.getRiskpoolId(riskpools-1) + + if riskpools > 1: + print('1 riskpool expected, {} riskpools available'.format(riskpools)) + print('returning last riskpool available') + + componentAddress = instanceService.getComponent(componentId) + riskpool = contract_from_address(AyiiRiskpool, componentAddress) + + if riskpool.getType() != 2: + riskpool = None + print('component (type={}) with id {} is not riskpool'.format(component.getType(), componentId)) + print('no riskpool returned (None)') + else: + print('1 riskpool expected, no riskpools available') + print('no riskpool returned (None)') + + return (instance, product, riskpool) From e751cad947c9d2a3f243f844b6f4fe700455a911 Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Mon, 7 Nov 2022 17:49:55 +0000 Subject: [PATCH 2/4] add matching for depeg applications and bundles, convenience scripting --- .vscode/settings.json | 2 +- brownie-config.yaml | 4 +- contracts/examples/DepegProduct.sol | 8 ++- contracts/examples/DepegRiskpool.sol | 15 ++-- scripts/deploy_depeg.py | 104 ++++++++++++++++++++++----- scripts/deploy_instance.py | 27 +++++++ 6 files changed, 133 insertions(+), 27 deletions(-) create mode 100644 scripts/deploy_instance.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 8214b27..9eec543 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,7 +9,7 @@ "solidity.remappingsUnix": [ "@openzeppelin/=/home/vscode/.brownie/packages/OpenZeppelin/openzeppelin-contracts@4.7.3", "@chainlink/=/home/vscode/.brownie/packages/smartcontractkit/chainlink@1.6.0", - "@etherisc/gif-interface/=/home/vscode/.brownie/packages/etherisc/gif-interface@6da625a", + "@etherisc/gif-interface/=/home/vscode/.brownie/packages/etherisc/gif-interface@87110db", ], "solidity.compileUsingRemoteVersion": "v0.8.2+commit.661d1103", "peacock.remoteColor": "1D3C43", diff --git a/brownie-config.yaml b/brownie-config.yaml index 8dc9dcb..c605802 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -22,7 +22,7 @@ compiler: remappings: - "@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.7.3" - "@chainlink=smartcontractkit/chainlink@1.6.0" - - "@etherisc/gif-interface=etherisc/gif-interface@6da625a" + - "@etherisc/gif-interface=etherisc/gif-interface@87110db" # packages below will be added to brownie # you may use 'brownie pm list' after 'brownie compile' @@ -32,7 +32,7 @@ dependencies: # github dependency format: /@ - OpenZeppelin/openzeppelin-contracts@4.7.3 - smartcontractkit/chainlink@1.6.0 - - etherisc/gif-interface@6da625a + - etherisc/gif-interface@87110db # exclude open zeppeling contracts when calculating test coverage # https://eth-brownie.readthedocs.io/en/v1.10.3/config.html#exclude_paths diff --git a/contracts/examples/DepegProduct.sol b/contracts/examples/DepegProduct.sol index 71fa05e..3b34a5c 100644 --- a/contracts/examples/DepegProduct.sol +++ b/contracts/examples/DepegProduct.sol @@ -1,6 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.2; +/* +from scripts.deploy_depeg import help +help() + */ + + import "@etherisc/gif-interface/contracts/components/IComponent.sol"; import "@etherisc/gif-interface/contracts/components/Product.sol"; @@ -124,6 +130,6 @@ contract DepegProduct is } function getApplicationDataStructure() external override pure returns(string memory dataStructure) { - return "TODO"; + return "(uint256 duration,uint256 maxPremium)"; } } \ No newline at end of file diff --git a/contracts/examples/DepegRiskpool.sol b/contracts/examples/DepegRiskpool.sol index e6945e4..fd47798 100644 --- a/contracts/examples/DepegRiskpool.sol +++ b/contracts/examples/DepegRiskpool.sol @@ -87,8 +87,7 @@ contract DepegRiskpool is // + .1 create application // + .2 figure out if there's a matching bundle // + .3 add function that let's user query conditions based on active bundles - // TODO function defined in base class Riskpool -> needs to be virtual! - function getFilterDataStructureTemp() external pure returns(string memory) { + function getFilterDataStructure() external override pure returns(string memory) { return "(uint256 minSumInsured,uint256 maxSumInsured,uint256 minDuration,uint256 maxDuration,uint256 annualPercentageReturn)"; } @@ -112,7 +111,7 @@ contract DepegRiskpool is } function decodeBundleParamsFromFilter( - bytes calldata filter + bytes memory filter ) public pure returns ( @@ -148,7 +147,7 @@ contract DepegRiskpool is function decodeApplicationParameterFromData( - bytes calldata data + bytes memory data ) public pure returns ( @@ -162,10 +161,14 @@ contract DepegRiskpool is ) = abi.decode(data, (uint256, uint256)); } + function getBundleFilter(uint256 bundleId) public view returns (bytes memory filter) { + IBundle.Bundle memory bundle = _instanceService.getBundle(bundleId); + filter = bundle.filter; + } function bundleMatchesApplication( - IBundle.Bundle calldata bundle, - IPolicy.Application calldata application + IBundle.Bundle memory bundle, + IPolicy.Application memory application ) public override pure diff --git a/scripts/deploy_depeg.py b/scripts/deploy_depeg.py index ae8382c..6af60d7 100644 --- a/scripts/deploy_depeg.py +++ b/scripts/deploy_depeg.py @@ -302,17 +302,11 @@ def deploy( erc20Token.approve(instance.getTreasury(), customerFunding, {'from': customer}) # policy creation - premium = [300, 400] - sumInsured = [2000, 3000] - print('6) policy creation (2x) for customers {}, {}'.format( - customer, customer2)) - - tx = [None, None] - tx[0] = product.applyForPolicy(premium[0], sumInsured[0], {'from': customer}) - tx[1] = product.applyForPolicy(premium[1], sumInsured[1], {'from': customer2}) - - processId1 = tx[0].events['LogDepegPolicyCreated']['policyId'] - processId2 = tx[1].events['LogDepegPolicyCreated']['policyId'] + sumInsured = 20000 + duration = 50 + maxPremium = 1000 + print('6) policy creation for customers {}'.format(customer)) + processId = new_policy(product, customer, sumInsured, duration, maxPremium) deploy_result = { INSTANCE_OPERATOR: instanceOperator, @@ -330,8 +324,7 @@ def deploy( COMPONENT_OWNER_SERVICE: contract_from_address(ComponentOwnerService, componentOwnerService), PRODUCT: contract_from_address(DepegProduct, product), RISKPOOL: contract_from_address(DepegRiskpool, riskpool), - PROCESS_ID1: processId1, - PROCESS_ID2: processId2, + PROCESS_ID1: processId, } print('deploy_result: {}'.format(deploy_result)) @@ -372,12 +365,15 @@ def deploy( def help(): - print('from scripts.deploy_depeg import allIn1, inspect_bundle') - print('(customer, product, riskpool, riskpoolWallet, usd1, instanceService, d) = allIn1()') + print('from scripts.deploy_depeg import all_in_1, new_policy, inspect_bundle, inspect_applications, help') + print('(customer, product, riskpool, riskpoolWallet, usd1, instanceService, processId, d) = all_in_1()') + print('instanceService.getPolicy(processId)') + print('instanceService.getBundle(1)') print('inspect_bundle(d, 1)') -def allIn1(): + +def all_in_1(): a = stakeholders_accounts_ganache() usd1 = TestCoin.deploy({'from':a[INSTANCE_OPERATOR]}) d = deploy_setup_including_token(a, usd1) @@ -387,8 +383,82 @@ def allIn1(): product = d[PRODUCT] riskpool = d[RISKPOOL] riskpoolWallet = d[RISKPOOL_WALLET] + processId = d[PROCESS_ID1] + + return (customer, product, riskpool, riskpoolWallet, usd1, instanceService, processId, d) + + +def new_policy( + product, + customer, + sumInsured, + durationDays, + maxPremium +) -> str: + duration = durationDays*24*3600 + tx = product.applyForPolicy(sumInsured, duration, maxPremium, {'from': customer}) + + if 'LogDepegApplicationCreated' in tx.events: + processId = tx.events['LogDepegApplicationCreated']['policyId'] + else: + processId = None + + applicationSuccess = 'success' if processId else 'failed' + policySuccess = 'success' if 'LogDepegPolicyCreated' in tx.events else 'failed' - return (customer, product, riskpool, riskpoolWallet, usd1, instanceService, d) + print('processId {} application {} policy {}'.format( + processId, + applicationSuccess, + policySuccess)) + + return processId + + +def inspect_applications(d): + instanceService = d[INSTANCE_SERVICE] + product = d[PRODUCT] + riskpool = d[RISKPOOL] + + processIds = product.applications() + + # print header row + print('i customer product id type state premium suminsured duration maxpremium') + + # print individual rows + for idx in range(processIds): + # TODO instanceService needs method getProcessId(idx) + processId = product.getApplicationId(idx) + metadata = instanceService.getMetadata(processId) + customer = metadata[0] + productId = metadata[1] + + application = instanceService.getApplication(processId) + state = application[0] + premium = application[1] + suminsured = application[2] + appdata = application[3] + (duration, maxpremium) = riskpool.decodeApplicationParameterFromData(appdata) + + if state == 2: + policy = instanceService.getPolicy(processId) + state = policy[0] + kind = 'policy' + else: + policy = None + kind = 'application' + + print('{} {} {} {} {} {} {} {} {} {}'.format( + idx, + customer[:6], + productId, + processId, + kind, + state, + premium, + suminsured, + duration/(24*3600), + maxpremium + )) def inspect_bundle(d, bundleId): diff --git a/scripts/deploy_instance.py b/scripts/deploy_instance.py new file mode 100644 index 0000000..8460ed1 --- /dev/null +++ b/scripts/deploy_instance.py @@ -0,0 +1,27 @@ +from brownie import web3 + +from brownie.network import accounts +from brownie.network.account import Account + +from brownie import ( + interface, + network, + TestCoin, + InstanceService, + InstanceOperatorService, + ComponentOwnerService, +) + +from scripts.instance import GifInstance + +def deploy_instance_ganache(): + instanceOperator=accounts[0] + instanceWallet=accounts[1] + + instance = GifInstance(instanceOperator, instanceWallet=instanceWallet) + registry = instance.getRegistry() + instanceService = instance.getInstanceService() + instanceOperatorService = instance.getInstanceOperatorService() + componentOwnerService = instance.getComponentOwnerService() + + return (registry, instanceOperator, instanceService) From df94df4d53943c3646824d9668bb5c31c0145653 Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Mon, 7 Nov 2022 21:16:38 +0000 Subject: [PATCH 3/4] add inspect_bundles --- scripts/deploy_depeg.py | 81 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/scripts/deploy_depeg.py b/scripts/deploy_depeg.py index 6af60d7..56dd47c 100644 --- a/scripts/deploy_depeg.py +++ b/scripts/deploy_depeg.py @@ -284,7 +284,6 @@ def deploy( policyMinDuration = 30 * 24 * 3600 policyMaxDuration = 90 * 24 * 3600 annualPercentageReturn = riskpool.getApr100PercentLevel() / 20; - applicationFilter = bytes(0) riskpool.createBundle( policyMinSumInsured, policyMaxSumInsured, @@ -365,11 +364,13 @@ def deploy( def help(): - print('from scripts.deploy_depeg import all_in_1, new_policy, inspect_bundle, inspect_applications, help') + print('from scripts.deploy_depeg import all_in_1, new_bundle, new_policy, inspect_bundle, inspect_applications, help') print('(customer, product, riskpool, riskpoolWallet, usd1, instanceService, processId, d) = all_in_1()') print('instanceService.getPolicy(processId)') print('instanceService.getBundle(1)') print('inspect_bundle(d, 1)') + print('inspect_bundles(d)') + print('inspect_applications(d)') @@ -388,6 +389,39 @@ def all_in_1(): return (customer, product, riskpool, riskpoolWallet, usd1, instanceService, processId, d) +def new_bundle( + d, + funding, + minSumInsured, + maxSumInsured, + minDurationDays, + maxDurationDays, + aprPercentage +): + instance = d['instance'] + instanceOperator = d['instanceOperator'] + investor = d['investor'] + riskpool = d['riskpool'] + tokenAddress = riskpool.getErc20Token() + token = contract_from_address(TestCoin, tokenAddress) + + token.transfer(investor, funding, {'from': instanceOperator}) + token.approve(instance.getTreasury(), funding, {'from': investor}) + + apr100level = riskpool.getApr100PercentLevel(); + apr = apr100level * aprPercentage / 100 + + spd = 24*3600 + riskpool.createBundle( + minSumInsured, + maxSumInsured, + minDurationDays * spd, + maxDurationDays * spd, + apr, + funding, + {'from': investor}) + + def new_policy( product, customer, @@ -461,6 +495,49 @@ def inspect_applications(d): )) +def inspect_bundles(d): + instanceService = d[INSTANCE_SERVICE] + riskpool = d[RISKPOOL] + riskpoolId = riskpool.getId() + activeBundles = instanceService.activeBundles(riskpoolId) + + # print header row + print('i riskpool bundle minsuminsured maxsuminsured minduration maxduration apr capital locked capacity') + + # print individual rows + for idx in range(activeBundles): + bundleId = instanceService.getActiveBundleId(riskpoolId, idx) + bundle = instanceService.getBundle(bundleId) + filter = bundle[4] + ( + minSumInsured, + maxSumInsured, + minDuration, + maxDuration, + annualPercentageReturn + + ) = riskpool.decodeBundleParamsFromFilter(filter) + + apr = 100 * annualPercentageReturn/riskpool.getApr100PercentLevel() + capital = bundle[5] + locked = bundle[6] + capacity = bundle[5]-bundle[6] + + print('{} {} {} {} {} {} {} {} {} {} {}'.format( + idx, + riskpoolId, + bundleId, + minSumInsured, + maxSumInsured, + minDuration/(24*3600), + maxDuration/(24*3600), + apr, + capital, + locked, + capacity + )) + + def inspect_bundle(d, bundleId): instanceService = d[INSTANCE_SERVICE] riskpool = d[RISKPOOL] From 9eca04fd7b510196b284fb138198bc14854c5ba1 Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Thu, 10 Nov 2022 15:41:38 +0000 Subject: [PATCH 4/4] next steps --- contracts/examples/AyiiRiskpool.sol | 1 + contracts/examples/BasicRiskpool2.sol | 190 ++++++++++++++ contracts/examples/DepegProduct.sol | 54 +++- contracts/examples/DepegRiskpool.sol | 78 +++++- contracts/examples/Riskpool2.sol | 340 +++++++++++++++++++++++++ contracts/services/InstanceService.sol | 5 + scripts/deploy_depeg.py | 89 ++++--- 7 files changed, 708 insertions(+), 49 deletions(-) create mode 100644 contracts/examples/BasicRiskpool2.sol create mode 100644 contracts/examples/Riskpool2.sol diff --git a/contracts/examples/AyiiRiskpool.sol b/contracts/examples/AyiiRiskpool.sol index 292fa59..94c2c57 100644 --- a/contracts/examples/AyiiRiskpool.sol +++ b/contracts/examples/AyiiRiskpool.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.2; import "@openzeppelin/contracts/access/AccessControl.sol"; +import "./BasicRiskpool2.sol"; import "@etherisc/gif-interface/contracts/components/BasicRiskpool.sol"; import "@etherisc/gif-interface/contracts/modules/IBundle.sol"; import "@etherisc/gif-interface/contracts/modules/IPolicy.sol"; diff --git a/contracts/examples/BasicRiskpool2.sol b/contracts/examples/BasicRiskpool2.sol new file mode 100644 index 0000000..a131693 --- /dev/null +++ b/contracts/examples/BasicRiskpool2.sol @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.2; + +import "./Riskpool2.sol"; +import "@etherisc/gif-interface/contracts/modules/IBundle.sol"; +import "@etherisc/gif-interface/contracts/modules/IPolicy.sol"; + +// basic riskpool always collateralizes one application using exactly one bundle +abstract contract BasicRiskpool2 is Riskpool2 { + + event LogBasicRiskpoolBundlesAndPolicies(uint256 activeBundles, uint256 policies); + event LogBasicRiskpoolCandidateBundleAmountCheck(uint256 index, uint256 bundleId, uint256 maxAmount, uint256 collateralAmount); + + // remember bundleId for each processId + // approach only works for basic risk pool where a + // policy is collateralized by exactly one bundle + mapping(bytes32 /* processId */ => uint256 /** bundleId */) internal _collateralizedBy; + uint32 private _policiesCounter = 0; + + // will hold a sorted active bundle id array + uint256[] private _activeBundleIds; + + // informational counter of active policies per bundle + mapping(uint256 /* bundleId */ => uint256 /* activePolicyCount */) private _activePoliciesForBundle; + + constructor( + bytes32 name, + uint256 collateralization, + uint256 sumOfSumInsuredCap, + address erc20Token, + address wallet, + address registry + ) + Riskpool2(name, collateralization, sumOfSumInsuredCap, erc20Token, wallet, registry) + { } + + + + // needs to remember which bundles helped to cover ther risk + // simple (retail) approach: single policy covered by single bundle + // first bundle with a match and sufficient capacity wins + // Component <- Riskpool <- BasicRiskpool <- TestRiskpool + // complex (wholesale) approach: single policy covered by many bundles + // Component <- Riskpool <- AdvancedRiskpool <- TestRiskpool + function _lockCollateral(bytes32 processId, uint256 collateralAmount) + internal override + returns(bool success) + { + uint256 capital = getCapital(); + uint256 lockedCapital = getTotalValueLocked(); + + emit LogBasicRiskpoolBundlesAndPolicies(_activeBundleIds.length, _policiesCounter); + require(_activeBundleIds.length > 0, "ERROR:BRP-001:NO_ACTIVE_BUNDLES"); + require(capital > lockedCapital, "ERROR:BRP-002:NO_FREE_CAPITAL"); + + // ensure there is a chance to find the collateral + if(capital >= lockedCapital + collateralAmount) { + IPolicy.Application memory application = _instanceService.getApplication(processId); + + // basic riskpool implementation: policy coverage by single bundle only/ + // active bundle arrays with the most attractive bundle at the first place + for (uint256 i = 0; i < _activeBundleIds.length && !success; i++) { + uint256 bundleId = _activeBundleIds[i]; + // uint256 bundleId = getActiveBundleId(bundleIdx); + IBundle.Bundle memory bundle = _instanceService.getBundle(bundleId); + bool isMatching = bundleMatchesApplication2(bundle, application); + // emit LogRiskpoolBundleMatchesPolicy(bundleId, isMatching); + + if (isMatching) { + uint256 maxAmount = bundle.capital - bundle.lockedCapital; + emit LogBasicRiskpoolCandidateBundleAmountCheck(i, bundleId, maxAmount, collateralAmount); + + if (maxAmount >= collateralAmount) { + _riskpoolService.collateralizePolicy(bundleId, processId, collateralAmount); + _collateralizedBy[processId] = bundleId; + success = true; + _policiesCounter++; + + // update active policies counter + _activePoliciesForBundle[bundleId]++; + } + } + } + } + } + + // hack + function bundleMatchesApplication2( + IBundle.Bundle memory bundle, + IPolicy.Application memory application + ) + public virtual returns(bool isMatching); + + // manage sorted list of active bundle ids + function _afterCreateBundle(uint256 bundleId, bytes memory filter, uint256 initialAmount) internal override virtual { + _addBundleToActiveList(bundleId); + } + + function _afterLockBundle(uint256 bundleId) internal override virtual { + _removeBundleFromActiveList(bundleId); + } + function _afterUnlockBundle(uint256 bundleId) internal override virtual { + _addBundleToActiveList(bundleId); + } + function _afterCloseBundle(uint256 bundleId) internal override virtual { + _removeBundleFromActiveList(bundleId); + } + + function _addBundleToActiveList(uint256 bundleId) internal { + bool found = false; + bool inserted = false; + + for (uint256 i = 0; !inserted && !found && i < _activeBundleIds.length; i++) { + if (bundleId == _activeBundleIds[i]) { + found = true; + } + else if (isHigherPriorityBundle(bundleId, _activeBundleIds[i])) { + inserted = true; + _activeBundleIds.push(10**6); + + for (uint256 j = _activeBundleIds.length - 1; j > i; j--) { + _activeBundleIds[j] = _activeBundleIds[j-1]; + } + + // does not work for inserting at end of list ... + _activeBundleIds[i] = bundleId; + } + } + + if (!found && !inserted) { + _activeBundleIds.push(bundleId); + } + } + + // default implementation adds new bundle at the end of the active list + function isHigherPriorityBundle(uint256 firstBundleId, uint256 secondBundleId) + public virtual + view + returns (bool firstBundleIsHigherPriority) + { + firstBundleIsHigherPriority = false; + } + + + function _removeBundleFromActiveList(uint256 bundleId) internal { + bool inList = false; + for (uint256 i = 0; !inList && i < _activeBundleIds.length; i++) { + inList = (bundleId == _activeBundleIds[i]); + if (inList) { + for (; i < _activeBundleIds.length - 1; i++) { + _activeBundleIds[i] = _activeBundleIds[i+1]; + } + _activeBundleIds.pop(); + } + } + } + + function getActiveBundleIds() public view returns (uint256[] memory activeBundleIds) { + return _activeBundleIds; + } + + function getActivePolicies(uint256 bundleId) public view returns (uint256 activePolicies) { + return _activePoliciesForBundle[bundleId]; + } + + function _processPayout(bytes32 processId, uint256 amount) + internal override + { + uint256 bundleId = _collateralizedBy[processId]; + _riskpoolService.processPayout(bundleId, processId, amount); + } + + function _processPremium(bytes32 processId, uint256 amount) + internal override + { + uint256 bundleId = _collateralizedBy[processId]; + _riskpoolService.processPremium(bundleId, processId, amount); + } + + function _releaseCollateral(bytes32 processId) + internal override + returns(uint256 collateralAmount) + { + uint256 bundleId = _collateralizedBy[processId]; + collateralAmount = _riskpoolService.releasePolicy(bundleId, processId); + + // update active policies counter + _activePoliciesForBundle[bundleId]--; + } +} \ No newline at end of file diff --git a/contracts/examples/DepegProduct.sol b/contracts/examples/DepegProduct.sol index 3b34a5c..827ed92 100644 --- a/contracts/examples/DepegProduct.sol +++ b/contracts/examples/DepegProduct.sol @@ -1,15 +1,12 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.2; -/* -from scripts.deploy_depeg import help -help() - */ - +import "@etherisc/gif-interface/contracts/modules/IPolicy.sol"; import "@etherisc/gif-interface/contracts/components/IComponent.sol"; import "@etherisc/gif-interface/contracts/components/Product.sol"; + import "./DepegRiskpool.sol"; contract DepegProduct is @@ -92,6 +89,51 @@ contract DepegProduct is } } + function getBestQuote( + uint256 sumInsured, + uint256 duration + ) + public + // view // TODO should be view, for now bundleMatchesApplication forces non-view visibility + returns(uint256 lowestPremiumAmount) + { + bytes memory applicationData = _riskPool.encodeApplicationParameterAsData( + duration, + sumInsured - 1 // ridiculously high max premium + ); + + IPolicy.Application memory applicationForQuote = IPolicy.Application( + IPolicy.ApplicationState.Applied, // state, not relevant + 0, // premium, not relevant + sumInsured, + applicationData, // contains duration + 0, // created at, not relevant + 0 // updated at, not relevant + ); + + uint256[] memory bundleIds = _riskPool.getActiveBundleIds(); + + for(uint256 i = 0; i < bundleIds.length; i++) { + IBundle.Bundle memory bundle = _instanceService.getBundle(bundleIds[i]); + + // check if bundle has enough capital + if(applicationForQuote.sumInsuredAmount < bundle.capital - bundle.lockedCapital) { + // check if application parameters match with bundle parameters + if(_riskPool.bundleMatchesApplication2(bundle, applicationForQuote)) { + ( + uint256 minSumInsured, + uint256 maxSumInsured, + uint256 minDuration, + uint256 maxDuration, + uint256 annualPercentageReturn + ) = _riskPool.decodeBundleParamsFromFilter(bundle.filter); + + return _riskPool.calculatePremium(sumInsured, duration, annualPercentageReturn); + } + } + } + } + function triggerOracle() external { diff --git a/contracts/examples/DepegRiskpool.sol b/contracts/examples/DepegRiskpool.sol index fd47798..551aa1a 100644 --- a/contracts/examples/DepegRiskpool.sol +++ b/contracts/examples/DepegRiskpool.sol @@ -3,14 +3,17 @@ pragma solidity 0.8.2; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./BasicRiskpool2.sol"; import "@etherisc/gif-interface/contracts/components/BasicRiskpool.sol"; import "@etherisc/gif-interface/contracts/modules/IBundle.sol"; import "@etherisc/gif-interface/contracts/modules/IPolicy.sol"; contract DepegRiskpool is - BasicRiskpool + BasicRiskpool2 { + event LogBundleMatchesApplication(uint256 bundleId, bool sumInsuredOk, bool durationOk, bool premiumOk); + uint256 public constant USD_RISK_CAPITAL_CAP = 1 * 10**6; uint256 public constant MAX_BUNDLE_DURATION = 180 * 24 * 3600; @@ -31,7 +34,7 @@ contract DepegRiskpool is address wallet, address registry ) - BasicRiskpool(name, getFullCollateralizationLevel(), sumOfSumInsuredCap, erc20Token, wallet, registry) + BasicRiskpool2(name, getFullCollateralizationLevel(), sumOfSumInsuredCap, erc20Token, wallet, registry) { ERC20 token = ERC20(erc20Token); _poolRiskCapitalCap = USD_RISK_CAPITAL_CAP * 10 ** token.decimals(); @@ -166,14 +169,48 @@ contract DepegRiskpool is filter = bundle.filter; } + // sorts bundles on increasing annual percentage return + function isHigherPriorityBundle(uint256 firstBundleId, uint256 secondBundleId) + public override + view + returns (bool firstBundleIsHigherPriority) + { + uint256 firstApr = _getBundleApr(firstBundleId); + uint256 secondApr = _getBundleApr(secondBundleId); + firstBundleIsHigherPriority = (firstApr < secondApr); + } + + function _getBundleApr(uint256 bundleId) internal view returns (uint256 apr) { + bytes memory filter = getBundleFilter(bundleId); + ( + uint256 minSumInsured, + uint256 maxSumInsured, + uint256 minDuration, + uint256 maxDuration, + uint256 annualPercentageReturn + ) = decodeBundleParamsFromFilter(filter); + + apr = annualPercentageReturn; + } + + function bundleMatchesApplication( IBundle.Bundle memory bundle, IPolicy.Application memory application + ) + public view override + returns(bool isMatching) + {} + + function bundleMatchesApplication2( + IBundle.Bundle memory bundle, + IPolicy.Application memory application ) public override - pure returns(bool isMatching) { + uint256 bundleId = bundle.id; + ( uint256 minSumInsured, uint256 maxSumInsured, @@ -188,25 +225,44 @@ contract DepegRiskpool is ) = decodeApplicationParameterFromData(application.data); uint256 sumInsured = application.sumInsuredAmount; + bool sumInsuredOk = true; + bool durationOk = true; + bool premiumOk = true; - if(sumInsured < minSumInsured) { return false; } - if(sumInsured > maxSumInsured) { return false; } + if(sumInsured < minSumInsured) { sumInsuredOk = false; } + if(sumInsured > maxSumInsured) { sumInsuredOk = false; } - if(duration < minDuration) { return false; } - if(duration > maxDuration) { return false; } + if(duration < minDuration) { durationOk = false; } + if(duration > maxDuration) { durationOk = false; } - uint256 policyDurationReturn = annualPercentageReturn * duration / ONE_YEAR_DURATION; - uint256 premium = sumInsured * policyDurationReturn / APR_100_PERCENTAGE; + uint256 premium = calculatePremium(sumInsured, duration, annualPercentageReturn); + if(premium > maxPremium) { premiumOk = false; } - if(premium > maxPremium) { return false; } + isMatching = (sumInsuredOk && durationOk && premiumOk); - return true; + emit LogBundleMatchesApplication(bundleId, sumInsuredOk, durationOk, premiumOk); + } + + function calculatePremium( + uint256 sumInsured, + uint256 duration, + uint256 annualPercentageReturn + ) + public view + returns(uint256 premiumAmount) + { + uint256 policyDurationReturn = annualPercentageReturn * duration / ONE_YEAR_DURATION; + premiumAmount = sumInsured * policyDurationReturn / APR_100_PERCENTAGE; } function getBundleRiskCapitalCap() public view returns (uint256 bundleRiskCapitalCap) { return _bundleRiskCapitalCap; } + function getOneYearDuration() public pure returns(uint256 apr100PercentLevel) { + return ONE_YEAR_DURATION; + } + function getApr100PercentLevel() public pure returns(uint256 apr100PercentLevel) { return APR_100_PERCENTAGE; } diff --git a/contracts/examples/Riskpool2.sol b/contracts/examples/Riskpool2.sol new file mode 100644 index 0000000..0b33e87 --- /dev/null +++ b/contracts/examples/Riskpool2.sol @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.2; + +import "@etherisc/gif-interface/contracts/components/IRiskpool.sol"; +import "@etherisc/gif-interface/contracts/components/Component.sol"; + +import "@etherisc/gif-interface/contracts/modules/IBundle.sol"; +import "@etherisc/gif-interface/contracts/modules/IPolicy.sol"; +import "@etherisc/gif-interface/contracts/services/IInstanceService.sol"; +import "@etherisc/gif-interface/contracts/services/IRiskpoolService.sol"; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +abstract contract Riskpool2 is + IRiskpool, + Component +{ + + // TODO move to IRiskpool + event LogMaximumNumberOfActiveBundlesSet(uint256 numberOfBundles); + event LogRiskpoolBundleFunded(uint256 bundleId, uint256 amount); + event LogRiskpoolBundleDefunded(uint256 bundleId, uint256 amount); + + event LogRiskpoolBundleLocked(uint256 bundleId); + event LogRiskpoolBundleUnlocked(uint256 bundleId); + event LogRiskpoolBundleClosed(uint256 bundleId); + event LogRiskpoolBundleBurned(uint256 bundleId); + + // used for representation of collateralization + // collateralization between 0 and 1 (1=100%) + // value might be larger when overcollateralization + uint256 public constant FULL_COLLATERALIZATION_LEVEL = 10**18; + string public constant DEFAULT_FILTER_DATA_STRUCTURE = ""; + + IInstanceService internal _instanceService; + IRiskpoolService internal _riskpoolService; + IERC721 internal _bundleToken; + + // keep track of bundles associated with this riskpool + uint256 [] internal _bundleIds; + + address private _wallet; + address private _erc20Token; + uint256 private _collateralization; + uint256 private _sumOfSumInsuredCap; + uint256 private _maxNumberOfActiveBundles; + + modifier onlyPool { + require( + _msgSender() == _getContractAddress("Pool"), + "ERROR:RPL-001:ACCESS_DENIED" + ); + _; + } + + modifier onlyBundleOwner(uint256 bundleId) { + IBundle.Bundle memory bundle = _instanceService.getBundle(bundleId); + address bundleOwner = _bundleToken.ownerOf(bundle.tokenId); + + require( + _msgSender() == bundleOwner, + "ERROR:RPL-002:NOT_BUNDLE_OWNER" + ); + _; + } + + constructor( + bytes32 name, + uint256 collateralization, + uint256 sumOfSumInsuredCap, + address erc20Token, + address wallet, + address registry + ) + Component(name, ComponentType.Riskpool, registry) + { + _collateralization = collateralization; + + require(sumOfSumInsuredCap != 0, "ERROR:RPL-003:SUM_OF_SUM_INSURED_CAP_ZERO"); + _sumOfSumInsuredCap = sumOfSumInsuredCap; + + require(erc20Token != address(0), "ERROR:RPL-005:ERC20_ADDRESS_ZERO"); + _erc20Token = erc20Token; + + require(wallet != address(0), "ERROR:RPL-006:WALLET_ADDRESS_ZERO"); + _wallet = wallet; + + _instanceService = IInstanceService(_getContractAddress("InstanceService")); + _riskpoolService = IRiskpoolService(_getContractAddress("RiskpoolService")); + _bundleToken = _instanceService.getBundleToken(); + } + + function _afterPropose() internal override virtual { + _riskpoolService.registerRiskpool( + _wallet, + _erc20Token, + _collateralization, + _sumOfSumInsuredCap + ); + } + + function createBundle(bytes memory filter, uint256 initialAmount) + public virtual override + returns(uint256 bundleId) + { + address bundleOwner = _msgSender(); + bundleId = _riskpoolService.createBundle(bundleOwner, filter, initialAmount); + _bundleIds.push(bundleId); + + // after action hook for child contracts + _afterCreateBundle(bundleId, filter, initialAmount); + + emit LogRiskpoolBundleCreated(bundleId, initialAmount); + } + + function fundBundle(uint256 bundleId, uint256 amount) + external override + onlyBundleOwner(bundleId) + returns(uint256 netAmount) + { + netAmount = _riskpoolService.fundBundle(bundleId, amount); + + // after action hook for child contracts + _afterFundBundle(bundleId, amount); + + emit LogRiskpoolBundleFunded(bundleId, amount); + } + + function defundBundle(uint256 bundleId, uint256 amount) + external override + onlyBundleOwner(bundleId) + returns(uint256 netAmount) + { + netAmount = _riskpoolService.defundBundle(bundleId, amount); + + // after action hook for child contracts + _afterDefundBundle(bundleId, amount); + + emit LogRiskpoolBundleDefunded(bundleId, amount); + } + + function lockBundle(uint256 bundleId) + external override + onlyBundleOwner(bundleId) + { + _riskpoolService.lockBundle(bundleId); + + // after action hook for child contracts + _afterLockBundle(bundleId); + + emit LogRiskpoolBundleLocked(bundleId); + } + + function unlockBundle(uint256 bundleId) + external override + onlyBundleOwner(bundleId) + { + _riskpoolService.unlockBundle(bundleId); + + // after action hook for child contracts + _afterUnlockBundle(bundleId); + + emit LogRiskpoolBundleUnlocked(bundleId); + } + + function closeBundle(uint256 bundleId) + external override + onlyBundleOwner(bundleId) + { + _riskpoolService.closeBundle(bundleId); + + // after action hook for child contracts + _afterCloseBundle(bundleId); + + emit LogRiskpoolBundleClosed(bundleId); + } + + function burnBundle(uint256 bundleId) + external override + onlyBundleOwner(bundleId) + { + _riskpoolService.burnBundle(bundleId); + + // after action hook for child contracts + _afterBurnBundle(bundleId); + + emit LogRiskpoolBundleBurned(bundleId); + } + + function collateralizePolicy(bytes32 processId, uint256 collateralAmount) + external override + onlyPool + returns(bool success) + { + success = _lockCollateral(processId, collateralAmount); + + emit LogRiskpoolCollateralLocked(processId, collateralAmount, success); + } + + function processPolicyPayout(bytes32 processId, uint256 amount) + external override + onlyPool + { + _processPayout(processId, amount); + emit LogRiskpoolPayoutProcessed(processId, amount); + } + + function processPolicyPremium(bytes32 processId, uint256 amount) + external override + onlyPool + { + _processPremium(processId, amount); + emit LogRiskpoolPremiumProcessed(processId, amount); + } + + function releasePolicy(bytes32 processId) + external override + onlyPool + { + uint256 collateralAmount = _releaseCollateral(processId); + emit LogRiskpoolCollateralReleased(processId, collateralAmount); + } + + function setMaximumNumberOfActiveBundles(uint256 maximumNumberOfActiveBundles) + public override + onlyOwner + { + // TODO remove riskpoolId parameter in service method (and infer it from sender address) + uint256 riskpoolId = getId(); + _riskpoolService.setMaximumNumberOfActiveBundles(riskpoolId, maximumNumberOfActiveBundles); + // after action hook for child contracts + _afterSetMaximumActiveBundles(maximumNumberOfActiveBundles); + + emit LogMaximumNumberOfActiveBundlesSet(maximumNumberOfActiveBundles); + } + + function getMaximumNumberOfActiveBundles() + public view override + returns(uint256 maximumNumberOfActiveBundles) + { + uint256 riskpoolId = getId(); + return _instanceService.getMaximumNumberOfActiveBundles(riskpoolId); + } + + function getWallet() public view override returns(address) { + return _wallet; + } + + function getErc20Token() public view override returns(address) { + return _erc20Token; + } + + function getSumOfSumInsuredCap() public view override returns (uint256) { + return _sumOfSumInsuredCap; + } + + function getFullCollateralizationLevel() public pure override returns (uint256) { + return FULL_COLLATERALIZATION_LEVEL; + } + + function getCollateralizationLevel() public view override returns (uint256) { + return _collateralization; + } + + function bundles() public override view returns(uint256) { + return _bundleIds.length; + } + + function getBundleId(uint256 idx) public override view returns(uint256 bundleId) { + require(idx < _bundleIds.length, "ERROR:RPL-007:BUNDLE_INDEX_TOO_LARGE"); + bundleId = _bundleIds[idx]; + } + + function activeBundles() public override view returns(uint256) { + uint256 riskpoolId = getId(); + return _instanceService.activeBundles(riskpoolId); + } + + function getActiveBundleId(uint256 idx) public override view returns(uint256 bundleId) { + uint256 riskpoolId = getId(); + require(idx < _instanceService.activeBundles(riskpoolId), "ERROR:RPL-008:ACTIVE_BUNDLE_INDEX_TOO_LARGE"); + + return _instanceService.getActiveBundleId(riskpoolId, idx); + } + + function getFilterDataStructure() external override virtual pure returns(string memory) { + return DEFAULT_FILTER_DATA_STRUCTURE; + } + + function getCapital() public override view returns(uint256) { + uint256 riskpoolId = getId(); + return _instanceService.getCapital(riskpoolId); + } + + function getTotalValueLocked() public override view returns(uint256) { + uint256 riskpoolId = getId(); + return _instanceService.getTotalValueLocked(riskpoolId); + } + + function getCapacity() public override view returns(uint256) { + uint256 riskpoolId = getId(); + return _instanceService.getCapacity(riskpoolId); + } + + function getBalance() public override view returns(uint256) { + uint256 riskpoolId = getId(); + return _instanceService.getBalance(riskpoolId); + } + + // change: no longer view to allow for log entries in derived contracts + function bundleMatchesApplication( + IBundle.Bundle memory bundle, + IPolicy.Application memory application + ) public override virtual view returns(bool isMatching); + + function _afterArchive() internal view override { + uint256 riskpoolId = getId(); + require( + _instanceService.unburntBundles(riskpoolId) == 0, + "ERROR:RPL-010:RISKPOOL_HAS_UNBURNT_BUNDLES" + ); + } + + // after action hooks for child contracts + function _afterSetMaximumActiveBundles(uint256 numberOfBundles) internal virtual {} + function _afterCreateBundle(uint256 bundleId, bytes memory filter, uint256 initialAmount) internal virtual {} + function _afterFundBundle(uint256 bundleId, uint256 amount) internal virtual {} + function _afterDefundBundle(uint256 bundleId, uint256 amount) internal virtual {} + + function _afterLockBundle(uint256 bundleId) internal virtual {} + function _afterUnlockBundle(uint256 bundleId) internal virtual {} + function _afterCloseBundle(uint256 bundleId) internal virtual {} + function _afterBurnBundle(uint256 bundleId) internal virtual {} + + // abstract functions to implement by concrete child contracts + function _lockCollateral(bytes32 processId, uint256 collateralAmount) internal virtual returns(bool success); + function _processPremium(bytes32 processId, uint256 amount) internal virtual; + function _processPayout(bytes32 processId, uint256 amount) internal virtual; + function _releaseCollateral(bytes32 processId) internal virtual returns(uint256 collateralAmount); +} \ No newline at end of file diff --git a/contracts/services/InstanceService.sol b/contracts/services/InstanceService.sol index 29b2137..e5a0fcb 100644 --- a/contracts/services/InstanceService.sol +++ b/contracts/services/InstanceService.sol @@ -325,4 +325,9 @@ contract InstanceService is function getFeeFractionFullUnit() external override view returns(uint256) { return _treasury.getFractionFullUnit(); } + + // TODO add to gif-interfaces IInstanceService + function getFeeSpecification(uint256 componentId) external view returns(ITreasury.FeeSpecification memory feeSpecification) { + return _treasury.getFeeSpecification(componentId); + } } diff --git a/scripts/deploy_depeg.py b/scripts/deploy_depeg.py index 56dd47c..4c4d09b 100644 --- a/scripts/deploy_depeg.py +++ b/scripts/deploy_depeg.py @@ -264,34 +264,31 @@ def deploy( print('====== create initial setup ======') - bundleInitialFunding = INITIAL_ERC20_BUNDLE_FUNDING - print('1) investor {} funding (transfer/approve) with {} token for erc20 {}'.format( - investor, bundleInitialFunding, erc20Token)) - - erc20Token.transfer(investor, bundleInitialFunding, {'from': instanceOperator}) - erc20Token.approve(instance.getTreasury(), bundleInitialFunding, {'from': investor}) + print('1) set up bundles for erc20') + tmp_d = {} + tmp_d['instance'] = instance + tmp_d[INSTANCE_OPERATOR] = instanceOperator + tmp_d[INSTANCE_SERVICE] = instanceService + tmp_d[PRODUCT] = product + tmp_d[CUSTOMER1] = customer + tmp_d[INVESTOR] = investor + tmp_d[RISKPOOL] = riskpool + initialFunding = 100000 + maxSumInsured = 20000 print('2) riskpool wallet {} approval for instance treasury {}'.format( riskpoolWallet, instance.getTreasury())) - erc20Token.approve(instance.getTreasury(), bundleInitialFunding, {'from': riskpoolWallet}) + erc20Token.approve(instance.getTreasury(), 10 * initialFunding, {'from': riskpoolWallet}) print('3) riskpool bundle creation by investor {}'.format( investor)) - - policyMinSumInsured = 1000 - policyMaxSumInsured = 50000 - policyMinDuration = 30 * 24 * 3600 - policyMaxDuration = 90 * 24 * 3600 - annualPercentageReturn = riskpool.getApr100PercentLevel() / 20; - riskpool.createBundle( - policyMinSumInsured, - policyMaxSumInsured, - policyMinDuration, - policyMaxDuration, - annualPercentageReturn, - bundleInitialFunding, - {'from': investor}) + + new_bundle(tmp_d, initialFunding, 8000, maxSumInsured, 60, 90, 1.7) + new_bundle(tmp_d, initialFunding, 4000, maxSumInsured, 30, 80, 2.1) + new_bundle(tmp_d, initialFunding, 5000, maxSumInsured, 14, 30, 3.3) + new_bundle(tmp_d, initialFunding, 2000, maxSumInsured, 20, 60, 4.2) + new_bundle(tmp_d, initialFunding, 1000, maxSumInsured, 10, 45, 5.0) customerFunding=1000 print('5) customer {} funding (transfer/approve) with {} token for erc20 {}'.format( @@ -305,7 +302,7 @@ def deploy( duration = 50 maxPremium = 1000 print('6) policy creation for customers {}'.format(customer)) - processId = new_policy(product, customer, sumInsured, duration, maxPremium) + processId = new_policy(tmp_d, sumInsured, duration, maxPremium) deploy_result = { INSTANCE_OPERATOR: instanceOperator, @@ -364,7 +361,7 @@ def deploy( def help(): - print('from scripts.deploy_depeg import all_in_1, new_bundle, new_policy, inspect_bundle, inspect_applications, help') + print('from scripts.deploy_depeg import all_in_1, new_bundle, best_quote, new_policy, inspect_bundle, inspect_bundles, inspect_applications, help') print('(customer, product, riskpool, riskpoolWallet, usd1, instanceService, processId, d) = all_in_1()') print('instanceService.getPolicy(processId)') print('instanceService.getBundle(1)') @@ -422,13 +419,39 @@ def new_bundle( {'from': investor}) +def best_quote( + d, + sumInsured, + durationDays, +) -> int: + product = d[PRODUCT] + customer = d[CUSTOMER1] + instanceService = d[INSTANCE_SERVICE] + + duration = durationDays * 24 * 3600 + tx = product.getBestQuote(sumInsured, duration, {'from': customer}) + netPremium = tx.return_value + + feeStructure = instanceService.getFeeSpecification(product.getId()) + feeFullUnit = instanceService.getFeeFractionFullUnit() + feeFixedAmount = feeStructure[1] + feeFractionPercentage = feeStructure[2]/feeFullUnit + + premium = (netPremium + feeFixedAmount)/(1.0 - feeFractionPercentage) + feeFractionAmount = int(premium * feeStructure[2]/feeFullUnit) + + print('premium {} (net premium {}, fixed fee {}, fractional fee {:.2f} ({:.2}%))'.format( + premium, netPremium, feeFixedAmount, feeFractionAmount, 100 * feeFractionPercentage)) + + def new_policy( - product, - customer, + d, sumInsured, durationDays, maxPremium ) -> str: + product = d[PRODUCT] + customer = d[CUSTOMER1] duration = durationDays*24*3600 tx = product.applyForPolicy(sumInsured, duration, maxPremium, {'from': customer}) @@ -499,14 +522,14 @@ def inspect_bundles(d): instanceService = d[INSTANCE_SERVICE] riskpool = d[RISKPOOL] riskpoolId = riskpool.getId() - activeBundles = instanceService.activeBundles(riskpoolId) + activeBundleIds = riskpool.getActiveBundleIds() # print header row - print('i riskpool bundle minsuminsured maxsuminsured minduration maxduration apr capital locked capacity') + print('i riskpool bundle apr minsuminsured maxsuminsured minduration maxduration capital locked capacity') # print individual rows - for idx in range(activeBundles): - bundleId = instanceService.getActiveBundleId(riskpoolId, idx) + for idx in range(len(activeBundleIds)): + bundleId = activeBundleIds[idx] bundle = instanceService.getBundle(bundleId) filter = bundle[4] ( @@ -522,19 +545,21 @@ def inspect_bundles(d): capital = bundle[5] locked = bundle[6] capacity = bundle[5]-bundle[6] + policies = riskpool.getActivePolicies(bundleId) - print('{} {} {} {} {} {} {} {} {} {} {}'.format( + print('{} {} {} {:.3f} {} {} {} {} {} {} {} {}'.format( idx, riskpoolId, bundleId, + apr, minSumInsured, maxSumInsured, minDuration/(24*3600), maxDuration/(24*3600), - apr, capital, locked, - capacity + capacity, + policies ))