From 03060291664c8a3e3820a22522aab6e004532649 Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Fri, 4 Aug 2023 07:30:04 +0000 Subject: [PATCH 01/18] amend deploay_ayii with help() and deploy_product_riskpool() --- scripts/ayii_product.py | 38 ++- scripts/deploy_ayii.py | 513 ++++++++++++++++++++++++++++++++-------- scripts/util.py | 26 ++ 3 files changed, 469 insertions(+), 108 deletions(-) diff --git a/scripts/ayii_product.py b/scripts/ayii_product.py index d195172..9db26de 100644 --- a/scripts/ayii_product.py +++ b/scripts/ayii_product.py @@ -6,6 +6,7 @@ from brownie.network.account import Account from brownie import ( + history, Wei, Contract, PolicyController, @@ -20,6 +21,7 @@ ) from scripts.util import ( + wait_for_confirmations, get_account, encode_function_data, # s2h, @@ -74,7 +76,10 @@ def __init__(self, instance.getRegistry(), {'from': riskpoolKeeper}, publish_source=publishSource) - + + tx = history[-1] + wait_for_confirmations(tx) + print('3) investor role granting to investor {} by riskpool keeper {}'.format( investor, riskpoolKeeper)) @@ -90,6 +95,9 @@ def __init__(self, self.riskpool, {'from': riskpoolKeeper}) + tx = history[-1] + wait_for_confirmations(tx) + print('5) approval of riskpool id {} by instance operator {}'.format( self.riskpool.getId(), instance.getOwner())) @@ -190,7 +198,7 @@ def __init__(self, oracleProvider)) self.oracle = AyiiOracle.deploy( - s2b32('AyiiOracle'), + s2b32(name), instance.getRegistry(), chainLinkTokenAddress, chainLinkOracleAddress, @@ -199,6 +207,9 @@ def __init__(self, {'from': oracleProvider}, publish_source=publishSource) + tx = history[-1] + wait_for_confirmations(tx) + print('6) oracle {} proposing to instance by oracle provider {}'.format( self.oracle, oracleProvider)) @@ -206,6 +217,9 @@ def __init__(self, self.oracle, {'from': oracleProvider}) + tx = history[-1] + wait_for_confirmations(tx) + print('7) approval of oracle id {} by instance operator {}'.format( self.oracle.getId(), instance.getOwner())) @@ -260,7 +274,7 @@ def __init__(self, productOwner)) self.product = AyiiProduct.deploy( - s2b32('AyiiProduct'), + s2b32(name), registry, erc20Token.address, oracle.getId(), @@ -269,6 +283,9 @@ def __init__(self, {'from': productOwner}, publish_source=publishSource) + tx = history[-1] + wait_for_confirmations(tx) + print('3) product {} proposing to instance by product owner {}'.format( self.product, productOwner)) @@ -276,6 +293,9 @@ def __init__(self, self.product, {'from': productOwner}) + tx = history[-1] + wait_for_confirmations(tx) + print('4) approval of product id {} by instance operator {}'.format( self.product.getId(), instance.getOwner())) @@ -291,8 +311,13 @@ def __init__(self, erc20Token, {'from': instance.getOwner()}) - fixedFee = 3 - fractionalFee = instanceService.getFeeFractionFullUnit() / 10 # corresponds to 10% + # fixedFee = 3 + # fractionalFee = instanceService.getFeeFractionFullUnit() / 10 # corresponds to 10% + + # set fees to zero + fixedFee = 0 + fractionalFee = 0 + print('6) creating premium fee spec (fixed: {}, fractional: {}) for product id {} by instance operator {}'.format( fixedFee, fractionalFee, self.product.getId(), instance.getOwner())) @@ -342,6 +367,7 @@ def __init__(self, investor: Account, erc20Token: Account, riskpoolWallet: Account, + collateralizationLevel: int, baseName='Ayii', publishSource=False ): @@ -358,7 +384,7 @@ def __init__(self, riskpoolKeeper, riskpoolWallet, investor, - instanceService.getFullCollateralizationLevel(), + collateralizationLevel, '{}Riskpool'.format(baseName), publishSource) diff --git a/scripts/deploy_ayii.py b/scripts/deploy_ayii.py index ee1fc16..3ffa60c 100644 --- a/scripts/deploy_ayii.py +++ b/scripts/deploy_ayii.py @@ -1,3 +1,7 @@ +from datetime import datetime + +from brownie import web3 + from brownie.network import accounts from brownie.network.account import Account @@ -43,16 +47,25 @@ PROCESS_ID1 = 'processId1' PROCESS_ID2 = 'processId2' -REQUIRED_FUNDS_S = 50000000000000000 -REQUIRED_FUNDS_M = 150000000000000000 -REQUIRED_FUNDS_L = 1500000000000000000 +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, INSURER: REQUIRED_FUNDS_M, - ORACLE_PROVIDER: REQUIRED_FUNDS_M, + ORACLE_PROVIDER: int(1.2 * REQUIRED_FUNDS_M), RISKPOOL_KEEPER: REQUIRED_FUNDS_M, RISKPOOL_WALLET: REQUIRED_FUNDS_S, INVESTOR: REQUIRED_FUNDS_S, @@ -60,19 +73,50 @@ CUSTOMER2: REQUIRED_FUNDS_S, } + +def help(): + print('from scripts.util import s2b, b2s, contract_from_address') + print('from scripts.deploy_ayii import stakeholders_accounts_ganache, check_funds, amend_funds, deploy, deploy_product_riskpool, from_registry, from_component, verify_deploy') + print() + print('#--- deploy ganache setup ---------------------------------------#') + print('a = stakeholders_accounts_ganache()') + print("instance_operator = a['instanceOperator']") + print("token = TestCoin.deploy({'from': instance_operator})") + print() + print('check_funds(a, token)') + print('# amend_funds(a)') + print('d = deploy(a, token, False)') + print() + print("(instance, product, oracle, riskpool) = from_registry(d['instance'].getRegistry())") + print("verify_deploy(a, token, instance.getRegistry())") + print() + print('#--- deploy to existing instance --------------------------------#') + print('check_funds(a, token)') + print("registry_address = d['instance'].getRegistry()") + print("registry_address = instance.getRegistry().address") + print('collateralization_level = 0') + print() + print('product_old = product') + print('oracle_old = oracle') + print('riskpool_old = riskpool') + print() + print('(instance, product, oracle, riskpool) = deploy_product_riskpool(registry_address, a, token, collateralization_level)') + + + def stakeholders_accounts_ganache(): - # define stakeholder accounts - instanceOperator=accounts[0] - instanceWallet=accounts[1] - oracleProvider=accounts[2] - chainlinkNodeOperator=accounts[3] - riskpoolKeeper=accounts[4] - riskpoolWallet=accounts[5] - investor=accounts[6] - productOwner=accounts[7] - insurer=accounts[8] - customer=accounts[9] - customer2=accounts[10] + # define stakeholder accounts + instanceOperator = accounts[0] + instanceWallet = accounts[1] + oracleProvider = accounts[2] + chainlinkNodeOperator = accounts[3] + riskpoolKeeper = accounts[4] + riskpoolWallet = accounts[5] + investor = accounts[6] + productOwner = accounts[7] + insurer = accounts[8] + customer = accounts[9] + customer2 = accounts[10] return { INSTANCE_OPERATOR: instanceOperator, @@ -89,38 +133,88 @@ def stakeholders_accounts_ganache(): } -def check_funds(stakeholders_accounts): +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() + 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 to cover missing funds'.format(INSTANCE_OPERATOR)) + print('{} sufficiently funded with native token to cover missing funds'.format( + INSTANCE_OPERATOR)) else: - print('{} needs additional funding of {} to cover missing funds'.format( + 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, - REQUIRED_FUNDS[INSTANCE_OPERATOR] + fundsMissing - a[INSTANCE_OPERATOR].balance() + 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() + 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 = {} @@ -132,83 +226,254 @@ def _get_balances(stakeholders_accounts): def _get_balances_delta(balances_before, balances_after): - balance_delta = { 'total': 0 } + balance_delta = {'total': 0} for accountName, account in balances_before.items(): - balance_delta[accountName] = balances_before[accountName] - balances_after[accountName] + 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)) + 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)) + 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, + stakeholders_accounts, + erc20_token, publishSource=False ): - return deploy(stakeholders_accounts, None) + return deploy(stakeholders_accounts, erc20_token, None) + + +def verify_deploy( + stakeholders_accounts, + erc20_token, + registry_address +): + # define stakeholder accounts + a = stakeholders_accounts + instanceOperator = a[INSTANCE_OPERATOR] + instanceWallet = a[INSTANCE_WALLET] + oracleProvider = a[ORACLE_PROVIDER] + chainlinkNodeOperator = a[NODE_OPERATOR] + riskpoolKeeper = a[RISKPOOL_KEEPER] + riskpoolWallet = a[RISKPOOL_WALLET] + investor = a[INVESTOR] + productOwner = a[PRODUCT_OWNER] + insurer = a[INSURER] + customer = a[CUSTOMER1] + customer2 = a[CUSTOMER2] + + ( + instance, + product, + oracle, + riskpool + ) = from_registry(registry_address) + + instanceService = instance.getInstanceService() + riskpoolId = 1 + oracleId = 2 + productId = 3 + + verify_element('Registry', instanceService.getRegistry(), registry_address) + verify_element('InstanceOperator', + instanceService.getInstanceOperator(), instanceOperator) + verify_element('InstanceWallet', + instanceService.getInstanceWallet(), instanceWallet) + + verify_element('RiskpoolId', riskpool.getId(), riskpoolId) + verify_element( + 'RiskpoolType', instanceService.getComponentType(riskpoolId), 2) + verify_element('RiskpoolState', + instanceService.getComponentState(riskpoolId), 3) + verify_element('RiskpoolKeeper', riskpool.owner(), riskpoolKeeper) + verify_element('RiskpoolWallet', instanceService.getRiskpoolWallet( + riskpoolId), riskpoolWallet) + verify_element('RiskpoolBalance', instanceService.getBalance( + riskpoolId), erc20_token.balanceOf(riskpoolWallet)) + verify_element('RiskpoolToken', riskpool.getErc20Token(), + erc20_token.address) + + verify_element('OracleId', oracle.getId(), oracleId) + verify_element('OracleType', instanceService.getComponentType(oracleId), 0) + verify_element( + 'OracleState', instanceService.getComponentState(oracleId), 3) + verify_element('OracleProvider', oracle.owner(), oracleProvider) + + verify_element('ProductId', product.getId(), productId) + verify_element( + 'ProductType', instanceService.getComponentType(productId), 1) + verify_element( + 'ProductState', instanceService.getComponentState(productId), 3) + verify_element('ProductOwner', product.owner(), productOwner) + verify_element('ProductToken', product.getToken(), erc20_token.address) + verify_element('ProductRiskpool', product.getRiskpoolId(), riskpoolId) + + print('InstanceWalletBalance {:.2f}'.format(erc20_token.balanceOf( + instanceService.getInstanceWallet())/10**erc20_token.decimals())) + print('RiskpoolWalletTVL {:.2f}'.format( + instanceService.getTotalValueLocked(riskpoolId)/10**erc20_token.decimals())) + print('RiskpoolWalletCapacity {:.2f}'.format( + instanceService.getCapacity(riskpoolId)/10**erc20_token.decimals())) + print('RiskpoolWalletBalance {:.2f}'.format(erc20_token.balanceOf( + instanceService.getRiskpoolWallet(riskpoolId))/10**erc20_token.decimals())) + print('RiskpoolBundles {}'.format(riskpool.bundles())) + + bundle_id = riskpool.getBundleId(0) + print('RiskpoolBundle[0] {}'.format(instanceService.getBundle(bundle_id).dict())) + print('ProductRisks {}'.format(product.risks())) + print('ProductApplications {}'.format(product.applications())) + + +def verify_element( + element, + value, + expected_value +): + if value == expected_value: + print('{} OK {}'.format(element, value)) + else: + print('{} ERROR {} expected {}'.format(element, value, expected_value)) + + +def deploy_product_riskpool( + registry_address, + stakeholders_accounts, + erc20_token, + collateralizaionLevel, + publishSource=False +): + # define stakeholder accounts + a = stakeholders_accounts + instanceOperator = a[INSTANCE_OPERATOR] + instanceWallet = a[INSTANCE_WALLET] + oracleProvider = a[ORACLE_PROVIDER] + chainlinkNodeOperator = a[NODE_OPERATOR] + riskpoolKeeper = a[RISKPOOL_KEEPER] + riskpoolWallet = a[RISKPOOL_WALLET] + investor = a[INVESTOR] + productOwner = a[PRODUCT_OWNER] + insurer = a[INSURER] + customer = a[CUSTOMER1] + customer2 = a[CUSTOMER2] + + # create basename including unix timestamp + baseName = 'Ayii_{}_'.format(int(datetime.now().timestamp())) + + if not check_funds(a, erc20_token): + print('ERROR: insufficient funding, aborting deploy') + return + + if not erc20_token: + print('ERROR: no erc20 defined, aborting deploy') + return + + print('====== setting erc20 token to {} ======'.format(erc20_token)) + erc20Token = erc20_token + + print('====== getting instance from registry address {} ======'.format(registry_address)) + (instance, product, oracle, riskpool) = from_registry(registry_address) + + + print("====== deploy ayii product /w base name '{}' and riskpool collateralization level {} ======".format(baseName, collateralizaionLevel)) + ayiiDeploy = GifAyiiProductComplete( + instance, productOwner, insurer, oracleProvider, chainlinkNodeOperator, + riskpoolKeeper, investor, erc20Token, riskpoolWallet, collateralizaionLevel, + baseName=baseName, publishSource=publishSource) + + ayiiProduct = ayiiDeploy.getProduct() + ayiiOracle = ayiiProduct.getOracle() + ayiiRiskpool = ayiiProduct.getRiskpool() + + product = ayiiProduct.getContract() + oracle = ayiiOracle.getContract() + riskpool = ayiiRiskpool.getContract() + + print("====== create bundle for investor {} ======".format(investor)) + initial_funding = 10**erc20Token.decimals() + bundle_filter = b'' + erc20Token.transfer(investor, initial_funding, {'from': instanceOperator}) + erc20Token.approve(instance.getTreasury().address, initial_funding, {'from': investor}) + riskpool.createBundle(bundle_filter, initial_funding, {'from': investor}) + + return ( + instance, + product, + oracle, + riskpool + ) def deploy( - stakeholders_accounts, + stakeholders_accounts, erc20_token, publishSource=False ): # define stakeholder accounts a = stakeholders_accounts - instanceOperator=a[INSTANCE_OPERATOR] - instanceWallet=a[INSTANCE_WALLET] - oracleProvider=a[ORACLE_PROVIDER] - chainlinkNodeOperator=a[NODE_OPERATOR] - riskpoolKeeper=a[RISKPOOL_KEEPER] - riskpoolWallet=a[RISKPOOL_WALLET] - investor=a[INVESTOR] - productOwner=a[PRODUCT_OWNER] - insurer=a[INSURER] - customer=a[CUSTOMER1] - customer2=a[CUSTOMER2] + instanceOperator = a[INSTANCE_OPERATOR] + instanceWallet = a[INSTANCE_WALLET] + oracleProvider = a[ORACLE_PROVIDER] + chainlinkNodeOperator = a[NODE_OPERATOR] + riskpoolKeeper = a[RISKPOOL_KEEPER] + riskpoolWallet = a[RISKPOOL_WALLET] + investor = a[INVESTOR] + productOwner = a[PRODUCT_OWNER] + insurer = a[INSURER] + 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('====== deploy erc20 test token ======') - erc20Token = TestCoin.deploy({'from': instanceOperator}) - else: - print('====== setting erc20 token to {} ======'.format(erc20_token)) - erc20Token = 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) + instance = GifInstance( + instanceOperator, instanceWallet=instanceWallet, publishSource=publishSource) instanceService = instance.getInstanceService() instanceOperatorService = instance.getInstanceOperatorService() componentOwnerService = instance.getComponentOwnerService() print('====== deploy ayii product ======') - ayiiDeploy = GifAyiiProductComplete(instance, productOwner, insurer, oracleProvider, chainlinkNodeOperator, riskpoolKeeper, investor, erc20Token, riskpoolWallet) + collateralizationLevel = instanceService.getFullCollateralizationLevel() + ayiiDeploy = GifAyiiProductComplete(instance, productOwner, insurer, oracleProvider, chainlinkNodeOperator, + riskpoolKeeper, investor, erc20Token, riskpoolWallet, collateralizationLevel, + publishSource=publishSource) # assess balances at beginning of deploy balances_after_deploy = _get_balances(stakeholders_accounts) @@ -223,38 +488,40 @@ def deploy( print('====== create initial setup ======') - bundleInitialFunding=1000000 + 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}) - maxUint256 = 2**256-1 + 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(), maxUint256, {'from': riskpoolWallet}) + + erc20Token.approve(instance.getTreasury(), bundleInitialFunding, { + 'from': riskpoolWallet}) print('3) riskpool bundle creation by investor {}'.format( investor)) applicationFilter = bytes(0) riskpool.createBundle( - applicationFilter, - bundleInitialFunding, - {'from': investor}) + applicationFilter, + bundleInitialFunding, + {'from': investor}) # create risks projectId = s2b32('2022.kenya.wfp.ayii') uaiId = [s2b32('1234'), s2b32('2345')] cropId = s2b32('mixed') - + triggerFloat = 0.75 exitFloat = 0.1 tsiFloat = 0.9 aphFloat = [2.0, 1.8] - + multiplier = product.getPercentageMultiplier() trigger = multiplier * triggerFloat exit_ = multiplier * exitFloat @@ -265,18 +532,21 @@ def deploy( insurer)) tx = [None, None] - tx[0] = product.createRisk(projectId, uaiId[0], cropId, trigger, exit_, tsi, aph[0], {'from': insurer}) - tx[1] = product.createRisk(projectId, uaiId[1], cropId, trigger, exit_, tsi, aph[1], {'from': insurer}) + tx[0] = product.createRisk( + projectId, uaiId[0], cropId, trigger, exit_, tsi, aph[0], {'from': insurer}) + tx[1] = product.createRisk( + projectId, uaiId[1], cropId, trigger, exit_, tsi, aph[1], {'from': insurer}) riskId1 = tx[0].events['LogAyiiRiskDataCreated']['riskId'] riskId2 = tx[1].events['LogAyiiRiskDataCreated']['riskId'] - customerFunding=1000 + customerFunding = 1000 print('5) customer {} funding (transfer/approve) with {} token for erc20 {}'.format( - investor, customerFunding, erc20Token)) + customer, customerFunding, erc20Token)) erc20Token.transfer(customer, customerFunding, {'from': instanceOperator}) - erc20Token.approve(instance.getTreasury(), customerFunding, {'from': customer}) + erc20Token.approve(instance.getTreasury(), + customerFunding, {'from': customer}) # policy creation premium = [300, 400] @@ -284,8 +554,10 @@ def deploy( print('6) policy creation (2x) for customers {}, {} by insurer {}'.format( customer, customer2, insurer)) - tx[0] = product.applyForPolicy(customer, premium[0], sumInsured[0], riskId1, {'from': insurer}) - tx[1] = product.applyForPolicy(customer2, premium[1], sumInsured[1], riskId2, {'from': insurer}) + tx[0] = product.applyForPolicy( + customer, premium[0], sumInsured[0], riskId1, {'from': insurer}) + tx[1] = product.applyForPolicy( + customer2, premium[1], sumInsured[1], riskId2, {'from': insurer}) processId1 = tx[0].events['LogAyiiPolicyCreated']['policyId'] processId2 = tx[1].events['LogAyiiPolicyCreated']['policyId'] @@ -302,7 +574,7 @@ def deploy( INSURER: insurer, CUSTOMER1: customer, CUSTOMER2: customer2, - ERC20_TOKEM: contract_from_address(TestCoin, erc20Token), + 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), @@ -330,7 +602,8 @@ def 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_setup = _get_balances_delta( + balances_after_deploy, balances_after_setup) delta_total = _get_balances_delta(balances_before, balances_after_setup) print('--------------------------------------------------------------------') @@ -358,7 +631,12 @@ def from_component(componentAddress): return from_registry(component.getRegistry()) -def from_registry(registryAddress): +def from_registry( + registryAddress, + productId=0, + oracleId=0, + riskpoolId=0 +): instance = GifInstance(registryAddress=registryAddress) instanceService = instance.getInstanceService() @@ -371,37 +649,67 @@ def from_registry(registryAddress): riskpool = None if products >= 1: - if products > 1: - print('1 product expected, {} product available'.format(products)) - print('returning last product available') - - componentId = instanceService.getProductId(products-1) - component = instanceService.getComponent(componentId) - product = contract_from_address(AyiiProduct, component) + 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 producta available') + print('1 product expected, no product available') print('no product returned (None)') if oracles >= 1: - if oracles > 1: - print('1 oracle expected, {} oracles available'.format(oracles)) - print('returning last oracle available') - - componentId = instanceService.getOracleId(oracles-1) - component = instanceService.getComponent(componentId) - oracle = contract_from_address(AyiiOracle, component) + if oracleId > 0: + componentId = oracleId + else: + componentId = instanceService.getOracleId(oracles-1) + + if oracles > 1: + print('1 oracle expected, {} oracles available'.format(oracles)) + print('returning last oracle available') + + componentAddress = instanceService.getComponent(componentId) + oracle = contract_from_address(AyiiOracle, componentAddress) + + if oracle.getType() != 0: + oracle = None + print('component (type={}) with id {} is not oracle'.format( + component.getType(), componentId)) + print('no oracle returned (None)') else: print('1 oracle expected, no oracles available') print('no oracle returned (None)') if riskpools >= 1: - if riskpools > 1: - print('1 riskpool expected, {} riskpools available'.format(riskpools)) - print('returning last riskpool available') - - componentId = instanceService.getRiskpoolId(riskpools-1) - component = instanceService.getComponent(componentId) - riskpool = contract_from_address(AyiiRiskpool, component) + 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)') @@ -415,7 +723,7 @@ def dry_run_create_risks(product, insurer): trigger = 0.75 tsi = 0.9 - exit_ = 0.1 + exit_ = 0.1 aez = [ 22, @@ -440,15 +748,16 @@ def dry_run_create_risks(product, insurer): 1.84, 2.60, 2.30] - + print("project, aez, crop, trigger, exit, tsi, aph, riskId") for i in range(len(aez)): - riskId = create_risk(product, insurer, project, aez[i], crop, trigger, exit_, tsi, aph[i]) + riskId = create_risk(product, insurer, project, + aez[i], crop, trigger, exit_, tsi, aph[i]) print(project, aez[i], crop, trigger, exit_, tsi, aph[i], riskId) def create_risk(product, insurer, project, uai, crop, trigger, exit_, tsi, aph): - + multiplier = product.getPercentageMultiplier() triggerInt = multiplier * trigger exitInt = multiplier * exit_ @@ -459,9 +768,9 @@ def create_risk(product, insurer, project, uai, crop, trigger, exit_, tsi, aph): s2b32(project), s2b32(str(uai)), s2b32(crop), - triggerInt, - exitInt, - tsiInt, + triggerInt, + exitInt, + tsiInt, aphInt, {'from': insurer} ) diff --git a/scripts/util.py b/scripts/util.py index fe87357..fede77a 100644 --- a/scripts/util.py +++ b/scripts/util.py @@ -1,6 +1,8 @@ from web3 import Web3 from brownie import ( + web3, + network, Contract, CoreProxy, ) @@ -9,6 +11,16 @@ from brownie.network import accounts from brownie.network.account import Account +CHAIN_ID_MUMBAI = 80001 +CHAIN_ID_FUJI = 43113 +CHAIN_ID_GOERLI = 5 + +CHAIN_ID_AVAX = 43114 +CHAIN_ID_MAINNET = 1 + +CHAIN_IDS_REQUIRING_CONFIRMATIONS = [CHAIN_ID_MUMBAI, CHAIN_ID_FUJI, CHAIN_ID_GOERLI, CHAIN_ID_AVAX, CHAIN_ID_MAINNET] + + def s2h(text: str) -> str: return Web3.toHex(text.encode('ascii')) @@ -39,6 +51,20 @@ def get_account(mnemonic: str, account_offset: int) -> Account: count=1, offset=account_offset) + +def wait_for_confirmations(tx): + if web3.chain_id in CHAIN_IDS_REQUIRING_CONFIRMATIONS: + if not is_forked_network(): + print('waiting for confirmations ...') + tx.wait(2) + else: + print('not waiting for confirmations in a forked network...') + + +def is_forked_network(): + return 'fork' in network.show_active() + + # source: https://github.com/brownie-mix/upgrades-mix/blob/main/scripts/helpful_scripts.py def encode_function_data(*args, initializer=None): """Encodes the function call so we can work with an initializer. From 7362867d98b26da388ddbdc07fa66ffc30d00f05 Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Fri, 4 Aug 2023 08:22:44 +0000 Subject: [PATCH 02/18] fix scripts --- scripts/ayii_product.py | 10 +++++----- scripts/deploy_ayii.py | 4 ++-- scripts/instance.py | 3 +++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/scripts/ayii_product.py b/scripts/ayii_product.py index 9db26de..b58fbf7 100644 --- a/scripts/ayii_product.py +++ b/scripts/ayii_product.py @@ -63,7 +63,7 @@ def __init__(self, instanceOperatorService.grantRole( riskpoolKeeperRole, riskpoolKeeper, - {'from': instance.getOwner()}) + {'from': instanceOperatorService.owner()}) print('2) deploy riskpool by riskpool keeper {}'.format( riskpoolKeeper)) @@ -103,7 +103,7 @@ def __init__(self, instanceOperatorService.approve( self.riskpool.getId(), - {'from': instance.getOwner()}) + {'from': instanceOperatorService.owner()}) print('6) riskpool wallet {} set for riskpool id {} by instance operator {}'.format( riskpoolWallet, self.riskpool.getId(), instance.getOwner())) @@ -111,7 +111,7 @@ def __init__(self, instanceOperatorService.setRiskpoolWallet( self.riskpool.getId(), riskpoolWallet, - {'from': instance.getOwner()}) + {'from': instanceOperatorService.owner()}) # 7) setup capital fees fixedFee = 42 @@ -124,14 +124,14 @@ def __init__(self, fixedFee, fractionalFee, b'', - {'from': instance.getOwner()}) + {'from': instanceOperatorService.owner()}) print('8) setting capital fee spec by instance operator {}'.format( instance.getOwner())) instanceOperatorService.setCapitalFees( feeSpec, - {'from': instance.getOwner()}) + {'from': instanceOperatorService.owner()}) def getId(self) -> int: return self.riskpool.getId() diff --git a/scripts/deploy_ayii.py b/scripts/deploy_ayii.py index 3ffa60c..0605cf5 100644 --- a/scripts/deploy_ayii.py +++ b/scripts/deploy_ayii.py @@ -343,8 +343,8 @@ def verify_deploy( instanceService.getRiskpoolWallet(riskpoolId))/10**erc20_token.decimals())) print('RiskpoolBundles {}'.format(riskpool.bundles())) - bundle_id = riskpool.getBundleId(0) - print('RiskpoolBundle[0] {}'.format(instanceService.getBundle(bundle_id).dict())) + # bundle_id = riskpool.getBundleId(0) + print('RiskpoolBundle[0] {}'.format(riskpool.getBundle(0).dict())) print('ProductRisks {}'.format(product.risks())) print('ProductApplications {}'.format(product.applications())) diff --git a/scripts/instance.py b/scripts/instance.py index a3a54b0..80ed43a 100644 --- a/scripts/instance.py +++ b/scripts/instance.py @@ -199,6 +199,9 @@ def contractFromGifRegistry(self, contractClass, name=None): address = self.registry.getContract(nameB32) return contractFromAddress(contractClass, address) + def getOwner(self): + return self.instanceOperatorService.owner() + def getRegistry(self) -> GifRegistry: return self.registry From e038eddc3bd72a0edd491428493fc38fbf42e5f8 Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Fri, 4 Aug 2023 08:52:04 +0000 Subject: [PATCH 03/18] fix test fixture --- scripts/ayii_product.py | 10 +++++----- tests/conftest.py | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/ayii_product.py b/scripts/ayii_product.py index b58fbf7..267c200 100644 --- a/scripts/ayii_product.py +++ b/scripts/ayii_product.py @@ -311,12 +311,12 @@ def __init__(self, erc20Token, {'from': instance.getOwner()}) - # fixedFee = 3 - # fractionalFee = instanceService.getFeeFractionFullUnit() / 10 # corresponds to 10% + fixedFee = 3 + fractionalFee = instanceService.getFeeFractionFullUnit() / 10 # corresponds to 10% - # set fees to zero - fixedFee = 0 - fractionalFee = 0 + # # set fees to zero + # fixedFee = 0 + # fractionalFee = 0 print('6) creating premium fee spec (fixed: {}, fractional: {}) for product id {} by instance operator {}'.format( fixedFee, fractionalFee, self.product.getId(), instance.getOwner())) diff --git a/tests/conftest.py b/tests/conftest.py index 8a77bab..b8f51f2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -218,6 +218,7 @@ def gifAyiiDeploy( testCoin, riskpoolWallet: Account ) -> GifAyiiProductComplete: + collateralizationLevel = 10**18 return GifAyiiProductComplete( instance, productOwner, @@ -227,7 +228,8 @@ def gifAyiiDeploy( riskpoolKeeper, investor, testCoin, - riskpoolWallet) + riskpoolWallet, + collateralizationLevel) @pytest.fixture(scope="module") def gifAyiiProduct(gifAyiiDeploy) -> GifAyiiProduct: From 0699d8217ca4c34fab271b0157b2bb38bec453bc Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Thu, 18 Jan 2024 10:11:34 +0100 Subject: [PATCH 04/18] add UsdcAccounting token --- contracts/examples/UsdcAccounting.sol | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 contracts/examples/UsdcAccounting.sol diff --git a/contracts/examples/UsdcAccounting.sol b/contracts/examples/UsdcAccounting.sol new file mode 100644 index 0000000..6289169 --- /dev/null +++ b/contracts/examples/UsdcAccounting.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.2; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract UsdcAccounting is ERC20 { + + string public constant NAME = "USD Coin - Accounting Token"; + string public constant SYMBOL = "USDC"; + uint256 public constant DECIMALS = 6; + + uint256 public constant INITIAL_SUPPLY = 10**24; + + constructor() + ERC20(NAME, SYMBOL) + { + _mint( + _msgSender(), + INITIAL_SUPPLY + ); + } + + function decimals() external view returns (uint256) { + return DECIMALS; + } +} From c1375c5acadb6ba28ef646ebb701032f0b9b13b7 Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Thu, 18 Jan 2024 09:51:00 +0000 Subject: [PATCH 05/18] adapt devcontainer setup for mac/rosetta --- .devcontainer/Dockerfile | 7 ++++--- contracts/examples/UsdcAccounting.sol | 5 ++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index b3c5706..1b39bd6 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,7 @@ -# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster -ARG VARIANT=3-bullseye -FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT} +ARG VARIANT=3.9-bookworm +# requires rosetta 2 on apple chips +ARG PLATFORM=linux/amd64 +FROM --platform=${PLATFORM} mcr.microsoft.com/devcontainers/python:${VARIANT} # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 ARG NODE_VERSION="16" diff --git a/contracts/examples/UsdcAccounting.sol b/contracts/examples/UsdcAccounting.sol index 6289169..7d278c2 100644 --- a/contracts/examples/UsdcAccounting.sol +++ b/contracts/examples/UsdcAccounting.sol @@ -7,8 +7,7 @@ contract UsdcAccounting is ERC20 { string public constant NAME = "USD Coin - Accounting Token"; string public constant SYMBOL = "USDC"; - uint256 public constant DECIMALS = 6; - + uint8 public constant DECIMALS = 6; uint256 public constant INITIAL_SUPPLY = 10**24; constructor() @@ -20,7 +19,7 @@ contract UsdcAccounting is ERC20 { ); } - function decimals() external view returns (uint256) { + function decimals() public view virtual override returns (uint8) { return DECIMALS; } } From 450c4047cd7b3af6f42f7347f3a5f93bd7a1dc33 Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Thu, 18 Jan 2024 16:02:43 +0000 Subject: [PATCH 06/18] add scripts.util.save_json --- scripts/util.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/util.py b/scripts/util.py index fede77a..361ec14 100644 --- a/scripts/util.py +++ b/scripts/util.py @@ -1,3 +1,4 @@ +import json from web3 import Web3 from brownie import ( @@ -201,3 +202,14 @@ def contractFromAddress(contractClass, contractAddress): def contract_from_address(contractClass, contractAddress): return Contract.from_abi(contractClass._name, contractAddress, contractClass.abi) + +def save_json(contract_class, file_name=None): + vi = contract_class.get_verification_info() + sji = vi['standard_json_input'] + + if not file_name or len(file_name) == 0: + file_name = './{}.json'.format(contract_class._name) + + print('writing standard json input file {}'.format(file_name)) + with open(file_name, "w") as json_file: + json.dump(sji, json_file) From a5a9a67a15366c92edc1d548ea0a950e86348639 Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Sun, 22 Dec 2024 14:18:56 +0100 Subject: [PATCH 07/18] update devcontainer setup --- .devcontainer/Dockerfile | 11 +- .devcontainer/devcontainer.json | 45 +++--- .devcontainer/requirements.txt | 245 ++++++++++++++++++++++++++++++++ .vscode/settings.json | 3 +- 4 files changed, 279 insertions(+), 25 deletions(-) create mode 100644 .devcontainer/requirements.txt diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 1b39bd6..af3831b 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -ARG VARIANT=3.9-bookworm +ARG VARIANT=3.11-bookworm # requires rosetta 2 on apple chips ARG PLATFORM=linux/amd64 FROM --platform=${PLATFORM} mcr.microsoft.com/devcontainers/python:${VARIANT} @@ -32,10 +32,13 @@ RUN echo 'eval "$(mcfly init zsh)"' >> ~/.zshrc \ # Install brownie & other devtools -# COPY requirements.txt /tmp/pip-tmp/ -RUN wget https://raw.githubusercontent.com/eth-brownie/brownie/master/requirements.txt -O /tmp/pip-requirements.txt +# The vyper dependency in the brownie requirements file currently breaks the devcontainer build. +# Should this change in the future consider to re-enable the line below +# RUN wget https://raw.githubusercontent.com/eth-brownie/brownie/master/requirements.txt -O /tmp/pip-requirements.txt -RUN pip install -r /tmp/pip-requirements.txt +RUN mkdir -p /tmp/brownie +COPY .devcontainer/requirements.txt /tmp/brownie +RUN pip install -r /tmp/brownie/requirements.txt RUN pip install eth-brownie \ && pip install fastapi \ && pip install uvicorn diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index cc41558..cb10be4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,11 +5,32 @@ "dockerComposeFile": "docker-compose.yaml", "service": "brownie", "workspaceFolder": "/workspace", + // Set *default* container specific settings.json values on container create. - "settings": { - //"terminal.integrated.shell.linux": "/bin/bash" - "editor.fontFamily": "'JetBrainsMono Nerd Font Mono', Menlo, Monaco, 'Courier New', monospace", - "editor.fontSize": 13, + "customizations": { + "vscode": { + "settings": { + //"terminal.integrated.shell.linux": "/bin/bash" + "editor.fontFamily": "'JetBrainsMono Nerd Font Mono', Menlo, Monaco, 'Courier New', monospace", + "editor.fontSize": 13 + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance", + "juanblanco.solidity", + "tintinweb.solidity-visual-auditor", + "github.vscode-pull-request-github", + "github.copilot", + "mhutchie.git-graph", + "eamodio.gitlens", + "gruntfuggly.todo-tree", + "oderwat.indent-rainbow", + "2gua.rainbow-brackets", + "johnpapa.vscode-peacock", + "vikas.code-navigation" + ] + } }, // "features": { @@ -20,22 +41,6 @@ // } // }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance", - "juanblanco.solidity", - "tintinweb.solidity-visual-auditor", - "github.vscode-pull-request-github", - "github.copilot", - "mhutchie.git-graph", - "eamodio.gitlens", - "gruntfuggly.todo-tree", - "oderwat.indent-rainbow", - "2gua.rainbow-brackets", - "johnpapa.vscode-peacock", - "vikas.code-navigation", - ], // Use 'forwardPorts' to make a list of ports inside the container available locally. "forwardPorts": [8000], diff --git a/.devcontainer/requirements.txt b/.devcontainer/requirements.txt new file mode 100644 index 0000000..6d08031 --- /dev/null +++ b/.devcontainer/requirements.txt @@ -0,0 +1,245 @@ +# Pip file for brownie with vyper package commented out. +# File content from line below. +# RUN wget https://raw.githubusercontent.com/eth-brownie/brownie/master/requirements.txt -O /tmp/pip-requirements.txt + + +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile requirements.in +# +aiohttp==3.9.3 + # via web3 +aiosignal==1.3.1 + # via aiohttp +asttokens==2.4.1 + # via vyper +attrs==23.2.0 + # via + # aiohttp + # hypothesis + # jsonschema + # pytest + # referencing +bitarray==2.9.2 + # via eth-account +black==24.2.0 + # via -r requirements.in +cbor2==5.6.2 + # via vyper +certifi==2024.2.2 + # via requests +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via black +cytoolz==0.12.3 + # via eth-utils +dataclassy==0.11.1 + # via eip712 +eip712==0.2.4 + # via -r requirements.in +eth-abi==5.0.0 + # via + # -r requirements.in + # eip712 + # eth-account + # eth-event + # web3 +eth-account==0.10.0 + # via + # -r requirements.in + # eip712 + # web3 +eth-event==1.2.5 + # via -r requirements.in +eth-hash[pycryptodome]==0.6.0 + # via + # -r requirements.in + # eip712 + # eth-event + # eth-utils + # web3 +eth-keyfile==0.7.0 + # via eth-account +eth-keys==0.5.0 + # via + # eth-account + # eth-keyfile +eth-rlp==1.0.1 + # via eth-account +eth-typing==3.5.2 + # via + # eip712 + # eth-abi + # eth-keys + # eth-utils + # web3 +eth-utils==2.3.1 + # via + # -r requirements.in + # eip712 + # eth-abi + # eth-account + # eth-event + # eth-keyfile + # eth-keys + # eth-rlp + # rlp + # web3 +execnet==2.0.2 + # via pytest-xdist +frozenlist==1.4.1 + # via + # aiohttp + # aiosignal +hexbytes==0.3.1 + # via + # -r requirements.in + # eip712 + # eth-account + # eth-event + # eth-rlp + # web3 +hypothesis==6.27.3 + # via -r requirements.in +idna==3.6 + # via + # requests + # yarl +importlib-metadata==7.0.1 + # via vyper +iniconfig==2.0.0 + # via pytest +jsonschema==4.21.1 + # via web3 +jsonschema-specifications==2023.12.1 + # via jsonschema +lazy-object-proxy==1.10.0 + # via -r requirements.in +lru-dict==1.2.0 + # via web3 +multidict==6.0.5 + # via + # aiohttp + # yarl +mypy-extensions==1.0.0 + # via black +packaging==23.2 + # via + # black + # pytest + # vyper +parsimonious==0.9.0 + # via eth-abi +pathspec==0.12.1 + # via black +platformdirs==4.2.0 + # via black +pluggy==1.4.0 + # via pytest +prompt-toolkit==3.0.43 + # via -r requirements.in +protobuf==4.25.3 + # via web3 +psutil==5.9.8 + # via -r requirements.in +py==1.11.0 + # via + # -r requirements.in + # pytest + # pytest-forked +py-solc-ast==1.2.10 + # via -r requirements.in +py-solc-x==1.1.1 + # via -r requirements.in +pycryptodome==3.20.0 + # via + # eth-hash + # eth-keyfile + # vyper +pygments==2.17.2 + # via + # -r requirements.in + # pygments-lexer-solidity +pygments-lexer-solidity==0.7.0 + # via -r requirements.in +pytest==6.2.5 + # via + # -r requirements.in + # pytest-forked + # pytest-xdist +pytest-forked==1.6.0 + # via pytest-xdist +pytest-xdist==1.34.0 + # via -r requirements.in +python-dotenv==0.16.0 + # via -r requirements.in +pyunormalize==15.1.0 + # via web3 +pyyaml==6.0.1 + # via -r requirements.in +referencing==0.33.0 + # via + # jsonschema + # jsonschema-specifications +regex==2023.12.25 + # via parsimonious +requests==2.31.0 + # via + # -r requirements.in + # py-solc-x + # vvm + # web3 +rlp==4.0.0 + # via + # -r requirements.in + # eth-account + # eth-rlp +rpds-py==0.18.0 + # via + # jsonschema + # referencing +semantic-version==2.10.0 + # via + # -r requirements.in + # py-solc-x + # vvm +six==1.16.0 + # via + # asttokens + # pytest-xdist +sortedcontainers==2.4.0 + # via hypothesis +toml==0.10.2 + # via pytest +toolz==0.12.1 + # via cytoolz +tqdm==4.66.2 + # via -r requirements.in +typing-extensions==4.9.0 + # via + # eth-rlp + # eth-typing + # web3 +urllib3==2.2.1 + # via requests +vvm==0.1.0 + # via -r requirements.in +# vyper==0.3.10 + # via -r requirements.in +wcwidth==0.2.13 + # via prompt-toolkit +web3==6.15.1 + # via -r requirements.in +websockets==12.0 + # via web3 +wheel==0.42.0 + # via vyper +wrapt==1.16.0 + # via -r requirements.in +yarl==1.9.4 + # via aiohttp +zipp==3.17.0 + # via importlib-metadata diff --git a/.vscode/settings.json b/.vscode/settings.json index 735d636..d7bfd16 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,5 +31,6 @@ "titleBar.activeForeground": "#e7e7e7", "titleBar.inactiveBackground": "#1d3c4399", "titleBar.inactiveForeground": "#e7e7e799" - } + }, + "security.olympix.project.includePath": "/contracts" } \ No newline at end of file From accb4bf0cd0e54a1c274af237a837ac7c8bb1d56 Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Sun, 22 Dec 2024 14:58:29 +0000 Subject: [PATCH 08/18] add component ids to verify_deploy script --- scripts/deploy_ayii.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/scripts/deploy_ayii.py b/scripts/deploy_ayii.py index 0605cf5..20dac6c 100644 --- a/scripts/deploy_ayii.py +++ b/scripts/deploy_ayii.py @@ -271,7 +271,10 @@ def deploy_setup_including_token( def verify_deploy( stakeholders_accounts, erc20_token, - registry_address + registry_address, + riskpoolId = 0, + oracleId = 0, + productId = 0 ): # define stakeholder accounts a = stakeholders_accounts @@ -292,31 +295,33 @@ def verify_deploy( product, oracle, riskpool - ) = from_registry(registry_address) + ) = from_registry( + registry_address, + riskpoolId = riskpoolId, + oracleId = oracleId, + productId = productId + ) instanceService = instance.getInstanceService() - riskpoolId = 1 - oracleId = 2 - productId = 3 verify_element('Registry', instanceService.getRegistry(), registry_address) verify_element('InstanceOperator', - instanceService.getInstanceOperator(), instanceOperator) + instanceService.getInstanceOperator(), instanceOperator) verify_element('InstanceWallet', - instanceService.getInstanceWallet(), instanceWallet) + instanceService.getInstanceWallet(), instanceWallet) verify_element('RiskpoolId', riskpool.getId(), riskpoolId) verify_element( 'RiskpoolType', instanceService.getComponentType(riskpoolId), 2) verify_element('RiskpoolState', - instanceService.getComponentState(riskpoolId), 3) + instanceService.getComponentState(riskpoolId), 3) verify_element('RiskpoolKeeper', riskpool.owner(), riskpoolKeeper) verify_element('RiskpoolWallet', instanceService.getRiskpoolWallet( riskpoolId), riskpoolWallet) verify_element('RiskpoolBalance', instanceService.getBalance( riskpoolId), erc20_token.balanceOf(riskpoolWallet)) verify_element('RiskpoolToken', riskpool.getErc20Token(), - erc20_token.address) + erc20_token.address) verify_element('OracleId', oracle.getId(), oracleId) verify_element('OracleType', instanceService.getComponentType(oracleId), 0) From ecb8abf63395b974ab6f5b9706e831747fd4209d Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Wed, 15 Jan 2025 12:08:36 +0000 Subject: [PATCH 09/18] make UsdcAccounting permit based --- contracts/examples/UsdcAccounting.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/examples/UsdcAccounting.sol b/contracts/examples/UsdcAccounting.sol index 7d278c2..5b531da 100644 --- a/contracts/examples/UsdcAccounting.sol +++ b/contracts/examples/UsdcAccounting.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.2; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; -contract UsdcAccounting is ERC20 { +contract UsdcAccounting is ERC20Permit { string public constant NAME = "USD Coin - Accounting Token"; string public constant SYMBOL = "USDC"; @@ -12,6 +13,7 @@ contract UsdcAccounting is ERC20 { constructor() ERC20(NAME, SYMBOL) + ERC20Permit(NAME) { _mint( _msgSender(), From 317f12197fd0fea889dd5fd7d83e9dbaefe3f1cd Mon Sep 17 00:00:00 2001 From: Matthias Zimmermann Date: Wed, 15 Jan 2025 13:25:44 +0100 Subject: [PATCH 10/18] modify accounting token symbol --- contracts/examples/UsdcAccounting.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/examples/UsdcAccounting.sol b/contracts/examples/UsdcAccounting.sol index 5b531da..035a4a0 100644 --- a/contracts/examples/UsdcAccounting.sol +++ b/contracts/examples/UsdcAccounting.sol @@ -7,7 +7,7 @@ import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/draft- contract UsdcAccounting is ERC20Permit { string public constant NAME = "USD Coin - Accounting Token"; - string public constant SYMBOL = "USDC"; + string public constant SYMBOL = "USDC-AT"; uint8 public constant DECIMALS = 6; uint256 public constant INITIAL_SUPPLY = 10**24; From 732c6c9e6420620dac4690ee031293d652808872 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Wed, 15 Jan 2025 21:07:23 +0000 Subject: [PATCH 11/18] CLF mit Gateway contract --- .devcontainer/devcontainer.json | 19 +--- contracts/examples/AyiiClf.sol | 9 ++ contracts/examples/AyiiOracle.sol | 170 +++++++++--------------------- 3 files changed, 61 insertions(+), 137 deletions(-) create mode 100644 contracts/examples/AyiiClf.sol diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index cb10be4..00f60ae 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,17 +3,11 @@ { "name": "gif-contracts", "dockerComposeFile": "docker-compose.yaml", - "service": "brownie", - "workspaceFolder": "/workspace", - + "service": "brownie", + "workspaceFolder": "/workspace", // Set *default* container specific settings.json values on container create. "customizations": { "vscode": { - "settings": { - //"terminal.integrated.shell.linux": "/bin/bash" - "editor.fontFamily": "'JetBrainsMono Nerd Font Mono', Menlo, Monaco, 'Courier New', monospace", - "editor.fontSize": 13 - }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "ms-python.python", @@ -32,7 +26,6 @@ ] } }, - // "features": { // // "github-cli": "latest", // "docker-from-docker": { @@ -40,14 +33,12 @@ // "moby": true // } // }, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [8000], - + "forwardPorts": [ + 8000 + ], // Use 'postCreateCommand' to run commands after the container is created. "postCreateCommand": "touch .env && brownie compile --all && python --version", - // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" } \ No newline at end of file diff --git a/contracts/examples/AyiiClf.sol b/contracts/examples/AyiiClf.sol new file mode 100644 index 0000000..e3b7266 --- /dev/null +++ b/contracts/examples/AyiiClf.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.2; + +interface AyiiClf { + function _sendRequest( + bytes calldata input + ) external returns (bytes32 requestId); +} diff --git a/contracts/examples/AyiiOracle.sol b/contracts/examples/AyiiOracle.sol index c6f6049..82376cd 100644 --- a/contracts/examples/AyiiOracle.sol +++ b/contracts/examples/AyiiOracle.sol @@ -1,151 +1,75 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.2; +// NEU: mit Chainlink Funktion + import "./strings.sol"; +import "./AyiiClf.sol"; -import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol"; +// import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol"; import "@etherisc/gif-interface/contracts/components/Oracle.sol"; -contract AyiiOracle is - Oracle, ChainlinkClient -{ +contract AyiiOracle is Oracle { using strings for bytes32; - using Chainlink for Chainlink.Request; - mapping(bytes32 /* Chainlink request ID */ => uint256 /* GIF request ID */) public gifRequests; - bytes32 public jobId; - uint256 public payment; + AyiiClf public ayiiClf; + modifier onlyAyiiClf() { + require( + _msgSender() == address(ayiiClf), + "ERROR:AYII-001:ACCESS_DENIED" + ); + _; + } + + mapping(bytes32 /* Chainlink request ID */ => uint256 /* GIF request ID */) + public gifRequests; event LogAyiiRequest(uint256 requestId, bytes32 chainlinkRequestId); - + event LogAyiiFulfill( - uint256 requestId, - bytes32 chainlinkRequestId, - bytes32 projectId, - bytes32 uaiId, - bytes32 cropId, - uint256 aaay + uint256 requestId, + bytes32 chainlinkRequestId, + bytes response ); - constructor( - bytes32 _name, - address _registry, - address _chainLinkToken, - address _chainLinkOperator, - bytes32 _jobId, - uint256 _payment - ) - Oracle(_name, _registry) - { - updateRequestDetails( - _chainLinkToken, - _chainLinkOperator, - _jobId, - _payment); + constructor(bytes32 _name, address _registry) Oracle(_name, _registry) { + // NOOP } - function updateRequestDetails( - address _chainLinkToken, - address _chainLinkOperator, - bytes32 _jobId, - uint256 _payment - ) - public - onlyOwner - { - if (_chainLinkToken != address(0)) { setChainlinkToken(_chainLinkToken); } - if (_chainLinkOperator != address(0)) { setChainlinkOracle(_chainLinkOperator); } - - jobId = _jobId; - payment = _payment; + function setClfGateWay(address _ayiiClf) external onlyOwner { + ayiiClf = AyiiClf(_ayiiClf); } - function request(uint256 gifRequestId, bytes calldata input) - external override - onlyQuery - { - Chainlink.Request memory request_ = buildChainlinkRequest( - jobId, - address(this), - this.fulfill.selector + function request( + uint256 gifRequestId, + bytes calldata input + ) external override onlyQuery { + require( + address(ayiiClf) != address(0), + "ERROR:AYII-002:GATEWAY_NOT_SET" ); - - ( - bytes32 projectId, - bytes32 uaiId, - bytes32 cropId - ) = abi.decode(input, (bytes32, bytes32, bytes32)); - - request_.add("projectId", projectId.toB32String()); - request_.add("uaiId", uaiId.toB32String()); - request_.add("cropId", cropId.toB32String()); - - bytes32 chainlinkRequestId = sendChainlinkRequest(request_, payment); + bytes32 chainlinkRequestId = ayiiClf._sendRequest(input); gifRequests[chainlinkRequestId] = gifRequestId; emit LogAyiiRequest(gifRequestId, chainlinkRequestId); } - function fulfill( - bytes32 chainlinkRequestId, - bytes32 projectId, - bytes32 uaiId, - bytes32 cropId, - uint256 aaay - ) - public recordChainlinkFulfillment(chainlinkRequestId) - { - uint256 gifRequest = gifRequests[chainlinkRequestId]; - bytes memory data = abi.encode(projectId, uaiId, cropId, aaay); - _respond(gifRequest, data); - - delete gifRequests[chainlinkRequestId]; - emit LogAyiiFulfill(gifRequest, chainlinkRequestId, projectId, uaiId, cropId, aaay); + function fulfillRequest( + bytes32 requestId, + bytes memory response, + bytes memory err + ) external onlyAyiiClf { + uint256 gifRequest = gifRequests[requestId]; + if (gifRequest == 0) { + revert("Unexpected Request Id"); + } + _respond(gifRequest, response); + + delete gifRequests[requestId]; + emit LogAyiiFulfill(gifRequest, requestId, response); } - function cancel(uint256 requestId) - external override - onlyOwner - { - // TODO mid/low priority - // cancelChainlinkRequest(_requestId, _payment, _callbackFunctionId, _expiration); - } - - // only used for testing of chainlink operator - function encodeFulfillParameters( - bytes32 chainlinkRequestId, - bytes32 projectId, - bytes32 uaiId, - bytes32 cropId, - uint256 aaay - ) - external - pure - returns(bytes memory parameterData) - { - return abi.encode( - chainlinkRequestId, - projectId, - uaiId, - cropId, - aaay - ); - } - - function getChainlinkJobId() external view returns(bytes32 chainlinkJobId) { - return jobId; - } - - function getChainlinkPayment() external view returns(uint256 paymentAmount) { - return payment; - } - - function getChainlinkToken() external view returns(address linkTokenAddress) { - return chainlinkTokenAddress(); - } - - function getChainlinkOperator() external view returns(address operator) { - return chainlinkOracleAddress(); + function cancel(uint256 requestId) external override onlyQuery { + // not implemented } } - From 98059227d95113f033242a47b6a83f1d8b3348b5 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Thu, 23 Jan 2025 14:25:02 +0000 Subject: [PATCH 12/18] Major rework of deployment scripts --- .devcontainer/Dockerfile | 2 +- .devcontainer/docker-compose.yaml | 3 +- .ipynb_checkpoints/Untitled-checkpoint.ipynb | 6 + .vscode/settings.json | 3 +- Untitled.ipynb | 6 + contracts/Migrations.sol | 24 -- contracts/examples/AyiiClf.sol | 2 +- contracts/examples/AyiiOracle.clf | 127 +++++++++++ contracts/examples/AyiiOracle.sol | 6 +- contracts/generic/GenericOracle.sol | 21 ++ contracts/generic/GenericProduct.sol | 16 ++ contracts/generic/GenericRiskPool.sol | 23 ++ scripts/ayii_product.py | 195 +++++++--------- scripts/context.py | 87 +++++++ scripts/deploy_ayii.py | 226 ++++++++++++------- scripts/instance.py | 214 +++++++++++------- scripts/interactive.py | 138 +++++++++++ scripts/onepassword.py | 58 +++++ scripts/prompt.py | 38 ++++ scripts/util.py | 120 +++++++--- 20 files changed, 972 insertions(+), 343 deletions(-) create mode 100644 .ipynb_checkpoints/Untitled-checkpoint.ipynb create mode 100644 Untitled.ipynb delete mode 100644 contracts/Migrations.sol create mode 100644 contracts/examples/AyiiOracle.clf create mode 100644 contracts/generic/GenericOracle.sol create mode 100644 contracts/generic/GenericProduct.sol create mode 100644 contracts/generic/GenericRiskPool.sol create mode 100644 scripts/context.py create mode 100644 scripts/interactive.py create mode 100644 scripts/onepassword.py create mode 100644 scripts/prompt.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index af3831b..5514672 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -ARG VARIANT=3.11-bookworm +ARG VARIANT=3.11-22.04 # requires rosetta 2 on apple chips ARG PLATFORM=linux/amd64 FROM --platform=${PLATFORM} mcr.microsoft.com/devcontainers/python:${VARIANT} diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml index ff1ad09..5c1c7b9 100644 --- a/.devcontainer/docker-compose.yaml +++ b/.devcontainer/docker-compose.yaml @@ -9,7 +9,8 @@ services: context: .. dockerfile: .devcontainer/Dockerfile args: - VARIANT: 3.9-bullseye + # VARIANT: 3.9-bullseye + VARIANT: 3.11-bookworm USER_UID: 1000 USER_GID: 1000 INSTALL_NODE: "true" diff --git a/.ipynb_checkpoints/Untitled-checkpoint.ipynb b/.ipynb_checkpoints/Untitled-checkpoint.ipynb new file mode 100644 index 0000000..363fcab --- /dev/null +++ b/.ipynb_checkpoints/Untitled-checkpoint.ipynb @@ -0,0 +1,6 @@ +{ + "cells": [], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/.vscode/settings.json b/.vscode/settings.json index d7bfd16..12051b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -32,5 +32,6 @@ "titleBar.inactiveBackground": "#1d3c4399", "titleBar.inactiveForeground": "#e7e7e799" }, - "security.olympix.project.includePath": "/contracts" + "security.olympix.project.includePath": "/contracts", + "solidity.defaultCompiler": "remote" } \ No newline at end of file diff --git a/Untitled.ipynb b/Untitled.ipynb new file mode 100644 index 0000000..363fcab --- /dev/null +++ b/Untitled.ipynb @@ -0,0 +1,6 @@ +{ + "cells": [], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol deleted file mode 100644 index 4e0c8ad..0000000 --- a/contracts/Migrations.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.2; - -contract Migrations { - address public owner; - uint256 public last_completed_migration; // solhint-disable-line - - constructor() { - owner = msg.sender; - } - - modifier restricted() { - if (msg.sender == owner) _; - } - - function setCompleted(uint256 _completed) public restricted { - last_completed_migration = _completed; - } - - function upgrade(address _newAddress) public restricted { - Migrations upgraded = Migrations(_newAddress); - upgraded.setCompleted(last_completed_migration); - } -} diff --git a/contracts/examples/AyiiClf.sol b/contracts/examples/AyiiClf.sol index e3b7266..b1bd721 100644 --- a/contracts/examples/AyiiClf.sol +++ b/contracts/examples/AyiiClf.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.2; interface AyiiClf { - function _sendRequest( + function sendClfRequest( bytes calldata input ) external returns (bytes32 requestId); } diff --git a/contracts/examples/AyiiOracle.clf b/contracts/examples/AyiiOracle.clf new file mode 100644 index 0000000..2bc9f95 --- /dev/null +++ b/contracts/examples/AyiiOracle.clf @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +// NEU: mit Chainlink Funktion + +import "./strings.sol"; + +// import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol"; +import "@etherisc/gif-interface/contracts/components/Oracle.sol"; + +contract AyiiOracle is Oracle, FunctionsClient, ConfirmedOwner { + using strings for bytes32; + using FunctionsRequest for FunctionsRequest.Request; + + mapping(bytes32 /* Chainlink request ID */ => uint256 /* GIF request ID */) + public gifRequests; + // DON Network parameters + // router and donID can be found on https://docs.chain.link/chainlink-functions/supported-networks + // subscriptionId can be found on https://functions.chain.link/ after loggin in with the chainlink subscription account + // gasLimit should be estimated + // source is the code text of the javascript function to be executed by the DON + address public router; + uint64 public subscriptionId; + uint32 public gasLimit; + bytes32 public donID; + string public source; + + event LogAyiiRequest(uint256 requestId, bytes32 chainlinkRequestId); + + event LogAyiiFulfill( + uint256 requestId, + bytes32 chainlinkRequestId, + bytes response + ); + constructor( + bytes32 _name, + address _registry, + address _router, + uint64 _subscriptionId, + uint32 _gasLimit, + bytes32 _donID, + string memory _source + ) + Oracle(_name, _registry) + FunctionsClient(_router) + ConfirmedOwner(msg.sender) + { + router = _router; + subscriptionId = _subscriptionId; + gasLimit = _gasLimit; + donID = _donID; + source = _source; + } + + function updateChainlinkFunctionParameters( + address _router, + uint64 _subscriptionId, + uint32 _gasLimit, + bytes32 _donID, + string memory _source + ) external onlyOwner { + router = _router; + subscriptionId = _subscriptionId; + gasLimit = _gasLimit; + donID = _donID; + source = _source; + } + + function request( + uint256 gifRequestId, + bytes calldata input + ) external override onlyQuery { + FunctionsRequest.Request memory req; + + (bytes32 projectId, bytes32 uaiId, bytes32 cropId) = abi.decode( + input, + (bytes32, bytes32, bytes32) + ); + + bytes[] memory bytesArgs = [ + projectId.toB32String(), + uaiId.toB32String(), + cropId.toB32String() + ]; + + req.initializeRequestForInlineJavaScript(source); + req.setBytesArgs(bytesArgs); + + bytes32 chainlinkRequestId = _sendRequest( + req.encodeCBOR(), + subscriptionId, + gasLimit, + donID + ); + + gifRequests[chainlinkRequestId] = gifRequestId; + emit LogAyiiRequest(gifRequestId, chainlinkRequestId); + } + /* +function fulfillRequest( + bytes32 requestId, + bytes memory response, + bytes memory err + ) internal override { + if (s_lastRequestId != requestId) { + revert UnexpectedRequestID(requestId); + } + s_lastResponse = response; + s_lastError = err; + emit Response(requestId, s_lastResponse, s_lastError); + } +*/ + function fulfillRequest( + bytes32 requestId, + bytes memory response, + bytes memory err + ) internal override { + uint256 gifRequest = gifRequests[requestId]; + if (gifRequest == 0) { + revert("Unexpected Request Id"); + } + _respond(gifRequest, response); + + delete gifRequests[chainlinkRequestId]; + emit LogAyiiFulfill(gifRequest, requestId, response); + } +} diff --git a/contracts/examples/AyiiOracle.sol b/contracts/examples/AyiiOracle.sol index 82376cd..fde79a8 100644 --- a/contracts/examples/AyiiOracle.sol +++ b/contracts/examples/AyiiOracle.sol @@ -48,20 +48,20 @@ contract AyiiOracle is Oracle { address(ayiiClf) != address(0), "ERROR:AYII-002:GATEWAY_NOT_SET" ); - bytes32 chainlinkRequestId = ayiiClf._sendRequest(input); + bytes32 chainlinkRequestId = ayiiClf.sendClfRequest(input); gifRequests[chainlinkRequestId] = gifRequestId; emit LogAyiiRequest(gifRequestId, chainlinkRequestId); } - function fulfillRequest( + function fulfillClfRequest( bytes32 requestId, bytes memory response, bytes memory err ) external onlyAyiiClf { uint256 gifRequest = gifRequests[requestId]; if (gifRequest == 0) { - revert("Unexpected Request Id"); + revert("ERROR:AYII-003:UNEXPECTED_REQUEST_ID"); } _respond(gifRequest, response); diff --git a/contracts/generic/GenericOracle.sol b/contracts/generic/GenericOracle.sol new file mode 100644 index 0000000..a21e8db --- /dev/null +++ b/contracts/generic/GenericOracle.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.2; + +import "@etherisc/gif-interface/contracts/components/Oracle.sol"; + +contract GenericOracle is Oracle { + constructor(bytes32 _name, address _registry) Oracle(_name, _registry) { + // not implemented + } + + function request( + uint256 gifRequestId, + bytes calldata input + ) external override { + // not implemented + } + + function cancel(uint256 requestId) external override { + // not implemented + } +} diff --git a/contracts/generic/GenericProduct.sol b/contracts/generic/GenericProduct.sol new file mode 100644 index 0000000..2ba2fac --- /dev/null +++ b/contracts/generic/GenericProduct.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.2; + +import "@etherisc/gif-interface/contracts/components/Product.sol"; + +contract GenericProduct is Product { + bytes32 public constant POLICY_FLOW = ""; + + constructor( + bytes32 productName, + address registry, + address token, + uint256 riskpoolId, + address insurer + ) Product(productName, token, POLICY_FLOW, riskpoolId, registry) {} +} diff --git a/contracts/generic/GenericRiskPool.sol b/contracts/generic/GenericRiskPool.sol new file mode 100644 index 0000000..5773ee6 --- /dev/null +++ b/contracts/generic/GenericRiskPool.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.2; + +import "@etherisc/gif-interface/contracts/components/BasicRiskpool.sol"; + +contract GenericRiskpool is BasicRiskpool { + constructor( + bytes32 name, + uint256 collateralization, + address erc20Token, + address wallet, + address registry + ) BasicRiskpool(name, collateralization, 0, erc20Token, wallet, registry) { + // not implemented + } + + function bundleMatchesApplication( + IBundle.Bundle memory bundle, + IPolicy.Application memory application + ) public pure override returns (bool isMatching) { + isMatching = true; + } +} diff --git a/scripts/ayii_product.py b/scripts/ayii_product.py index 267c200..26f4c87 100644 --- a/scripts/ayii_product.py +++ b/scripts/ayii_product.py @@ -8,7 +8,7 @@ from brownie import ( history, Wei, - Contract, + Contract, PolicyController, OracleService, ComponentOwnerService, @@ -16,8 +16,8 @@ AyiiRiskpool, AyiiProduct, AyiiOracle, - ChainlinkOperator, - ChainlinkToken, + ChainlinkOperator, + ChainlinkToken, ) from scripts.util import ( @@ -37,16 +37,18 @@ ORACLE_NAME = 'AyiiOracle' PRODUCT_NAME = 'AyiiProduct' + class GifAyiiRiskpool(object): - def __init__(self, - instance: GifInstance, + def __init__( + self, + instance: GifInstance, erc20Token: Account, - riskpoolKeeper: Account, + riskpoolKeeper: Account, riskpoolWallet: Account, investor: Account, - collateralization:int, - name=RISKPOOL_NAME, + collateralization: int, + name=RISKPOOL_NAME, publishSource=False ): instanceService = instance.getInstanceService() @@ -61,8 +63,8 @@ def __init__(self, riskpoolKeeperRole, riskpoolKeeper)) instanceOperatorService.grantRole( - riskpoolKeeperRole, - riskpoolKeeper, + riskpoolKeeperRole, + riskpoolKeeper, {'from': instanceOperatorService.owner()}) print('2) deploy riskpool by riskpool keeper {}'.format( @@ -78,7 +80,7 @@ def __init__(self, publish_source=publishSource) tx = history[-1] - wait_for_confirmations(tx) + wait_for_confirmations(tx) print('3) investor role granting to investor {} by riskpool keeper {}'.format( investor, riskpoolKeeper)) @@ -90,35 +92,35 @@ def __init__(self, print('4) riskpool {} proposing to instance by riskpool keeper {}'.format( self.riskpool, riskpoolKeeper)) - + componentOwnerService.propose( self.riskpool, {'from': riskpoolKeeper}) tx = history[-1] - wait_for_confirmations(tx) + wait_for_confirmations(tx) print('5) approval of riskpool id {} by instance operator {}'.format( self.riskpool.getId(), instance.getOwner())) - + instanceOperatorService.approve( self.riskpool.getId(), {'from': instanceOperatorService.owner()}) 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': instanceOperatorService.owner()}) # 7) setup capital fees - fixedFee = 42 - fractionalFee = instanceService.getFeeFractionFullUnit() / 20 # corresponds to 5% + fixedFee = 0 + fractionalFee = 0 # 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, @@ -128,31 +130,30 @@ def __init__(self, print('8) setting capital fee spec by instance operator {}'.format( instance.getOwner())) - + instanceOperatorService.setCapitalFees( feeSpec, {'from': instanceOperatorService.owner()}) - + def getId(self) -> int: return self.riskpool.getId() - + def getContract(self) -> AyiiRiskpool: return self.riskpool class GifAyiiOracle(object): - def __init__(self, - instance: GifInstance, - oracleProvider: Account, - chainlinkNodeOperator: Account, - name=ORACLE_NAME, + def __init__( + self, + instance: GifInstance, + oracleProvider: Account, + name=ORACLE_NAME, publishSource=False ): instanceService = instance.getInstanceService() instanceOperatorService = instance.getInstanceOperatorService() componentOwnerService = instance.getComponentOwnerService() - oracleService = instance.getOracleService() print('------ setting up oracle ------') @@ -161,54 +162,21 @@ def __init__(self, providerRole, oracleProvider)) instanceOperatorService.grantRole( - providerRole, - oracleProvider, + providerRole, + oracleProvider, {'from': instance.getOwner()}) - - clTokenOwner = oracleProvider - clTokenSupply = 10**20 - print('2) deploy chainlink (mock) token with token owner (=oracle provider) {} by oracle provider {}'.format( - clTokenOwner, oracleProvider)) - - self.chainlinkToken = ChainlinkToken.deploy( - clTokenOwner, - clTokenSupply, - {'from': oracleProvider}, - publish_source=publishSource) - - print('3) deploy chainlink (mock) operator by oracle provider {}'.format( - oracleProvider)) - - self.chainlinkOperator = ChainlinkOperator.deploy( - {'from': oracleProvider}, - publish_source=publishSource) - - print('4) set node operator list [{}] as authorized sender by oracle provider {}'.format( - chainlinkNodeOperator, oracleProvider)) - - self.chainlinkOperator.setAuthorizedSenders([chainlinkNodeOperator]) - - # 2c) oracle provider creates oracle - chainLinkTokenAddress = self.chainlinkToken.address - chainLinkOracleAddress = self.chainlinkOperator.address - chainLinkJobId = s2b32('1') - chainLinkPaymentAmount = 0 print('5) deploy oracle by oracle provider {}'.format( oracleProvider)) - + self.oracle = AyiiOracle.deploy( s2b32(name), instance.getRegistry(), - chainLinkTokenAddress, - chainLinkOracleAddress, - chainLinkJobId, - chainLinkPaymentAmount, {'from': oracleProvider}, publish_source=publishSource) tx = history[-1] - wait_for_confirmations(tx) + wait_for_confirmations(tx) print('6) oracle {} proposing to instance by oracle provider {}'.format( self.oracle, oracleProvider)) @@ -218,7 +186,7 @@ def __init__(self, {'from': oracleProvider}) tx = history[-1] - wait_for_confirmations(tx) + wait_for_confirmations(tx) print('7) approval of oracle id {} by instance operator {}'.format( self.oracle.getId(), instance.getOwner())) @@ -226,27 +194,28 @@ def __init__(self, instanceOperatorService.approve( self.oracle.getId(), {'from': instance.getOwner()}) - + def getId(self) -> int: return self.oracle.getId() - + def getClOperator(self) -> ChainlinkOperator: return self.chainlinkOperator - + def getContract(self) -> AyiiOracle: return self.oracle class GifAyiiProduct(object): - def __init__(self, - instance: GifInstance, - erc20Token, - productOwner: Account, - insurer: Account, - oracle: GifAyiiOracle, - riskpool: GifAyiiRiskpool, - name=PRODUCT_NAME, + def __init__( + self, + instance: GifInstance, + erc20Token, + productOwner: Account, + insurer: Account, + oracle: GifAyiiOracle, + riskpool: GifAyiiRiskpool, + name=PRODUCT_NAME, publishSource=False ): self.policy = instance.getPolicy() @@ -267,12 +236,12 @@ def __init__(self, instanceOperatorService.grantRole( productOwnerRole, - productOwner, + productOwner, {'from': instance.getOwner()}) print('2) deploy product by product owner {}'.format( productOwner)) - + self.product = AyiiProduct.deploy( s2b32(name), registry, @@ -284,21 +253,21 @@ def __init__(self, publish_source=publishSource) tx = history[-1] - wait_for_confirmations(tx) + wait_for_confirmations(tx) print('3) product {} proposing to instance by product owner {}'.format( self.product, productOwner)) - + componentOwnerService.propose( self.product, {'from': productOwner}) tx = history[-1] - wait_for_confirmations(tx) + wait_for_confirmations(tx) print('4) approval of product id {} by instance operator {}'.format( self.product.getId(), instance.getOwner())) - + instanceOperatorService.approve( self.product.getId(), {'from': instance.getOwner()}) @@ -307,35 +276,34 @@ def __init__(self, erc20Token, self.product.getId(), instance.getOwner())) instanceOperatorService.setProductToken( - self.product.getId(), + self.product.getId(), erc20Token, - {'from': instance.getOwner()}) + {'from': instance.getOwner()}) - fixedFee = 3 - fractionalFee = instanceService.getFeeFractionFullUnit() / 10 # corresponds to 10% + fixedFee = 0 + fractionalFee = 0 # instanceService.getFeeFractionFullUnit() / 10 # corresponds to 10% # # set fees to zero # fixedFee = 0 # fractionalFee = 0 - + 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()}) + {'from': instance.getOwner()}) print('7) setting premium fee spec by instance operator {}'.format( instance.getOwner())) instanceOperatorService.setPremiumFees( feeSpec, - {'from': instance.getOwner()}) + {'from': instance.getOwner()}) - def getId(self) -> int: return self.product.getId() @@ -347,7 +315,7 @@ def getOracle(self) -> GifAyiiOracle: def getRiskpool(self) -> GifAyiiRiskpool: return self.riskpool - + def getContract(self) -> AyiiProduct: return self.product @@ -357,52 +325,45 @@ def getPolicy(self, policyId: str): class GifAyiiProductComplete(object): - def __init__(self, - instance: GifInstance, - productOwner: Account, + def __init__( + self, + instance: GifInstance, + productOwner: Account, insurer: Account, - oracleProvider: Account, - chainlinkNodeOperator: Account, - riskpoolKeeper: Account, + oracleProvider: Account, + riskpoolKeeper: Account, investor: Account, erc20Token: Account, riskpoolWallet: Account, collateralizationLevel: int, - baseName='Ayii', + baseName='Ayii', publishSource=False ): - instanceService = instance.getInstanceService() - instanceOperatorService = instance.getInstanceOperatorService() - componentOwnerService = instance.getComponentOwnerService() - registry = instance.getRegistry() self.token = erc20Token self.riskpool = GifAyiiRiskpool( - instance, - erc20Token, - riskpoolKeeper, - riskpoolWallet, - investor, + instance, + erc20Token, + riskpoolKeeper, + riskpoolWallet, + investor, collateralizationLevel, '{}Riskpool'.format(baseName), publishSource) self.oracle = GifAyiiOracle( - instance, - oracleProvider, + instance, oracleProvider, - # TODO analyze how to set a separate chainlink operator node account - # chainlinkNodeOperator, '{}Oracle'.format(baseName), publishSource) self.product = GifAyiiProduct( - instance, - erc20Token, - productOwner, - insurer, - self.oracle, + instance, + erc20Token, + productOwner, + insurer, + self.oracle, self.riskpool, '{}Product'.format(baseName), publishSource) diff --git a/scripts/context.py b/scripts/context.py new file mode 100644 index 0000000..bc31edb --- /dev/null +++ b/scripts/context.py @@ -0,0 +1,87 @@ +import asyncio +import os + +from brownie import ( + network, + accounts, + UsdcAccounting, + Wei +) + +from scripts.onepassword import signIn, getItem, getSecret +from scripts.deploy_ayii import (from_registry) +from scripts.util import (contract_from_address) + + +class Context: + + networkName = network.show_active() + vault = os.getenv('OP_VAULT') + stakeholders = [ + 'fundsOwner', + 'instanceOperator', + 'instanceWallet', + 'oracleProvider', + 'chainlinkNodeOperator', + 'riskpoolKeeper', + 'riskpoolWallet', + 'investor', + 'productOwner', + 'insurer', + 'customer1', + 'customer2' + ] + feeCapitalFix = 0 + feeCapitalPercentage = 0 + feePremiumFix = 0 + feePremiumPercentage = 0 + + def __init__(self): + self.secretsItem = f'{self.networkName} secrets' + + asyncio.run(signIn()) + print(f'Getting secrets from {self.vault} item {self.secretsItem}') + asyncio.run(getItem(self.vault, self.secretsItem)) + self.registry = getSecret('Addresses', 'registry') + self.usdcAccountingToken = getSecret( + 'Addresses', 'usdc_accounting_token') + self.usdc = contract_from_address( + UsdcAccounting, self.usdcAccountingToken + ) + + self.accounts = { + s: accounts.from_mnemonic(getSecret('Mnemonics', s)) + for s in self.stakeholders + } + ( + instance, + product, + oracle, + riskpool, + componentController + ) = from_registry(self.registry) + self.instanceService = instance.getInstanceService() + self.instance = instance + self.treasury = instance.getTreasury() + self.product = product + self.oracle = oracle + self.riskpool = riskpool + self.riskpoolId = riskpool.getId() + self.oracleId = oracle.getId() + self.productId = product.getId() + self.componentController = componentController + self.instanceOperator = self.accounts['instanceOperator'] + assert self.instanceService.getInstanceOperator() == self.instanceOperator + assert self.riskpool.getFullCollateralizationLevel() == 1000000000000000000 + self.fullCollateralizationLevel = self.riskpool.getFullCollateralizationLevel() + self.noPrint = ['accounts', 'stakeholders', 'noPrint'] + + def printContext(self): + print('Context:') + for k, v in self.__dict__.items(): + if k not in self.noPrint: + print(f'{k.ljust(30)}: {v}') + print('Accounts:') + for k, v in self.accounts.items(): + print( + f'{k.ljust(30)}: {v.address} {str(Wei(v.balance()).to("ether")).rjust(25)}') diff --git a/scripts/deploy_ayii.py b/scripts/deploy_ayii.py index 20dac6c..72b1504 100644 --- a/scripts/deploy_ayii.py +++ b/scripts/deploy_ayii.py @@ -1,3 +1,4 @@ +import click from datetime import datetime from brownie import web3 @@ -8,18 +9,18 @@ from brownie import ( interface, network, - TestCoin, InstanceService, InstanceOperatorService, ComponentOwnerService, AyiiProduct, AyiiOracle, - AyiiRiskpool + AyiiRiskpool, + ComponentController ) from scripts.ayii_product import GifAyiiProductComplete from scripts.instance import GifInstance -from scripts.util import contract_from_address, s2b32 +from scripts.util import contract_from_address, s2b32, getChainName, utcStr INSTANCE_OPERATOR = 'instanceOperator' INSTANCE_WALLET = 'instanceWallet' @@ -73,6 +74,19 @@ CUSTOMER2: REQUIRED_FUNDS_S, } +INTERACTIVE = True + + +def set_interactive(interactive): + global INTERACTIVE + INTERACTIVE = interactive + + +def confirm(text): + if INTERACTIVE: + click.confirm( + f"This action will alter the state of the blockchain <{network.show_active()}>. The action is '{text}'. Do you want to proceed?") + def help(): print('from scripts.util import s2b, b2s, contract_from_address') @@ -81,13 +95,13 @@ def help(): print('#--- deploy ganache setup ---------------------------------------#') print('a = stakeholders_accounts_ganache()') print("instance_operator = a['instanceOperator']") - print("token = TestCoin.deploy({'from': instance_operator})") print() print('check_funds(a, token)') print('# amend_funds(a)') print('d = deploy(a, token, False)') print() - print("(instance, product, oracle, riskpool) = from_registry(d['instance'].getRegistry())") + print( + "(instance, product, oracle, riskpool) = from_registry(d['instance'].getRegistry())") print("verify_deploy(a, token, instance.getRegistry())") print() print('#--- deploy to existing instance --------------------------------#') @@ -101,7 +115,7 @@ def help(): print('riskpool_old = riskpool') print() print('(instance, product, oracle, riskpool) = deploy_product_riskpool(registry_address, a, token, collateralization_level)') - + print(f"You are on chain {network.show_active()}") def stakeholders_accounts_ganache(): @@ -109,7 +123,6 @@ def stakeholders_accounts_ganache(): instanceOperator = accounts[0] instanceWallet = accounts[1] oracleProvider = accounts[2] - chainlinkNodeOperator = accounts[3] riskpoolKeeper = accounts[4] riskpoolWallet = accounts[5] investor = accounts[6] @@ -122,7 +135,6 @@ def stakeholders_accounts_ganache(): INSTANCE_OPERATOR: instanceOperator, INSTANCE_WALLET: instanceWallet, ORACLE_PROVIDER: oracleProvider, - NODE_OPERATOR: chainlinkNodeOperator, RISKPOOL_KEEPER: riskpoolKeeper, RISKPOOL_WALLET: riskpoolWallet, INVESTOR: investor, @@ -191,6 +203,9 @@ def check_erc20_funds(a, erc20_token): def amend_funds(stakeholders_accounts): + if not confirm("amend funds"): + return + a = stakeholders_accounts for accountName, requiredAmount in REQUIRED_FUNDS.items(): if a[accountName].balance() < REQUIRED_FUNDS[accountName]: @@ -203,7 +218,8 @@ def amend_funds(stakeholders_accounts): def _print_constants(): - print('chain id: {}'.format(web3.eth.chain_id)) + chainId = web3.eth.chain_id + print('chain id: {} {}'.format(chainId, getChainName(chainId))) print('gas price [Mwei]: {}'.format(GAS_PRICE/10**6)) print('gas price safety factor: {}'.format(GAS_PRICE_SAFETY_FACTOR)) @@ -272,71 +288,77 @@ def verify_deploy( stakeholders_accounts, erc20_token, registry_address, - riskpoolId = 0, - oracleId = 0, - productId = 0 + riskpoolId=0, + oracleId=0, + productId=0 ): # define stakeholder accounts a = stakeholders_accounts instanceOperator = a[INSTANCE_OPERATOR] instanceWallet = a[INSTANCE_WALLET] oracleProvider = a[ORACLE_PROVIDER] - chainlinkNodeOperator = a[NODE_OPERATOR] riskpoolKeeper = a[RISKPOOL_KEEPER] riskpoolWallet = a[RISKPOOL_WALLET] - investor = a[INVESTOR] productOwner = a[PRODUCT_OWNER] - insurer = a[INSURER] - customer = a[CUSTOMER1] - customer2 = a[CUSTOMER2] ( instance, product, oracle, - riskpool + riskpool, + componentController ) = from_registry( registry_address, - riskpoolId = riskpoolId, - oracleId = oracleId, - productId = productId + riskpoolId=riskpoolId, + oracleId=oracleId, + productId=productId ) instanceService = instance.getInstanceService() - verify_element('Registry', instanceService.getRegistry(), registry_address) - verify_element('InstanceOperator', - instanceService.getInstanceOperator(), instanceOperator) - verify_element('InstanceWallet', - instanceService.getInstanceWallet(), instanceWallet) + verify_element( + 'Registry', instanceService.getRegistry(), registry_address) + verify_element( + 'InstanceOperator', instanceService.getInstanceOperator(), instanceOperator) + verify_element( + 'InstanceWallet', instanceService.getInstanceWallet(), instanceWallet) - verify_element('RiskpoolId', riskpool.getId(), riskpoolId) + verify_element( + 'RiskpoolId', riskpool.getId(), riskpoolId) verify_element( 'RiskpoolType', instanceService.getComponentType(riskpoolId), 2) - verify_element('RiskpoolState', - instanceService.getComponentState(riskpoolId), 3) - verify_element('RiskpoolKeeper', riskpool.owner(), riskpoolKeeper) - verify_element('RiskpoolWallet', instanceService.getRiskpoolWallet( - riskpoolId), riskpoolWallet) - verify_element('RiskpoolBalance', instanceService.getBalance( - riskpoolId), erc20_token.balanceOf(riskpoolWallet)) - verify_element('RiskpoolToken', riskpool.getErc20Token(), - erc20_token.address) - - verify_element('OracleId', oracle.getId(), oracleId) - verify_element('OracleType', instanceService.getComponentType(oracleId), 0) + verify_element( + 'RiskpoolState', instanceService.getComponentState(riskpoolId), 3) + verify_element( + 'RiskpoolKeeper', riskpool.owner(), riskpoolKeeper) + verify_element( + 'RiskpoolWallet', instanceService.getRiskpoolWallet(riskpoolId), riskpoolWallet) + verify_element( + 'RiskpoolBalance', instanceService.getBalance(riskpoolId), erc20_token.balanceOf(riskpoolWallet)) + verify_element( + 'RiskpoolToken', riskpool.getErc20Token(), erc20_token.address) + + verify_element( + 'OracleId', oracle.getId(), oracleId) + verify_element( + 'OracleType', instanceService.getComponentType(oracleId), 0) verify_element( 'OracleState', instanceService.getComponentState(oracleId), 3) - verify_element('OracleProvider', oracle.owner(), oracleProvider) + verify_element( + 'OracleProvider', oracle.owner(), oracleProvider) - verify_element('ProductId', product.getId(), productId) + verify_element( + 'ProductId', product.getId(), productId) verify_element( 'ProductType', instanceService.getComponentType(productId), 1) verify_element( 'ProductState', instanceService.getComponentState(productId), 3) - verify_element('ProductOwner', product.owner(), productOwner) - verify_element('ProductToken', product.getToken(), erc20_token.address) - verify_element('ProductRiskpool', product.getRiskpoolId(), riskpoolId) + verify_element( + 'ProductOwner', product.owner(), productOwner) + verify_element( + 'ProductToken', product.getToken(), erc20_token.address) + verify_element( + 'ProductRiskpool', product.getRiskpoolId(), riskpoolId) print('InstanceWalletBalance {:.2f}'.format(erc20_token.balanceOf( instanceService.getInstanceWallet())/10**erc20_token.decimals())) @@ -349,7 +371,10 @@ def verify_deploy( print('RiskpoolBundles {}'.format(riskpool.bundles())) # bundle_id = riskpool.getBundleId(0) - print('RiskpoolBundle[0] {}'.format(riskpool.getBundle(0).dict())) + for key, value in riskpool.getBundle(0).dict().items(): + if key in ['createdAt', 'updatedAt']: # Apply conversion for specific keys + value = utcStr(value) + print(f" {key}: {value}") print('ProductRisks {}'.format(product.risks())) print('ProductApplications {}'.format(product.applications())) @@ -365,26 +390,25 @@ def verify_element( print('{} ERROR {} expected {}'.format(element, value, expected_value)) -def deploy_product_riskpool( +def deploy_product_with_oracle_riskpool( registry_address, stakeholders_accounts, erc20_token, - collateralizaionLevel, + collateralizationLevel, publishSource=False ): + if not confirm(f"deployment with product and riskpool"): + return + # define stakeholder accounts a = stakeholders_accounts instanceOperator = a[INSTANCE_OPERATOR] - instanceWallet = a[INSTANCE_WALLET] oracleProvider = a[ORACLE_PROVIDER] - chainlinkNodeOperator = a[NODE_OPERATOR] riskpoolKeeper = a[RISKPOOL_KEEPER] riskpoolWallet = a[RISKPOOL_WALLET] investor = a[INVESTOR] productOwner = a[PRODUCT_OWNER] insurer = a[INSURER] - customer = a[CUSTOMER1] - customer2 = a[CUSTOMER2] # create basename including unix timestamp baseName = 'Ayii_{}_'.format(int(datetime.now().timestamp())) @@ -400,15 +424,24 @@ def deploy_product_riskpool( print('====== setting erc20 token to {} ======'.format(erc20_token)) erc20Token = erc20_token - print('====== getting instance from registry address {} ======'.format(registry_address)) + print('====== getting instance from registry address {} ======'.format( + registry_address)) (instance, product, oracle, riskpool) = from_registry(registry_address) - print("====== deploy ayii product /w base name '{}' and riskpool collateralization level {} ======".format(baseName, collateralizaionLevel)) ayiiDeploy = GifAyiiProductComplete( - instance, productOwner, insurer, oracleProvider, chainlinkNodeOperator, - riskpoolKeeper, investor, erc20Token, riskpoolWallet, collateralizaionLevel, - baseName=baseName, publishSource=publishSource) + instance, + productOwner, + insurer, + oracleProvider, + riskpoolKeeper, + investor, + erc20Token, + riskpoolWallet, + collateralizationLevel, + baseName=baseName, + publishSource=publishSource + ) ayiiProduct = ayiiDeploy.getProduct() ayiiOracle = ayiiProduct.getOracle() @@ -422,7 +455,9 @@ def deploy_product_riskpool( initial_funding = 10**erc20Token.decimals() bundle_filter = b'' erc20Token.transfer(investor, initial_funding, {'from': instanceOperator}) - erc20Token.approve(instance.getTreasury().address, initial_funding, {'from': investor}) + erc20Token.approve( + instance.getTreasury().address, + initial_funding, {'from': investor}) riskpool.createBundle(bundle_filter, initial_funding, {'from': investor}) return ( @@ -438,13 +473,14 @@ def deploy( erc20_token, publishSource=False ): + if not confirm("full deployment"): + return # define stakeholder accounts a = stakeholders_accounts instanceOperator = a[INSTANCE_OPERATOR] instanceWallet = a[INSTANCE_WALLET] oracleProvider = a[ORACLE_PROVIDER] - chainlinkNodeOperator = a[NODE_OPERATOR] riskpoolKeeper = a[RISKPOOL_KEEPER] riskpoolWallet = a[RISKPOOL_WALLET] investor = a[INVESTOR] @@ -476,8 +512,9 @@ def deploy( print('====== deploy ayii product ======') collateralizationLevel = instanceService.getFullCollateralizationLevel() - ayiiDeploy = GifAyiiProductComplete(instance, productOwner, insurer, oracleProvider, chainlinkNodeOperator, - riskpoolKeeper, investor, erc20Token, riskpoolWallet, collateralizationLevel, + ayiiDeploy = GifAyiiProductComplete( + instance, productOwner, insurer, oracleProvider, chainlinkNodeOperator, + riskpoolKeeper, investor, erc20Token, riskpoolWallet, collateralizationLevel, publishSource=publishSource) # assess balances at beginning of deploy @@ -499,14 +536,16 @@ def deploy( erc20Token.transfer(investor, bundleInitialFunding, {'from': instanceOperator}) - erc20Token.approve(instance.getTreasury(), - bundleInitialFunding, {'from': investor}) + 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}) + erc20Token.approve( + instance.getTreasury(), bundleInitialFunding, { + 'from': riskpoolWallet}) print('3) riskpool bundle creation by investor {}'.format( investor)) @@ -550,8 +589,9 @@ def deploy( customer, customerFunding, erc20Token)) erc20Token.transfer(customer, customerFunding, {'from': instanceOperator}) - erc20Token.approve(instance.getTreasury(), - customerFunding, {'from': customer}) + erc20Token.approve( + instance.getTreasury(), + customerFunding, {'from': customer}) # policy creation premium = [300, 400] @@ -571,7 +611,6 @@ def deploy( INSTANCE_OPERATOR: instanceOperator, INSTANCE_WALLET: instanceWallet, ORACLE_PROVIDER: oracleProvider, - NODE_OPERATOR: chainlinkNodeOperator, RISKPOOL_KEEPER: riskpoolKeeper, RISKPOOL_WALLET: riskpoolWallet, INVESTOR: investor, @@ -640,9 +679,11 @@ def from_registry( registryAddress, productId=0, oracleId=0, - riskpoolId=0 + riskpoolId=0, + verbose=False ): instance = GifInstance(registryAddress=registryAddress) + registry = instance.getRegistry() instanceService = instance.getInstanceService() products = instanceService.products() @@ -659,7 +700,7 @@ def from_registry( else: componentId = instanceService.getProductId(products-1) - if products > 1: + if verbose and products > 1: print('1 product expected, {} products available'.format(products)) print('returning last product available') @@ -668,10 +709,11 @@ def from_registry( if product.getType() != 1: product = None - print('component (type={}) with id {} is not product'.format( - product.getType(), componentId)) - print('no product returned (None)') - else: + if verbose: + print('component (type={}) with id {} is not product'.format( + product.getType(), componentId)) + print('no product returned (None)') + elif verbose: print('1 product expected, no product available') print('no product returned (None)') @@ -681,7 +723,7 @@ def from_registry( else: componentId = instanceService.getOracleId(oracles-1) - if oracles > 1: + if verbose and oracles > 1: print('1 oracle expected, {} oracles available'.format(oracles)) print('returning last oracle available') @@ -690,10 +732,11 @@ def from_registry( if oracle.getType() != 0: oracle = None - print('component (type={}) with id {} is not oracle'.format( - component.getType(), componentId)) - print('no oracle returned (None)') - else: + if verbose: + print('component (type={}) with id {} is not oracle'.format( + component.getType(), componentId)) + print('no oracle returned (None)') + elif verbose: print('1 oracle expected, no oracles available') print('no oracle returned (None)') @@ -703,7 +746,7 @@ def from_registry( else: componentId = instanceService.getRiskpoolId(riskpools-1) - if riskpools > 1: + if verbose and riskpools > 1: print('1 riskpool expected, {} riskpools available'.format(riskpools)) print('returning last riskpool available') @@ -712,17 +755,24 @@ def from_registry( if riskpool.getType() != 2: riskpool = None - print('component (type={}) with id {} is not riskpool'.format( - component.getType(), componentId)) - print('no riskpool returned (None)') - else: + if verbose: + print('component (type={}) with id {} is not riskpool'.format( + component.getType(), componentId)) + print('no riskpool returned (None)') + elif verbose: print('1 riskpool expected, no riskpools available') print('no riskpool returned (None)') - return (instance, product, oracle, riskpool) + componentController = contract_from_address( + ComponentController, registry.getContract(s2b32('Component'))) + + return (instance, product, oracle, riskpool, componentController) def dry_run_create_risks(product, insurer): + if not confirm("dry run create risks"): + return + project = '2022.kenya.wfp.ayii' crop = 'maize' @@ -756,13 +806,17 @@ def dry_run_create_risks(product, insurer): print("project, aez, crop, trigger, exit, tsi, aph, riskId") for i in range(len(aez)): - riskId = create_risk(product, insurer, project, - aez[i], crop, trigger, exit_, tsi, aph[i]) + riskId = create_risk( + product, insurer, project, + aez[i], crop, trigger, exit_, tsi, aph[i]) print(project, aez[i], crop, trigger, exit_, tsi, aph[i], riskId) def create_risk(product, insurer, project, uai, crop, trigger, exit_, tsi, aph): + if not confirm("create risks"): + return + multiplier = product.getPercentageMultiplier() triggerInt = multiplier * trigger exitInt = multiplier * exit_ diff --git a/scripts/instance.py b/scripts/instance.py index 80ed43a..f1551b2 100644 --- a/scripts/instance.py +++ b/scripts/instance.py @@ -9,7 +9,7 @@ from brownie import ( Wei, - Contract, + Contract, BundleToken, RiskpoolToken, CoreProxy, @@ -50,10 +50,11 @@ contractFromAddress, ) + class GifRegistry(object): def __init__( - self, + self, owner: Account, publishSource: bool = False ): @@ -67,7 +68,7 @@ def __init__( proxy = CoreProxy.deploy( controller.address, - encoded_initializer, + encoded_initializer, {'from': owner}, publish_source=publishSource) @@ -78,10 +79,13 @@ def __init__( print('controller.address {}'.format(controller.address)) print('proxy.address {}'.format(proxy.address)) print('registry.address {}'.format(self.registry.address)) - print('registry.getContract(InstanceOperatorService) {}'.format(self.registry.getContract(s2h("InstanceOperatorService")))) + print('registry.getContract(InstanceOperatorService) {}'.format( + self.registry.getContract(s2h("InstanceOperatorService")))) - self.registry.register(s2b32("Registry"), proxy.address, {'from': owner}) - self.registry.register(s2b32("RegistryController"), controller.address, {'from': owner}) + self.registry.register( + s2b32("Registry"), proxy.address, {'from': owner}) + self.registry.register(s2b32("RegistryController"), + controller.address, {'from': owner}) def getOwner(self) -> Account: return self.owner @@ -93,73 +97,93 @@ def getRegistry(self) -> RegistryController: class GifInstance(GifRegistry): def __init__( - self, - owner: Account = None, - instanceWallet: Account = None, - registryAddress = None, + self, + owner: Account = None, + instanceWallet: Account = None, + registryAddress=None, publishSource: bool = False, setInstanceWallet: bool = True ): if registryAddress: self.fromRegistryAddress(registryAddress) - self.owner=owner - + self.owner = owner + elif owner: super().__init__( - owner, + owner, publishSource) - + self.deployWithRegistry( - self.registry, + self.registry, owner, publishSource) - + if setInstanceWallet: self.instanceOperatorService.setInstanceWallet( instanceWallet, {'from': owner}) - + else: - raise ValueError('either owner or registry_address need to be provided') + raise ValueError( + 'either owner or registry_address need to be provided') + def __str__(self): + return 'GifInstance class with registry = {}'.format(self.registry.address) def deployWithRegistry( - self, - registry: GifRegistry, + self, + registry: GifRegistry, owner: Account, publishSource: bool ): # gif instance tokens - self.bundleToken = deployGifToken("BundleToken", BundleToken, registry, owner, publishSource) - self.riskpoolToken = deployGifToken("RiskpoolToken", RiskpoolToken, registry, owner, publishSource) + self.bundleToken = deployGifToken( + "BundleToken", BundleToken, registry, owner, publishSource) + self.riskpoolToken = deployGifToken( + "RiskpoolToken", RiskpoolToken, registry, owner, publishSource) # modules (need to be deployed first) # deploy order needs to respect module dependencies - self.access = deployGifModuleV2("Access", AccessController, registry, owner, publishSource) - self.component = deployGifModuleV2("Component", ComponentController, registry, owner, publishSource) - self.query = deployGifModuleV2("Query", QueryModule, registry, owner, publishSource) - self.license = deployGifModuleV2("License", LicenseController, registry, owner, publishSource) - self.policy = deployGifModuleV2("Policy", PolicyController, registry, owner, publishSource) - self.bundle = deployGifModuleV2("Bundle", BundleController, registry, owner, publishSource) - self.pool = deployGifModuleV2("Pool", PoolController, registry, owner, publishSource) - self.treasury = deployGifModuleV2("Treasury", TreasuryModule, registry, owner, publishSource) + self.access = deployGifModuleV2( + "Access", AccessController, registry, owner, publishSource) + self.component = deployGifModuleV2( + "Component", ComponentController, registry, owner, publishSource) + self.query = deployGifModuleV2( + "Query", QueryModule, registry, owner, publishSource) + self.license = deployGifModuleV2( + "License", LicenseController, registry, owner, publishSource) + self.policy = deployGifModuleV2( + "Policy", PolicyController, registry, owner, publishSource) + self.bundle = deployGifModuleV2( + "Bundle", BundleController, registry, owner, publishSource) + self.pool = deployGifModuleV2( + "Pool", PoolController, registry, owner, publishSource) + self.treasury = deployGifModuleV2( + "Treasury", TreasuryModule, registry, owner, publishSource) # TODO these contracts do not work with proxy pattern - self.policyFlow = deployGifService(PolicyDefaultFlow, registry, owner, publishSource) + self.policyFlow = deployGifService( + PolicyDefaultFlow, registry, owner, publishSource) # services - self.instanceService = deployGifModuleV2("InstanceService", InstanceService, registry, owner, publishSource) - self.componentOwnerService = deployGifModuleV2("ComponentOwnerService", ComponentOwnerService, registry, owner, publishSource) - self.oracleService = deployGifModuleV2("OracleService", OracleService, registry, owner, publishSource) - self.riskpoolService = deployGifModuleV2("RiskpoolService", RiskpoolService, registry, owner, publishSource) + self.instanceService = deployGifModuleV2( + "InstanceService", InstanceService, registry, owner, publishSource) + self.componentOwnerService = deployGifModuleV2( + "ComponentOwnerService", ComponentOwnerService, registry, owner, publishSource) + self.oracleService = deployGifModuleV2( + "OracleService", OracleService, registry, owner, publishSource) + self.riskpoolService = deployGifModuleV2( + "RiskpoolService", RiskpoolService, registry, owner, publishSource) # TODO these contracts do not work with proxy pattern - self.productService = deployGifService(ProductService, registry, owner, publishSource) + self.productService = deployGifService( + ProductService, registry, owner, publishSource) - # needs to be the last module to register as it will - # perform some post deploy wirings and changes the address + # needs to be the last module to register as it will + # perform some post deploy wirings and changes the address # of the instance operator service to its true address - self.instanceOperatorService = deployGifModuleV2("InstanceOperatorService", InstanceOperatorService, registry, owner, publishSource) + self.instanceOperatorService = deployGifModuleV2( + "InstanceOperatorService", InstanceOperatorService, registry, owner, publishSource) # post deploy wiring steps # self.bundleToken.setBundleModule(self.bundle) @@ -167,35 +191,44 @@ def deployWithRegistry( # ensure that the instance has 32 contracts when freshly deployed assert 32 == registry.contracts() - def fromRegistryAddress(self, registry_address): - self.registry = contractFromAddress(RegistryController, registry_address) + self.registry = contractFromAddress( + RegistryController, registry_address) self.access = self.contractFromGifRegistry(AccessController, "Access") - self.component = self.contractFromGifRegistry(AccessController, "Component") + self.component = self.contractFromGifRegistry( + AccessController, "Component") self.query = self.contractFromGifRegistry(QueryModule, "Query") - self.license = self.contractFromGifRegistry(LicenseController, "License") + self.license = self.contractFromGifRegistry( + LicenseController, "License") self.policy = self.contractFromGifRegistry(PolicyController, "Policy") self.bundle = self.contractFromGifRegistry(BundleController, "Bundle") self.pool = self.contractFromGifRegistry(PoolController, "Pool") - self.treasury = self.contractFromGifRegistry(TreasuryModule, "Treasury") - - self.instanceService = self.contractFromGifRegistry(InstanceService, "InstanceService") - self.oracleService = self.contractFromGifRegistry(OracleService, "OracleService") - self.riskpoolService = self.contractFromGifRegistry(RiskpoolService, "RiskpoolService") - self.productService = self.contractFromGifRegistry(ProductService, "ProductService") - - self.policyFlow = self.contractFromGifRegistry(PolicyDefaultFlow, "PolicyDefaultFlow") - self.componentOwnerService = self.contractFromGifRegistry(ComponentOwnerService) - self.instanceOperatorService = self.contractFromGifRegistry(InstanceOperatorService) - + self.treasury = self.contractFromGifRegistry( + TreasuryModule, "Treasury") + + self.instanceService = self.contractFromGifRegistry( + InstanceService, "InstanceService") + self.oracleService = self.contractFromGifRegistry( + OracleService, "OracleService") + self.riskpoolService = self.contractFromGifRegistry( + RiskpoolService, "RiskpoolService") + self.productService = self.contractFromGifRegistry( + ProductService, "ProductService") + + self.policyFlow = self.contractFromGifRegistry( + PolicyDefaultFlow, "PolicyDefaultFlow") + self.componentOwnerService = self.contractFromGifRegistry( + ComponentOwnerService) + self.instanceOperatorService = self.contractFromGifRegistry( + InstanceOperatorService) def contractFromGifRegistry(self, contractClass, name=None): if not name: nameB32 = s2b32(contractClass._name) else: nameB32 = s2b32(name) - + address = self.registry.getContract(nameB32) return contractFromAddress(contractClass, address) @@ -222,7 +255,7 @@ def getLicense(self) -> LicenseController: def getPolicy(self) -> PolicyController: return self.policy - + def getPolicyDefaultFlow(self) -> PolicyDefaultFlow: return self.policyFlow @@ -240,24 +273,26 @@ def getInstanceOperatorService(self) -> InstanceOperatorService: def getInstanceService(self) -> InstanceService: return self.instanceService - + def getRiskpoolService(self) -> RiskpoolService: return self.riskpoolService - + def getProductService(self) -> ProductService: return self.productService - + def getComponentOwnerService(self) -> ComponentOwnerService: return self.componentOwnerService - + def getOracleService(self) -> OracleService: return self.oracleService def dump_sources(registryAddress=None): - dump_sources_summary_dir = './dump_sources/{}'.format(network.show_active()) - dump_sources_summary_file = '{}/contracts.txt'.format(dump_sources_summary_dir) + dump_sources_summary_dir = './dump_sources/{}'.format( + network.show_active()) + dump_sources_summary_file = '{}/contracts.txt'.format( + dump_sources_summary_dir) # create parent dir try: @@ -270,36 +305,42 @@ def dump_sources(registryAddress=None): os.mkdir(dump_sources_summary_dir) except OSError: pass - + instance = None - + if registryAddress: instance = GifInstance(registryAddress=registryAddress) - + contracts = [] contracts.append(dump_single(CoreProxy, "Registry", instance)) - contracts.append(dump_single(RegistryController, "RegistryController", instance)) + contracts.append(dump_single(RegistryController, + "RegistryController", instance)) contracts.append(dump_single(BundleToken, "BundleToken", instance)) contracts.append(dump_single(RiskpoolToken, "RiskpoolToken", instance)) contracts.append(dump_single(CoreProxy, "Access", instance)) - contracts.append(dump_single(AccessController, "AccessController", instance)) + contracts.append(dump_single(AccessController, + "AccessController", instance)) contracts.append(dump_single(CoreProxy, "Component", instance)) - contracts.append(dump_single(ComponentController, "ComponentController", instance)) + contracts.append(dump_single(ComponentController, + "ComponentController", instance)) contracts.append(dump_single(CoreProxy, "Query", instance)) contracts.append(dump_single(QueryModule, "QueryModule", instance)) contracts.append(dump_single(CoreProxy, "License", instance)) - contracts.append(dump_single(LicenseController, "LicenseController", instance)) + contracts.append(dump_single(LicenseController, + "LicenseController", instance)) contracts.append(dump_single(CoreProxy, "Policy", instance)) - contracts.append(dump_single(PolicyController, "PolicyController", instance)) + contracts.append(dump_single(PolicyController, + "PolicyController", instance)) contracts.append(dump_single(CoreProxy, "Bundle", instance)) - contracts.append(dump_single(BundleController, "BundleController", instance)) + contracts.append(dump_single(BundleController, + "BundleController", instance)) contracts.append(dump_single(CoreProxy, "Pool", instance)) contracts.append(dump_single(PoolController, "PoolController", instance)) @@ -307,31 +348,39 @@ def dump_sources(registryAddress=None): contracts.append(dump_single(CoreProxy, "Treasury", instance)) contracts.append(dump_single(TreasuryModule, "TreasuryModule", instance)) - contracts.append(dump_single(PolicyDefaultFlow, "PolicyDefaultFlow", instance)) + contracts.append(dump_single(PolicyDefaultFlow, + "PolicyDefaultFlow", instance)) contracts.append(dump_single(CoreProxy, "InstanceService", instance)) - contracts.append(dump_single(InstanceService, "InstanceServiceController", instance)) + contracts.append(dump_single( + InstanceService, "InstanceServiceController", instance)) contracts.append(dump_single(CoreProxy, "ComponentOwnerService", instance)) - contracts.append(dump_single(ComponentOwnerService, "ComponentOwnerServiceController", instance)) + contracts.append(dump_single(ComponentOwnerService, + "ComponentOwnerServiceController", instance)) contracts.append(dump_single(CoreProxy, "OracleService", instance)) - contracts.append(dump_single(OracleService, "OracleServiceController", instance)) + contracts.append(dump_single( + OracleService, "OracleServiceController", instance)) contracts.append(dump_single(CoreProxy, "RiskpoolService", instance)) - contracts.append(dump_single(RiskpoolService, "RiskpoolServiceController", instance)) + contracts.append(dump_single( + RiskpoolService, "RiskpoolServiceController", instance)) contracts.append(dump_single(ProductService, "ProductService", instance)) - contracts.append(dump_single(CoreProxy, "InstanceOperatorService", instance)) - contracts.append(dump_single(InstanceOperatorService, "InstanceOperatorServiceController", instance)) + contracts.append(dump_single( + CoreProxy, "InstanceOperatorService", instance)) + contracts.append(dump_single(InstanceOperatorService, + "InstanceOperatorServiceController", instance)) - with open(dump_sources_summary_file,'w') as f: + with open(dump_sources_summary_file, 'w') as f: f.write('\n'.join(contracts)) f.write('\n') print('\n'.join(contracts)) - print('\nfor contract json files see directory {}'.format(dump_sources_summary_dir)) + print('\nfor contract json files see directory {}'.format( + dump_sources_summary_dir)) def dump_single(contract, registryName, instance=None) -> str: @@ -350,7 +399,8 @@ def dump_single(contract, registryName, instance=None) -> str: address = instance.registry.getContract(nameB32) dump_sources_contract_file = './dump_sources/{}/{}.json'.format(netw, name) - with open(dump_sources_contract_file,'w') as f: - f.write(json.dumps(contract.get_verification_info()['standard_json_input'])) + with open(dump_sources_contract_file, 'w') as f: + f.write(json.dumps(contract.get_verification_info() + ['standard_json_input'])) return '{} {} {} {} {} {} {}'.format(netw, compiler, optimizer, runs, license, address, name) diff --git a/scripts/interactive.py b/scripts/interactive.py new file mode 100644 index 0000000..8c64092 --- /dev/null +++ b/scripts/interactive.py @@ -0,0 +1,138 @@ +from brownie import ( + AyiiProduct, + AyiiOracle, + AyiiRiskpool, + GenericOracle, + GenericRiskpool, + GenericProduct +) + +from scripts.deploy_ayii import ( + check_funds, + amend_funds, + deploy, + deploy_product_with_oracle_riskpool, + verify_deploy +) +from scripts.util import (contract_from_address, decodeEnum) + +from scripts.prompt import Prompt +from scripts.context import Context + +prompt = Prompt() + +context = Context() + + +def menu(): + run = True + while run: + options = { + 'Check Funds': {'function': check_funds, 'args': [context.accounts, context.usdc]}, + 'Amend Funds': {'function': amend_funds, 'args': [context.accounts]}, + 'Deploy': { + 'function': deploy, + 'args': [ + context.accounts, + context.usdc + ]}, + 'Deploy Product, Oracle and Riskpool': { + 'function': deploy_product_with_oracle_riskpool, + 'args': [ + context.registry, + context.accounts, + context.usdc, + context.fullCollateralizationLevel + ]}, + 'Verify Deploy': { + 'function': verify_deploy, + 'args': [ + context.accounts, + context.usdc, + context.registry, + context.riskpoolId, + context.oracleId, + context.productId + ] + }, + 'Context': context.printContext, + 'List Components': listComponents, + 'Exit': lambda: False + } + selection = prompt.dict_menu(options) + run = selection != 'Exit' + + +def listComponents(): + componentController = context.componentController + components = componentController.components() + for componentIndex in range(1, components+1): + componentAddress = componentController.getComponent(componentIndex) + componentType = componentController.getComponentType(componentIndex) + print( + f'{componentIndex:>3}: {componentAddress} : {decodeEnum("ComponentType", componentType)}') + + +def setRiskpool(address): + context.riskpool = contract_from_address(AyiiRiskpool, address) + + +def setOracle(address): + context.oracle = contract_from_address(AyiiOracle, address) + + +def setProduct(address): + context.product = contract_from_address(AyiiProduct, address) + + +def verifyDeploy(): + verify_deploy( + context.accounts, + context.usdc, + context.registry, + context.riskpoolId.getId(), + context.oracle.getId(), + context.product.getId() + ) + + +def setFeeCapital(): + + instanceOperatorService = context.instanceOperatorService + feeRiskpool = instanceOperatorService.createFeeSpecification( + context.riskpool.getId(), + context.feeCapitalFix, + context.feeCapitalPercentage, + b'' + ) + instanceOperatorService.setCapitalFees( + feeRiskpool, {'from': context.instanceOperator}) + + +def setFeePremium(): + + instanceOperatorService = context.instanceOperatorService + feeProduct = instanceOperatorService.createFeeSpecification( + context.product.getId(), + context.feePremiumFix, + context.feePremiumPercentage, + b'' + ) + + instanceOperatorService.setPremiumFees( + feeProduct, {'from': context.instanceOperator}) + + +def getFeeSpecification(): + print(dict(context.treasury.getFeeSpecification(context.riskpoolId))) + print(dict(context.treasury.getFeeSpecification(context.productId))) + + +def getComponent(componentId): + address = context.instanceService.getComponent(componentId) + type = context.instanceService.getComponentType(componentId) + contract = [GenericOracle, GenericRiskpool, GenericProduct][type] + return contract_from_address(contract, address) + + +menu() diff --git a/scripts/onepassword.py b/scripts/onepassword.py new file mode 100644 index 0000000..4725b3e --- /dev/null +++ b/scripts/onepassword.py @@ -0,0 +1,58 @@ +import asyncio +import os +from onepassword.client import Client +from onepassword import ItemCreateParams, ItemCategory, ItemSection, ItemField, ItemFieldType + +client = None +lastItem = None + + +async def signIn(): + global client + # Gets your service account token from the OP_SERVICE_ACCOUNT_TOKEN environment variable. + token = os.getenv("OP_SERVICE_ACCOUNT_TOKEN") + + # Connects to 1Password. Fill in your own integration name and version. + client = await Client.authenticate(auth=token, integration_name="Test", integration_version="v1.0.0") + + +async def listVaults(): + vaults = await client.vaults.list_all() + async for vault in vaults: + print(vault.title, vault.id) + + +async def getVaultId(vault): + vaults = await client.vaults.list_all() + async for v in vaults: + if v.title == vault: + return v.id + + +async def listItems(vault): + items = await client.items.list_all(await getVaultId(vault)) + async for item in items: + print(dict(item)) + + +async def getItem(vault, item): + global lastItem + vault_id = await getVaultId(vault) + items = await client.items.list_all(vault_id) + async for i in items: + if i.title == item: + item_id = i.id + lastItem = await client.items.get(vault_id, item_id) + + +def getLastItem(): + return lastItem + + +def getSecret(section, title): + for s in lastItem.sections: + if s.title == section: + for f in lastItem.fields: + if f.section_id == s.id and f.title == title: + return f.value + return None diff --git a/scripts/prompt.py b/scripts/prompt.py new file mode 100644 index 0000000..43839c5 --- /dev/null +++ b/scripts/prompt.py @@ -0,0 +1,38 @@ +from simple_term_menu import TerminalMenu + + +class Prompt: + def execute_function_or_object(self, param): + if callable(param): + # If param is a function, call it without parameters + return param() + elif isinstance(param, dict) and 'function' in param and 'args' in param: + # If param is a dictionary with 'function' and 'args', execute the function with arguments + func = param['function'] + args = param['args'] + + if not callable(func): + raise ValueError( + "'function' in the dictionary must be callable") + if not isinstance(args, (list, tuple)): + raise ValueError( + "'args' in the dictionary must be a list or tuple") + + return func(*args) + else: + raise ValueError( + "Parameter must be a function or a dictionary with 'function' and 'args' keys") + + def menu(self, options): + terminal_menu = TerminalMenu(options) + menu_entry_index = terminal_menu.show() + selection = options[menu_entry_index] + return selection + + def dict_menu(self, dict_options): + # Convert keys to list and make a menu + selection = self.menu(list(dict_options.keys())) + # Get the method that corresponds to the selected key + selected_function = dict_options.get(selection) + self.execute_function_or_object(selected_function) # Invoke the method + return selection diff --git a/scripts/util.py b/scripts/util.py index 361ec14..41f1938 100644 --- a/scripts/util.py +++ b/scripts/util.py @@ -1,10 +1,14 @@ import json +import re +import requests +from datetime import datetime, timezone + from web3 import Web3 from brownie import ( web3, - network, - Contract, + network, + Contract, CoreProxy, ) @@ -19,33 +23,42 @@ CHAIN_ID_AVAX = 43114 CHAIN_ID_MAINNET = 1 -CHAIN_IDS_REQUIRING_CONFIRMATIONS = [CHAIN_ID_MUMBAI, CHAIN_ID_FUJI, CHAIN_ID_GOERLI, CHAIN_ID_AVAX, CHAIN_ID_MAINNET] +CHAIN_IDS_REQUIRING_CONFIRMATIONS = [ + CHAIN_ID_MUMBAI, CHAIN_ID_FUJI, CHAIN_ID_GOERLI, CHAIN_ID_AVAX, CHAIN_ID_MAINNET] def s2h(text: str) -> str: return Web3.toHex(text.encode('ascii')) + def h2s(hex: str) -> str: return Web3.toText(hex).split('\x00')[-1] + def h2sLeft(hex: str) -> str: return Web3.toText(hex).split('\x00')[0] + def s2b32(text: str): - return '{:0<66}'.format(Web3.toHex(text.encode('ascii')))[:66] + return '{:0<66}'.format(Web3.to_hex(text.encode('ascii')))[:66] + def b322s(b32: bytes): return b32.decode().split('\x00')[0] -def s2b(text:str): + +def s2b(text: str): return s2b32(text) + def b2s(b32: bytes): return b322s(b32) -def keccak256(text:str): + +def keccak256(text: str): return Web3.solidityKeccak(['string'], [text]).hex() + def get_account(mnemonic: str, account_offset: int) -> Account: return accounts.from_mnemonic( mnemonic, @@ -66,7 +79,7 @@ def is_forked_network(): return 'fork' in network.show_active() -# source: https://github.com/brownie-mix/upgrades-mix/blob/main/scripts/helpful_scripts.py +# source: https://github.com/brownie-mix/upgrades-mix/blob/main/scripts/helpful_scripts.py def encode_function_data(*args, initializer=None): """Encodes the function call so we can work with an initializer. Args: @@ -78,39 +91,46 @@ def encode_function_data(*args, initializer=None): Returns: [bytes]: Return the encoded bytes. """ - if not len(args): args = b'' + if not len(args): + args = b'' - if initializer: return initializer.encode_input(*args) + if initializer: + return initializer.encode_input(*args) return b'' # generic upgradable gif module deployment + + def deployGifModule( - controllerClass, - storageClass, - registry, + controllerClass, + storageClass, + registry, owner, publishSource ): controller = controllerClass.deploy( - registry.address, + registry.address, {'from': owner}, publish_source=publishSource) - + storage = storageClass.deploy( - registry.address, + registry.address, {'from': owner}, publish_source=publishSource) controller.assignStorage(storage.address, {'from': owner}) storage.assignController(controller.address, {'from': owner}) - registry.register(controller.NAME.call(), controller.address, {'from': owner}) + registry.register(controller.NAME.call(), + controller.address, {'from': owner}) registry.register(storage.NAME.call(), storage.address, {'from': owner}) return contractFromAddress(controllerClass, storage.address) # gif token deployment + + def deployGifToken( tokenName, tokenClass, @@ -133,8 +153,8 @@ def deployGifToken( # generic open zeppelin upgradable gif module deployment def deployGifModuleV2( moduleName, - controllerClass, - registry, + controllerClass, + registry, owner, publishSource ): @@ -149,15 +169,16 @@ def deployGifModuleV2( print('module {} deploy proxy'.format(moduleName)) proxy = CoreProxy.deploy( - controller.address, - encoded_initializer, + controller.address, + encoded_initializer, {'from': owner}, publish_source=publishSource) moduleNameB32 = s2b32(moduleName) controllerNameB32 = s2b32('{}Controller'.format(moduleName))[:32] - print('module {} ({}) register controller'.format(moduleName, controllerNameB32)) + print('module {} ({}) register controller'.format( + moduleName, controllerNameB32)) registry.register(controllerNameB32, controller.address, {'from': owner}) print('module {} ({}) register proxy'.format(moduleName, moduleNameB32)) registry.register(moduleNameB32, proxy.address, {'from': owner}) @@ -167,13 +188,13 @@ def deployGifModuleV2( # generic upgradable gif service deployment def deployGifService( - serviceClass, - registry, + serviceClass, + registry, owner, publishSource ): service = serviceClass.deploy( - registry.address, + registry.address, {'from': owner}, publish_source=publishSource) @@ -181,15 +202,16 @@ def deployGifService( return service + def deployGifServiceV2( serviceName, - serviceClass, - registry, + serviceClass, + registry, owner, publishSource ): service = serviceClass.deploy( - registry.address, + registry.address, {'from': owner}, publish_source=publishSource) @@ -197,12 +219,15 @@ def deployGifServiceV2( return service + def contractFromAddress(contractClass, contractAddress): return contract_from_address(contractClass, contractAddress) + def contract_from_address(contractClass, contractAddress): return Contract.from_abi(contractClass._name, contractAddress, contractClass.abi) + def save_json(contract_class, file_name=None): vi = contract_class.get_verification_info() sji = vi['standard_json_input'] @@ -213,3 +238,44 @@ def save_json(contract_class, file_name=None): print('writing standard json input file {}'.format(file_name)) with open(file_name, "w") as json_file: json.dump(sji, json_file) + + +enums = { + 'ComponentType': ['Oracle', 'Product', 'Riskpool'], + 'ComponentState': ['Created', 'Proposed', 'Declined', 'Active', 'Paused', 'Suspended', 'Archived'], + 'BundleState': ['Active', 'Locked', 'Closed', 'Burned'], + 'PolicyFlowState': ['Started', 'Active', 'Finished'], + 'ApplicationState': ['Applied', 'Revoked', 'Underwritten', 'Declined'], + 'PolicyState': ['Active', 'Expired', 'Closed'], + 'ClaimState': ['Applied', 'Confirmed', 'Declined', 'Closed'], + 'PayoutState': ['Expected', 'PaidOut'] +} + + +def decodeEnum(enum_name, value): + if enum_name not in enums: + return 'invalid' + return enums[enum_name][value] + + +def getChainName(chainId): + try: + # Fetch the list of chains from ChainList + response = requests.get('https://chainid.network/chains.json') + response.raise_for_status() # Raise an error for bad status codes + chains = response.json() + + # Search for the chain ID in the list + for chain in chains: + if chain['chainId'] == chainId: + return chain['name'] + + return 'ChainId not found' + except requests.RequestException as e: + return f'Error fetching chain name for chainId={chainId} {e}' + + +def utcStr(timestamp): + dt_object = datetime.fromtimestamp(timestamp, tz=timezone.utc) + date_string = dt_object.strftime('%Y-%m-%d %H:%M:%S') + return date_string From 88457a18d57f28a098c42ec1a2958b37dea5c3bb Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Fri, 24 Jan 2025 09:06:31 +0000 Subject: [PATCH 13/18] more interaction --- .devcontainer/Dockerfile | 6 +- .devcontainer/devcontainer.json | 11 +- README.md | 29 ++++- scripts/context.py | 55 +++++++++- scripts/deploy_ayii.py | 18 +-- scripts/interactive.py | 188 ++++++++++++++++++++++---------- scripts/menu.py | 67 ++++++++++++ scripts/onepassword.py | 3 +- scripts/prompt.py | 103 +++++++++++++++-- scripts/util.py | 5 +- 10 files changed, 390 insertions(+), 95 deletions(-) create mode 100644 scripts/menu.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5514672..c50c6c0 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -ARG VARIANT=3.11-22.04 +ARG VARIANT=3.11-bookworm # requires rosetta 2 on apple chips ARG PLATFORM=linux/amd64 FROM --platform=${PLATFORM} mcr.microsoft.com/devcontainers/python:${VARIANT} @@ -41,7 +41,9 @@ COPY .devcontainer/requirements.txt /tmp/brownie RUN pip install -r /tmp/brownie/requirements.txt RUN pip install eth-brownie \ && pip install fastapi \ - && pip install uvicorn + && pip install uvicorn \ + && pip install onepassword-sdk \ + && pip install simple_term_menu # [Optional] Uncomment this line to install global node packages. RUN npm install -g ganache solhint prettier prettier-plugin-solidity solhint-plugin-prettier diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 00f60ae..985b802 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,7 +22,8 @@ "oderwat.indent-rainbow", "2gua.rainbow-brackets", "johnpapa.vscode-peacock", - "vikas.code-navigation" + "vikas.code-navigation", + "ms-azuretools.vscode-docker" ] } }, @@ -38,7 +39,11 @@ 8000 ], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "touch .env && brownie compile --all && python --version", + // This reads the 1password environment variables from the .env.op file and exports them to the container. + "postCreateCommand": "touch .env && brownie compile --all && python --version && grep -v '^#' /home/vscode/.env.op | sed 's/^/export /' >> /home/vscode/.zshrc", // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" + "remoteUser": "vscode", + "mounts": [ + "source=${localEnv:HOME}/.env.op,target=/home/vscode/.env.op,type=bind,consistency=cached" + ] } \ No newline at end of file diff --git a/README.md b/README.md index 8064b5e..3a3ef9c 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,34 @@ In this case replace the `auto` keyword in the command with the number of execut Deployments to live networks can be done with brownie console as well. -Example for the deployment to Polygon test +### Setup Deployment Secrets + +Deployments typically use wallets etc and we need to ensure that secrets are not exposed. +We use 1password to store secrets in vaults, which are accessed programatically via the onepassword-sdk. +The access tokens are stored in environment variables which are not part of the devcontainer, but +are imported when the devcontainer is built. +The solution uses two environment variables: +* `OP_VAULT` designates the vault under which the secrets are stored in 1password +* `OP_SERVICE_ACCOUNT_TOKEN` designates the service account token which is used to access the secrets. + +To use this system, create a `.env.op` file in your local home folder. In the file, store the two values: +``` +OP_VAULT="MY VAULT" +OP_SERVICE_ACCOUNT_TOKEN="mysecrettoken..." +``` +When the devcontainer is rebuild, the values are read from this file and stored as environment variables in your devcontainer. + +In the vault, create items for each chain you will work with. +The item name needs to follow this pattern: + +` secrets` + +where `` is the output of the brownie `network.show_active()` command. + +In the item, store each Mnemonic and each Address to be used in the deployment in two different sections "Mnemonics" and "Addresses". + + +### Example for the deployment to Polygon test ```bash brownie console --network polygon-test diff --git a/scripts/context.py b/scripts/context.py index bc31edb..f9f07dd 100644 --- a/scripts/context.py +++ b/scripts/context.py @@ -1,16 +1,20 @@ import asyncio import os +from prompt_toolkit.shortcuts.progress_bar import ProgressBar from brownie import ( network, accounts, UsdcAccounting, + AyiiProduct, + AyiiRiskpool, + AyiiOracle, Wei ) from scripts.onepassword import signIn, getItem, getSecret from scripts.deploy_ayii import (from_registry) -from scripts.util import (contract_from_address) +from scripts.util import (contract_from_address, decodeEnum) class Context: @@ -37,10 +41,10 @@ class Context: feePremiumPercentage = 0 def __init__(self): + print('Initializing context, please wait...') self.secretsItem = f'{self.networkName} secrets' asyncio.run(signIn()) - print(f'Getting secrets from {self.vault} item {self.secretsItem}') asyncio.run(getItem(self.vault, self.secretsItem)) self.registry = getSecret('Addresses', 'registry') self.usdcAccountingToken = getSecret( @@ -85,3 +89,50 @@ def printContext(self): for k, v in self.accounts.items(): print( f'{k.ljust(30)}: {v.address} {str(Wei(v.balance()).to("ether")).rjust(25)}') + + def getComponents(self, type=None, reload=False): + if not reload and hasattr(self, 'components'): + return self.components + componentController = self.componentController + components = componentController.components() + result = [] + with ProgressBar() as pb: + for componentIndex in pb(range(1, components+1), label="Fetching components..."): + cAddress = componentController.getComponent(componentIndex) + cType = componentController.getComponentType(componentIndex) + cState = componentController.getComponentState(componentIndex) + componentData = { + 'address': cAddress, + 'type': cType, + 'state': cState, + 'typeStr': decodeEnum('ComponentType', cType), + 'stateStr': decodeEnum('ComponentState', cState) + } + try: + if cType == 0: # Oracle + componentData['additionalData'] = {} + elif cType == 1: # Product + product = contract_from_address(AyiiProduct, cAddress) + risks = product.risks() + policies = 0 + for riskIndex in range(0, risks): + riskId = product.getRiskId(riskIndex) + policies += product.policies(riskId) + componentData['additionalData'] = { + 'risks': product.risks(), + 'policies': policies, + 'applications': product.applications() + } + elif cType == 2: # Riskpool + riskpool = contract_from_address( + AyiiRiskpool, cAddress) + componentData['additionalData'] = { + 'bundles': riskpool.bundles() + } + except Exception as e: + componentData['error'] = str(e) + + result.append(componentData) + + self.components = result + return list(filter(lambda x: x['type'] == type, result)) if type else result diff --git a/scripts/deploy_ayii.py b/scripts/deploy_ayii.py index 72b1504..a74fc50 100644 --- a/scripts/deploy_ayii.py +++ b/scripts/deploy_ayii.py @@ -1,12 +1,10 @@ -import click from datetime import datetime -from brownie import web3 - from brownie.network import accounts from brownie.network.account import Account from brownie import ( + web3, interface, network, InstanceService, @@ -21,6 +19,7 @@ from scripts.ayii_product import GifAyiiProductComplete from scripts.instance import GifInstance from scripts.util import contract_from_address, s2b32, getChainName, utcStr +from scripts.prompt import confirm INSTANCE_OPERATOR = 'instanceOperator' INSTANCE_WALLET = 'instanceWallet' @@ -74,19 +73,6 @@ CUSTOMER2: REQUIRED_FUNDS_S, } -INTERACTIVE = True - - -def set_interactive(interactive): - global INTERACTIVE - INTERACTIVE = interactive - - -def confirm(text): - if INTERACTIVE: - click.confirm( - f"This action will alter the state of the blockchain <{network.show_active()}>. The action is '{text}'. Do you want to proceed?") - def help(): print('from scripts.util import s2b, b2s, contract_from_address') diff --git a/scripts/interactive.py b/scripts/interactive.py index 8c64092..c756d35 100644 --- a/scripts/interactive.py +++ b/scripts/interactive.py @@ -7,70 +7,85 @@ GenericProduct ) -from scripts.deploy_ayii import ( - check_funds, - amend_funds, - deploy, - deploy_product_with_oracle_riskpool, - verify_deploy -) -from scripts.util import (contract_from_address, decodeEnum) - -from scripts.prompt import Prompt +from scripts.deploy_ayii import (verify_deploy) +from scripts.util import (contract_from_address, decodeEnum, b2s, s2b, utcStr) from scripts.context import Context - -prompt = Prompt() +from scripts.prompt import prompt context = Context() - - -def menu(): - run = True - while run: - options = { - 'Check Funds': {'function': check_funds, 'args': [context.accounts, context.usdc]}, - 'Amend Funds': {'function': amend_funds, 'args': [context.accounts]}, - 'Deploy': { - 'function': deploy, - 'args': [ - context.accounts, - context.usdc - ]}, - 'Deploy Product, Oracle and Riskpool': { - 'function': deploy_product_with_oracle_riskpool, - 'args': [ - context.registry, - context.accounts, - context.usdc, - context.fullCollateralizationLevel - ]}, - 'Verify Deploy': { - 'function': verify_deploy, - 'args': [ - context.accounts, - context.usdc, - context.registry, - context.riskpoolId, - context.oracleId, - context.productId - ] - }, - 'Context': context.printContext, - 'List Components': listComponents, - 'Exit': lambda: False - } - selection = prompt.dict_menu(options) - run = selection != 'Exit' +a = context.accounts def listComponents(): - componentController = context.componentController - components = componentController.components() - for componentIndex in range(1, components+1): - componentAddress = componentController.getComponent(componentIndex) - componentType = componentController.getComponentType(componentIndex) - print( - f'{componentIndex:>3}: {componentAddress} : {decodeEnum("ComponentType", componentType)}') + components = context.getComponents() + for componentIndex, componentData in enumerate(components): + info = ( + f'{componentIndex:>3} : ' + f'{componentData["typeStr"].ljust(12)}\n' + f' Address: {componentData["address"]}\n' + f' Status: {componentData["stateStr"].ljust(12)}' + ) + if componentData["type"] == 0: # Oracle + pass + elif componentData["type"] == 1: # Product + info += ( + '\n' + f' Risks: {componentData["additionalData"]["risks"]}\n' + f' Policies: {componentData["additionalData"]["policies"]}\n' + f' Applications: {componentData["additionalData"]["applications"]}' + ) + elif componentData["type"] == 2: # Riskpool + info += ( + '\n' + f' Bundles: {componentData["additionalData"]["bundles"]}' + ) + + print(info) + + +def selectComponent(type): + components = context.getComponents(type) + options = [ + f"{i:>3}: {component['address']}" for i, component in enumerate(components, 1) + ] + options.append('Cancel') + print(f'Select {decodeEnum("ComponentType", type)}:') + selection = options.index(prompt.menu(options)) + if selection == len(options) - 1: + return None + return components[selection] + + +def selectComponentType(): + options = [ + f"{i}: {decodeEnum('ComponentType', i)}" for i in range(3) + ] + options.append('Cancel') + print('Select Component Type:') + selection = options.index(prompt.menu(options)) + if selection == len(options) - 1: + return None + return selection + + +def selectTypeAndComponent(): + type = selectComponentType() + if type is None: + return + component = selectComponent(type) + if component is None: + return + print(( + f'Selected: {decodeEnum("ComponentType", component[1])} ' + f'Address: {component[0]} ' + f'State: {decodeEnum("ComponentState", component[2])}' + )) + if component[1] == 0: + context.oracle = contract_from_address(GenericOracle, component[0]) + elif component[1] == 1: + context.product = contract_from_address(AyiiProduct, component[0]) + elif component[1] == 2: + context.riskpool = contract_from_address(AyiiRiskpool, component[0]) def setRiskpool(address): @@ -135,4 +150,59 @@ def getComponent(componentId): return contract_from_address(contract, address) -menu() +def createRisk(): + insurer = a['insurer'] + projectId = prompt.enterString("Enter the project ID:", r'^\d{1,10}$') + uaiId = prompt.enterString("Enter the UAI ID:", r'^\d{1,10}$') + cropId = prompt.enterString("Enter the crop ID:", r'^[a-zA-Z]+$') + trigger = prompt.enterFloat("Enter the trigger:", 0, 1) + exit = prompt.enterFloat("Enter the exit:", 0, 1) + tsi = prompt.enterFloat("Enter the TSI:", 0, 1) + aph = prompt.enterFloat("Enter the APH:", 0, 1) + print("Creating risk:") + print(f"projectId: {projectId}") + print(f"uaiId : {uaiId}") + print(f"cropId : {cropId}") + print(f"Trigger : {trigger}") + print(f"Exit : {exit}") + print(f"TSI : {tsi}") + print(f"APH : {aph}") + if not prompt.confirm("create risk"): + return + + mul = context.product.getPercentageMultiplier() + (trigger, exit, tsi, aph) = (mul * trigger, mul * exit, mul * tsi, mul * aph) + tx = context.product.createRisk( + s2b(projectId), s2b(uaiId), s2b(cropId), + trigger, exit, tsi, aph, + {'from': insurer} + ) + riskId = dict(tx.events['LogAyiiRiskDataCreated'])['riskId'] + print(f"Risk created with ID: {riskId}") + + +def listRisks(): + product = context.product + riskCount = product.risks() + for riskIndex in range(0, riskCount): + riskId = product.getRiskId(riskIndex) + risk = product.getRisk(riskId) + mul = product.getPercentageMultiplier() + print(( + f'Index: {riskIndex}\n' + f'RiskId: {riskId}\n' + f'ProjectId: {b2s(risk["projectId"]).ljust(10)}\n' + f'UAI: {b2s(risk["uaiId"]).ljust(10)}\n' + f'CropId: {b2s(risk["cropId"]).ljust(10)}\n' + f'Trigger: {risk["trigger"]/mul:.5f}\n' + f'Exit: {risk["exit"]/mul:.5f}\n' + f'TSI: {risk["tsi"]/mul:.5f}\n' + f'APH: {risk["aph"]/mul:.5f}\n' + f'AAAY: {risk["aaay"]/mul:.5f}\n' + f'Payout Pct: {risk["payoutPercentage"]/mul:.5f}\n' + f'Triggered: {risk["requestTriggered"]}\n' + f'RequestId: {risk["requestId"]}\n' + f'Response at: {utcStr(risk["responseAt"])}\n' + f'Created: {utcStr(risk["createdAt"])}\n' + f'Updated: {utcStr(risk["updatedAt"])}\n' + )) diff --git a/scripts/menu.py b/scripts/menu.py new file mode 100644 index 0000000..3e60af4 --- /dev/null +++ b/scripts/menu.py @@ -0,0 +1,67 @@ +from scripts.prompt import prompt +from scripts.interactive import ( + context, + listComponents, + createRisk, + listRisks, + selectTypeAndComponent +) + +from scripts.deploy_ayii import ( + check_funds, + amend_funds, + deploy, + deploy_product_with_oracle_riskpool, + verify_deploy +) + + +def menu(): + run = True + while run: + options = { + 'Check Funds': { + 'function': check_funds, + 'args': [context.accounts, context.usdc] + }, + 'Amend Funds': { + 'function': amend_funds, + 'args': [context.accounts] + }, + 'Deploy': { + 'function': deploy, + 'args': [ + context.accounts, + context.usdc + ]}, + 'Deploy Product, Oracle and Riskpool': { + 'function': deploy_product_with_oracle_riskpool, + 'args': [ + context.registry, + context.accounts, + context.usdc, + context.fullCollateralizationLevel + ]}, + 'Verify Deploy': { + 'function': verify_deploy, + 'args': [ + context.accounts, + context.usdc, + context.registry, + context.riskpoolId, + context.oracleId, + context.productId + ] + }, + 'Context': context.printContext, + 'List Components': listComponents, + 'Create Risk': createRisk, + 'List Risks': listRisks, + 'Select Type and Component': selectTypeAndComponent, + 'Exit': None + } + selection = prompt.dictMenu(options) + run = selection != 'Exit' + + +menu() diff --git a/scripts/onepassword.py b/scripts/onepassword.py index 4725b3e..d41f4f8 100644 --- a/scripts/onepassword.py +++ b/scripts/onepassword.py @@ -1,4 +1,3 @@ -import asyncio import os from onepassword.client import Client from onepassword import ItemCreateParams, ItemCategory, ItemSection, ItemField, ItemFieldType @@ -11,7 +10,7 @@ async def signIn(): global client # Gets your service account token from the OP_SERVICE_ACCOUNT_TOKEN environment variable. token = os.getenv("OP_SERVICE_ACCOUNT_TOKEN") - + # print(token) # Connects to 1Password. Fill in your own integration name and version. client = await Client.authenticate(auth=token, integration_name="Test", integration_version="v1.0.0") diff --git a/scripts/prompt.py b/scripts/prompt.py index 43839c5..21a416c 100644 --- a/scripts/prompt.py +++ b/scripts/prompt.py @@ -1,8 +1,15 @@ +import questionary +import click +import re from simple_term_menu import TerminalMenu +from brownie import network class Prompt: - def execute_function_or_object(self, param): + + INTERACTIVE = True + + def executeFunctionOrObject(self, param): if callable(param): # If param is a function, call it without parameters return param() @@ -19,9 +26,9 @@ def execute_function_or_object(self, param): "'args' in the dictionary must be a list or tuple") return func(*args) - else: - raise ValueError( - "Parameter must be a function or a dictionary with 'function' and 'args' keys") + # else: + # raise ValueError( + # "Parameter must be a function or a dictionary with 'function' and 'args' keys") def menu(self, options): terminal_menu = TerminalMenu(options) @@ -29,10 +36,90 @@ def menu(self, options): selection = options[menu_entry_index] return selection - def dict_menu(self, dict_options): + def dictMenu(self, dictOptions): # Convert keys to list and make a menu - selection = self.menu(list(dict_options.keys())) + selection = self.menu(list(dictOptions.keys())) # Get the method that corresponds to the selected key - selected_function = dict_options.get(selection) - self.execute_function_or_object(selected_function) # Invoke the method + selected_function = dictOptions.get(selection) + self.executeFunctionOrObject(selected_function) # Invoke the method return selection + + def qText(self, prompt, validate): + response = questionary.text(prompt.ljust(30), validate=validate).ask() + return response + + def enterCurrency(self, prompt, scale=1, lowerBound=0.1, upperBound=100000): + def validateCurrency(value): + try: + # Convert the input to a number + number = float(value) + # Check if the number is within the range + if lowerBound <= number <= upperBound: + return True + else: + return f"Please enter a number between {lowerBound} and {upperBound}." + except ValueError: + return "Invalid input. Please enter a valid number." + + response = self.qText(prompt, validate=validateCurrency) + return int(round(float(response) * 10 ** scale)) + + def enterNumber(self, prompt, lowerBound, upperBound): + def validateNumber(value): + try: + # Convert the input to a number + number = int(value) + # Check if the number is within the range + if lowerBound <= number <= upperBound: + return True + else: + return f"Please enter a number between {lowerBound} and {upperBound}." + except ValueError: + return "Invalid input. Please enter a valid number." + + response = self.qText(prompt, validate=validateNumber) + return int(response) + + def enterFloat(self, prompt, lowerBound, upperBound): + def validateFloat(value): + try: + # Convert the input to a number + number = float(value) + # Check if the number is within the range + if lowerBound <= number <= upperBound: + return True + else: + return f"Please enter a number between {lowerBound} and {upperBound}." + except ValueError: + return "Invalid input. Please enter a valid number." + + response = self.qText(prompt, validate=validateFloat) + return float(response) + + def enterString(self, prompt, regex): + def validateString(value): + r = re.compile(regex) + if r.match(value): + return True + return "Invalid input. Please enter a valid string." + response = self.qText(prompt, validate=validateString) + return response + + def setInteractive(self, interactive): + self.INTERACTIVE = interactive + + def confirm(self, text): + if self.INTERACTIVE: + return click.confirm(( + f"This action will alter the state of " + f"the blockchain <{network.show_active()}>. " + f"The action is '{text}'. " + f"Do you want to proceed?" + )) + else: + return True + + +# Prompt object +prompt = Prompt() +confirm = prompt.confirm diff --git a/scripts/util.py b/scripts/util.py index 41f1938..b8a04bb 100644 --- a/scripts/util.py +++ b/scripts/util.py @@ -122,8 +122,9 @@ def deployGifModule( controller.assignStorage(storage.address, {'from': owner}) storage.assignController(controller.address, {'from': owner}) - registry.register(controller.NAME.call(), - controller.address, {'from': owner}) + registry.register( + controller.NAME.call(), + controller.address, {'from': owner}) registry.register(storage.NAME.call(), storage.address, {'from': owner}) return contractFromAddress(controllerClass, storage.address) From caf54028b51eb9ec7fb32315c96504055185c74b Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Fri, 24 Jan 2025 09:33:53 +0000 Subject: [PATCH 14/18] Select Component --- scripts/context.py | 15 ++++++++++----- scripts/interactive.py | 24 ++++++++++++++---------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/scripts/context.py b/scripts/context.py index f9f07dd..449d476 100644 --- a/scripts/context.py +++ b/scripts/context.py @@ -78,7 +78,7 @@ def __init__(self): assert self.instanceService.getInstanceOperator() == self.instanceOperator assert self.riskpool.getFullCollateralizationLevel() == 1000000000000000000 self.fullCollateralizationLevel = self.riskpool.getFullCollateralizationLevel() - self.noPrint = ['accounts', 'stakeholders', 'noPrint'] + self.noPrint = ['accounts', 'stakeholders', 'noPrint', 'components'] def printContext(self): print('Context:') @@ -90,9 +90,7 @@ def printContext(self): print( f'{k.ljust(30)}: {v.address} {str(Wei(v.balance()).to("ether")).rjust(25)}') - def getComponents(self, type=None, reload=False): - if not reload and hasattr(self, 'components'): - return self.components + def loadComponents(self): componentController = self.componentController components = componentController.components() result = [] @@ -135,4 +133,11 @@ def getComponents(self, type=None, reload=False): result.append(componentData) self.components = result - return list(filter(lambda x: x['type'] == type, result)) if type else result + return result + + def getComponents(self, type=None, reload=False): + if reload or not hasattr(self, 'components'): + self.loadComponents() + if type is not None: + return list(filter(lambda x: x['type'] == type, self.components)) + return self.components diff --git a/scripts/interactive.py b/scripts/interactive.py index c756d35..25062d9 100644 --- a/scripts/interactive.py +++ b/scripts/interactive.py @@ -45,8 +45,9 @@ def listComponents(): def selectComponent(type): components = context.getComponents(type) + ct = [context.oracle.address, context.product.address, context.riskpool.address] options = [ - f"{i:>3}: {component['address']}" for i, component in enumerate(components, 1) + f"{i:>3}: {component['address']} {'< in context' if component['address'] in ct else ''}" for i, component in enumerate(components, 1) ] options.append('Cancel') print(f'Select {decodeEnum("ComponentType", type)}:') @@ -76,16 +77,19 @@ def selectTypeAndComponent(): if component is None: return print(( - f'Selected: {decodeEnum("ComponentType", component[1])} ' - f'Address: {component[0]} ' - f'State: {decodeEnum("ComponentState", component[2])}' + f'Selected: {component["typeStr"]} ' + f'Address: {component["address"]} ' + f'State: {component["stateStr"]}' )) - if component[1] == 0: - context.oracle = contract_from_address(GenericOracle, component[0]) - elif component[1] == 1: - context.product = contract_from_address(AyiiProduct, component[0]) - elif component[1] == 2: - context.riskpool = contract_from_address(AyiiRiskpool, component[0]) + if component['type'] == 0: # Oracle + context.oracle = contract_from_address( + GenericOracle, component['address']) + elif component['type'] == 1: # Product + context.product = contract_from_address( + AyiiProduct, component['address']) + elif component['type'] == 2: # Riskpool + context.riskpool = contract_from_address( + AyiiRiskpool, component['address']) def setRiskpool(address): From e9626043e71d757c24f7941a0751cdab012e03f0 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Sun, 26 Jan 2025 11:05:32 +0000 Subject: [PATCH 15/18] trigger riskpool interactive --- .devcontainer/Dockerfile | 3 +- .ipynb_checkpoints/Untitled-checkpoint.ipynb | 6 - Untitled.ipynb | 6 - scripts/context.py | 5 + scripts/deploy_ayii.py | 18 +- scripts/interactive.py | 249 +++++++++++++++++-- scripts/menu.py | 8 +- scripts/prompt.py | 4 +- scripts/util.py | 9 + 9 files changed, 265 insertions(+), 43 deletions(-) delete mode 100644 .ipynb_checkpoints/Untitled-checkpoint.ipynb delete mode 100644 Untitled.ipynb diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index c50c6c0..3e3bb0f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -43,7 +43,8 @@ RUN pip install eth-brownie \ && pip install fastapi \ && pip install uvicorn \ && pip install onepassword-sdk \ - && pip install simple_term_menu + && pip install simple_term_menu \ + && pip install questionary # [Optional] Uncomment this line to install global node packages. RUN npm install -g ganache solhint prettier prettier-plugin-solidity solhint-plugin-prettier diff --git a/.ipynb_checkpoints/Untitled-checkpoint.ipynb b/.ipynb_checkpoints/Untitled-checkpoint.ipynb deleted file mode 100644 index 363fcab..0000000 --- a/.ipynb_checkpoints/Untitled-checkpoint.ipynb +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cells": [], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/Untitled.ipynb b/Untitled.ipynb deleted file mode 100644 index 363fcab..0000000 --- a/Untitled.ipynb +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cells": [], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/scripts/context.py b/scripts/context.py index 449d476..10c6f7f 100644 --- a/scripts/context.py +++ b/scripts/context.py @@ -46,6 +46,7 @@ def __init__(self): asyncio.run(signIn()) asyncio.run(getItem(self.vault, self.secretsItem)) + self.registry = getSecret('Addresses', 'registry') self.usdcAccountingToken = getSecret( 'Addresses', 'usdc_accounting_token') @@ -117,6 +118,8 @@ def loadComponents(self): riskId = product.getRiskId(riskIndex) policies += product.policies(riskId) componentData['additionalData'] = { + 'erc20Token': product.getToken(), + 'riskpoolId': product.getRiskpoolId(), 'risks': product.risks(), 'policies': policies, 'applications': product.applications() @@ -125,6 +128,8 @@ def loadComponents(self): riskpool = contract_from_address( AyiiRiskpool, cAddress) componentData['additionalData'] = { + 'erc20Token': riskpool.getErc20Token(), + 'capital': riskpool.getCapital(), 'bundles': riskpool.bundles() } except Exception as e: diff --git a/scripts/deploy_ayii.py b/scripts/deploy_ayii.py index a74fc50..f4f7466 100644 --- a/scripts/deploy_ayii.py +++ b/scripts/deploy_ayii.py @@ -18,7 +18,7 @@ from scripts.ayii_product import GifAyiiProductComplete from scripts.instance import GifInstance -from scripts.util import contract_from_address, s2b32, getChainName, utcStr +from scripts.util import contract_from_address, s2b32, getChainName, utcStr, decodeEnum, fromWei from scripts.prompt import confirm INSTANCE_OPERATOR = 'instanceOperator' @@ -357,14 +357,22 @@ def verify_deploy( print('RiskpoolBundles {}'.format(riskpool.bundles())) # bundle_id = riskpool.getBundleId(0) - for key, value in riskpool.getBundle(0).dict().items(): - if key in ['createdAt', 'updatedAt']: # Apply conversion for specific keys - value = utcStr(value) - print(f" {key}: {value}") + printBundle(riskpool, 0) print('ProductRisks {}'.format(product.risks())) print('ProductApplications {}'.format(product.applications())) +def printBundle(riskpool, bundleId): + for key, value in riskpool.getBundle(bundleId).dict().items(): + if key in ['createdAt', 'updatedAt']: # Apply conversion for specific keys + value = utcStr(value) + if key in ['state']: + value = decodeEnum('BundleState', value) + if key in ['balance', 'lockedCapital', 'capital']: + value = fromWei(value, 6) + print(f" {key:<15}: {value}") + + def verify_element( element, value, diff --git a/scripts/interactive.py b/scripts/interactive.py index 25062d9..481ae92 100644 --- a/scripts/interactive.py +++ b/scripts/interactive.py @@ -7,8 +7,11 @@ GenericProduct ) -from scripts.deploy_ayii import (verify_deploy) -from scripts.util import (contract_from_address, decodeEnum, b2s, s2b, utcStr) +from scripts.deploy_ayii import (verify_deploy, printBundle) +from scripts.util import ( + contract_from_address, + decodeEnum, b2s, s2b, utcStr, fromWei +) from scripts.context import Context from scripts.prompt import prompt @@ -31,12 +34,16 @@ def listComponents(): info += ( '\n' f' Risks: {componentData["additionalData"]["risks"]}\n' + f' RiskpoolId: {componentData["additionalData"]["riskpoolId"]}\n' + f' Token: {componentData["additionalData"]["erc20Token"]}\n' f' Policies: {componentData["additionalData"]["policies"]}\n' f' Applications: {componentData["additionalData"]["applications"]}' ) elif componentData["type"] == 2: # Riskpool info += ( '\n' + f' Token: {componentData["additionalData"]["erc20Token"]}\n' + f' Capital(wei): {componentData["additionalData"]["capital"]}\n' f' Bundles: {componentData["additionalData"]["bundles"]}' ) @@ -92,18 +99,6 @@ def selectTypeAndComponent(): AyiiRiskpool, component['address']) -def setRiskpool(address): - context.riskpool = contract_from_address(AyiiRiskpool, address) - - -def setOracle(address): - context.oracle = contract_from_address(AyiiOracle, address) - - -def setProduct(address): - context.product = contract_from_address(AyiiProduct, address) - - def verifyDeploy(): verify_deploy( context.accounts, @@ -185,28 +180,238 @@ def createRisk(): print(f"Risk created with ID: {riskId}") -def listRisks(): +def getRisks(): product = context.product riskCount = product.risks() + result = [] for riskIndex in range(0, riskCount): riskId = product.getRiskId(riskIndex) risk = product.getRisk(riskId) mul = product.getPercentageMultiplier() + result.append({ + 'riskId': riskId, + 'risk': risk, + 'mul': mul, + 'trigger': risk["trigger"]/mul, + 'exit': risk["exit"]/mul, + 'tsi': risk["tsi"]/mul, + 'aph': risk["aph"]/mul, + 'aaay': risk["aaay"]/mul, + 'payoutPercentage': risk["payoutPercentage"]/mul + }) + return result + + +def listRisks(): + riskData = getRisks() + riskCount = len(riskData) + for riskIndex in range(0, riskCount): + data = riskData[riskIndex] + risk = data['risk'] print(( f'Index: {riskIndex}\n' - f'RiskId: {riskId}\n' + f'RiskId: {data["riskId"]}\n' f'ProjectId: {b2s(risk["projectId"]).ljust(10)}\n' f'UAI: {b2s(risk["uaiId"]).ljust(10)}\n' f'CropId: {b2s(risk["cropId"]).ljust(10)}\n' - f'Trigger: {risk["trigger"]/mul:.5f}\n' - f'Exit: {risk["exit"]/mul:.5f}\n' - f'TSI: {risk["tsi"]/mul:.5f}\n' - f'APH: {risk["aph"]/mul:.5f}\n' - f'AAAY: {risk["aaay"]/mul:.5f}\n' - f'Payout Pct: {risk["payoutPercentage"]/mul:.5f}\n' + f'Trigger: {data["trigger"]:.5f}\n' + f'Exit: {data["exit"]:.5f}\n' + f'TSI: {data["tsi"]:.5f}\n' + f'APH: {data["aph"]:.5f}\n' + f'AAAY: {data["aaay"]:.5f}\n' + f'Payout Pct: {data["payoutPercentage"]:.5f}\n' f'Triggered: {risk["requestTriggered"]}\n' f'RequestId: {risk["requestId"]}\n' f'Response at: {utcStr(risk["responseAt"])}\n' f'Created: {utcStr(risk["createdAt"])}\n' f'Updated: {utcStr(risk["updatedAt"])}\n' )) + + +def listRiskShort(): + riskData = getRisks() + return [ + f'{i:>3}: ' + f'{b2s(risk["risk"]["projectId"]).ljust(10)} ' + f'{b2s(risk["risk"]["uaiId"]).ljust(10)} ' + f'{b2s(risk["risk"]["cropId"]).ljust(10)} ' + f'{risk["trigger"]:.5f} ' + f'{risk["exit"]:.5f} ' + f'{risk["tsi"]:.5f} ' + f'{risk["aph"]:.5f} ' + for i, risk in enumerate(riskData) + ] + + +def getPolicies(): + product = context.product + riskCount = product.risks() + result = [] + for riskIndex in range(0, riskCount): + riskId = product.getRiskId(riskIndex) + policyCount = product.policies(riskId) + for policyIndex in range(0, policyCount): + policyId = product.getPolicyId(riskId, policyIndex) + policy = context.instanceService.getPolicy(policyId) + result.append({ + 'policyId': policyId, + 'policy': policy, + 'riskId': riskId + }) + return result + + +def selectPolicy(): + policies = getPolicies() + if len(policies) == 0: + print('No policies available') + return None + options = [ + f"{i:>3}: {p['policyId']}" for i, p in enumerate(policies) + ] + options.append('Cancel') + print('Select Policy:') + selection = options.index(prompt.menu(options)) + if selection == len(options) - 1: + return None + return policies[selection] + + +def selectBundle(): + bundles = context.riskpool.bundles() + if bundles == 0: + print('No bundles available') + return None + elif bundles == 1: + print('Only one bundle available - selecting it') + return 0 + options = [ + f"{i:>3}: {context.riskpool.getBundle(i)}" for i in range(bundles) + ] + options.append('Cancel') + print('Select Bundle:') + selection = options.index(prompt.menu(options)) + if selection == len(options) - 1: + return None + return selection + + +def selectRisk(): + risks = context.product.risks() + if risks == 0: + print('No risks available') + return None + elif risks == 1: + print('Only one risk available - selecting it') + return 0 + options = listRiskShort() + options.append('Cancel') + print('Select Risk:') + selection = options.index(prompt.menu(options)) + if selection == len(options) - 1: + return None + return context.product.getRiskId(selection) + + +def fundBundle(): + bundleId = selectBundle() + if bundleId is None: + return + printBundle(context.riskpool, bundleId) + + bundle = context.riskpool.getBundle(bundleId) + if bundle['state'] != 0: + print('Bundle is not active - aborting') + return + + funding = prompt.enterCurrency( + "Enter the funding amount:", + context.usdc.decimals(), + lowerBound=0 + ) + + if funding == 0: + return + if not prompt.confirm(f"Fund bundle with {fromWei(funding, context.usdc.decimals())} USDC"): + return + + investor = context.accounts['investor'] + if context.usdc.balanceOf(investor) < funding: + print('Insufficient funds at investor - supplying more') + context.usdc.transfer( + investor, + funding, + {'from': context.instanceOperator} + ) + if context.usdc.allowance(investor, context.instanceService.getTreasuryAddress()) < funding: + print('Approving USDC transfer from investor to treasury') + context.usdc.approve( + context.instanceService.getTreasuryAddress(), + funding, + {'from': investor} + ) + + context.riskpool.fundBundle(bundle['id'], funding, {'from': investor}) + print('Bundle successfully funded:') + printBundle(context.riskpool, bundleId) + + +def createPolicy(): + + riskId = selectRisk() + if riskId is None: + return + + premium = prompt.enterCurrency( + "Enter the premium amount:", + context.usdc.decimals(), + lowerBound=0 + ) + if premium == 0: + return + + sumInsured = prompt.enterCurrency( + "Enter the sum insured amount:", + context.usdc.decimals(), + lowerBound=0 + ) + if sumInsured == 0: + return + + customer = context.accounts['customer1'] + insurer = context.accounts['insurer'] + + if context.usdc.balanceOf(customer) < premium: + print('Insufficient funds at customer - supplying more') + context.usdc.transfer( + customer, + premium, + {'from': context.instanceOperator} + ) + if context.usdc.allowance(customer, context.instanceService.getTreasuryAddress()) < premium: + print('Approving USDC transfer from customer to treasury') + context.usdc.approve( + context.instanceService.getTreasuryAddress(), + premium, + {'from': customer} + ) + + print(f'Creating policy for riskId: {riskId}') + tx = context.product.applyForPolicy( + customer, premium, sumInsured, riskId, {'from': insurer}) + policyId = dict(tx.events['LogAyiiPolicyCreated'])['policyId'] + print(f'Policy created with ID: {policyId}') + + +def triggerOracle(): + policy = selectPolicy() + if policy is None: + return + policyId = policy['policyId'] + insurer = context.accounts['insurer'] + if not prompt.confirm(f'Trigger oracle request for policyId: {policyId}'): + return + print(f'Triggering oracle request for policyId: {policyId}') + tx = context.product.triggerOracle(policyId, {'from': insurer}) + requestId = dict(tx.events['LogAyiiRiskDataRequested'])['requestId'] + print(f'Oracle request triggered with requestId: {requestId}') diff --git a/scripts/menu.py b/scripts/menu.py index 3e60af4..33a5f88 100644 --- a/scripts/menu.py +++ b/scripts/menu.py @@ -4,7 +4,10 @@ listComponents, createRisk, listRisks, - selectTypeAndComponent + selectTypeAndComponent, + fundBundle, + createPolicy, + triggerOracle ) from scripts.deploy_ayii import ( @@ -58,6 +61,9 @@ def menu(): 'Create Risk': createRisk, 'List Risks': listRisks, 'Select Type and Component': selectTypeAndComponent, + 'Fund Bundle': fundBundle, + 'Create Policy': createPolicy, + 'Trigger Oracle': triggerOracle, 'Exit': None } selection = prompt.dictMenu(options) diff --git a/scripts/prompt.py b/scripts/prompt.py index 21a416c..3410ad5 100644 --- a/scripts/prompt.py +++ b/scripts/prompt.py @@ -112,8 +112,8 @@ def confirm(self, text): if self.INTERACTIVE: return click.confirm(( f"This action will alter the state of " - f"the blockchain <{network.show_active()}>. " - f"The action is '{text}'. " + f"the blockchain <{network.show_active()}>. \n" + f"The action is '{text}'. \n" f"Do you want to proceed?" )) else: diff --git a/scripts/util.py b/scripts/util.py index b8a04bb..5556037 100644 --- a/scripts/util.py +++ b/scripts/util.py @@ -2,6 +2,7 @@ import re import requests from datetime import datetime, timezone +from decimal import Decimal from web3 import Web3 @@ -280,3 +281,11 @@ def utcStr(timestamp): dt_object = datetime.fromtimestamp(timestamp, tz=timezone.utc) date_string = dt_object.strftime('%Y-%m-%d %H:%M:%S') return date_string + + +def fromWei(raw, decimals): + return Decimal(raw) / Decimal(10**decimals) + + +def toWei(value, decimals): + return int(Decimal(value) * Decimal(10**decimals)) From 95d84862bcc73fa732a258657f730b15f0032c66 Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Sun, 26 Jan 2025 19:36:11 +0000 Subject: [PATCH 16/18] switch to black formatter --- .devcontainer/devcontainer.json | 3 +- .vscode/settings.json | 8 +- brownie-config.yaml | 38 +- scripts/ayii_product.py | 247 +++++++----- scripts/component.py | 11 +- scripts/const.py | 66 +-- scripts/context.py | 123 +++--- scripts/deploy_ayii.py | 685 ++++++++++++++++---------------- scripts/instance.py | 252 ++++++------ scripts/interactive copy.py | 130 ++++++ scripts/interactive.py | 281 +++++++------ scripts/menu.py | 64 ++- scripts/onepassword.py | 12 +- scripts/product.py | 152 ++++--- scripts/prompt.py | 51 ++- scripts/setup.py | 51 +-- scripts/util.py | 191 ++++----- 17 files changed, 1267 insertions(+), 1098 deletions(-) create mode 100644 scripts/interactive copy.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 985b802..f223bf3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -23,7 +23,8 @@ "2gua.rainbow-brackets", "johnpapa.vscode-peacock", "vikas.code-navigation", - "ms-azuretools.vscode-docker" + "ms-azuretools.vscode-docker", + "ms-python.black-formatter" ] } }, diff --git a/.vscode/settings.json b/.vscode/settings.json index 12051b9..065e081 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,5 +33,11 @@ "titleBar.inactiveForeground": "#e7e7e799" }, "security.olympix.project.includePath": "/contracts", - "solidity.defaultCompiler": "remote" + "solidity.defaultCompiler": "remote", + "[python]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "editor.formatOnSave": true + } \ No newline at end of file diff --git a/brownie-config.yaml b/brownie-config.yaml index 298a54a..e3a53f8 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -37,22 +37,22 @@ dependencies: # exclude open zeppeling contracts when calculating test coverage # https://eth-brownie.readthedocs.io/en/v1.10.3/config.html#exclude_paths reports: - exclude_contracts: - # chainlink - - ChainlinkClient - - Operator - # openzeppelin - - AccessControl - - AccessControlEnumerable - - Context - - Ownable - - EnumerableMap - - EnumerableSet - - ERC1967Proxy - - ERC20 - - ERC721 - - IERC20 - - IERC721 - - Initializable - - SafeERC20 - - Strings + exclude_contracts: + # chainlink + - ChainlinkClient + - Operator + # openzeppelin + - AccessControl + - AccessControlEnumerable + - Context + - Ownable + - EnumerableMap + - EnumerableSet + - ERC1967Proxy + - ERC20 + - ERC721 + - IERC20 + - IERC721 + - Initializable + - SafeERC20 + - Strings diff --git a/scripts/ayii_product.py b/scripts/ayii_product.py index 26f4c87..cbbfc19 100644 --- a/scripts/ayii_product.py +++ b/scripts/ayii_product.py @@ -33,9 +33,9 @@ from scripts.instance import GifInstance -RISKPOOL_NAME = 'AyiiRiskpool' -ORACLE_NAME = 'AyiiOracle' -PRODUCT_NAME = 'AyiiProduct' +RISKPOOL_NAME = "AyiiRiskpool" +ORACLE_NAME = "AyiiOracle" +PRODUCT_NAME = "AyiiProduct" class GifAyiiRiskpool(object): @@ -49,26 +49,29 @@ def __init__( investor: Account, collateralization: int, name=RISKPOOL_NAME, - publishSource=False + publishSource=False, ): instanceService = instance.getInstanceService() instanceOperatorService = instance.getInstanceOperatorService() componentOwnerService = instance.getComponentOwnerService() riskpoolService = instance.getRiskpoolService() - print('------ setting up riskpool ------') + print("------ setting up riskpool ------") riskpoolKeeperRole = instanceService.getRiskpoolKeeperRole() - print('1) grant riskpool keeper role {} to riskpool keeper {}'.format( - riskpoolKeeperRole, riskpoolKeeper)) + print( + "1) grant riskpool keeper role {} to riskpool keeper {}".format( + riskpoolKeeperRole, riskpoolKeeper + ) + ) instanceOperatorService.grantRole( riskpoolKeeperRole, riskpoolKeeper, - {'from': instanceOperatorService.owner()}) + {"from": instanceOperatorService.owner()}, + ) - print('2) deploy riskpool by riskpool keeper {}'.format( - riskpoolKeeper)) + print("2) deploy riskpool by riskpool keeper {}".format(riskpoolKeeper)) self.riskpool = AyiiRiskpool.deploy( s2b32(name), @@ -76,64 +79,85 @@ def __init__( erc20Token, riskpoolWallet, instance.getRegistry(), - {'from': riskpoolKeeper}, - publish_source=publishSource) + {"from": riskpoolKeeper}, + publish_source=publishSource, + ) tx = history[-1] wait_for_confirmations(tx) - print('3) investor role granting to investor {} by riskpool keeper {}'.format( - investor, riskpoolKeeper)) + print( + "3) investor role granting to investor {} by riskpool keeper {}".format( + investor, riskpoolKeeper + ) + ) self.riskpool.grantInvestorRole( investor, - {'from': riskpoolKeeper}, + {"from": riskpoolKeeper}, ) - print('4) riskpool {} proposing to instance by riskpool keeper {}'.format( - self.riskpool, riskpoolKeeper)) + print( + "4) riskpool {} proposing to instance by riskpool keeper {}".format( + self.riskpool, riskpoolKeeper + ) + ) - componentOwnerService.propose( - self.riskpool, - {'from': riskpoolKeeper}) + componentOwnerService.propose(self.riskpool, {"from": riskpoolKeeper}) tx = history[-1] wait_for_confirmations(tx) - print('5) approval of riskpool id {} by instance operator {}'.format( - self.riskpool.getId(), instance.getOwner())) + print( + "5) approval of riskpool id {} by instance operator {}".format( + self.riskpool.getId(), instance.getOwner() + ) + ) instanceOperatorService.approve( - self.riskpool.getId(), - {'from': instanceOperatorService.owner()}) + self.riskpool.getId(), {"from": instanceOperatorService.owner()} + ) - print('6) riskpool wallet {} set for riskpool id {} by instance operator {}'.format( - riskpoolWallet, self.riskpool.getId(), instance.getOwner())) + 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': instanceOperatorService.owner()}) + {"from": instanceOperatorService.owner()}, + ) # 7) setup capital fees fixedFee = 0 - fractionalFee = 0 # 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())) + fractionalFee = ( + 0 # 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': instanceOperatorService.owner()}) + b"", + {"from": instanceOperatorService.owner()}, + ) - print('8) setting capital fee spec by instance operator {}'.format( - instance.getOwner())) + print( + "8) setting capital fee spec by instance operator {}".format( + instance.getOwner() + ) + ) instanceOperatorService.setCapitalFees( - feeSpec, - {'from': instanceOperatorService.owner()}) + feeSpec, {"from": instanceOperatorService.owner()} + ) def getId(self) -> int: return self.riskpool.getId() @@ -149,51 +173,57 @@ def __init__( instance: GifInstance, oracleProvider: Account, name=ORACLE_NAME, - publishSource=False + publishSource=False, ): instanceService = instance.getInstanceService() instanceOperatorService = instance.getInstanceOperatorService() componentOwnerService = instance.getComponentOwnerService() - print('------ setting up oracle ------') + print("------ setting up oracle ------") providerRole = instanceService.getOracleProviderRole() - print('1) grant oracle provider role {} to oracle provider {}'.format( - providerRole, oracleProvider)) + print( + "1) grant oracle provider role {} to oracle provider {}".format( + providerRole, oracleProvider + ) + ) instanceOperatorService.grantRole( - providerRole, - oracleProvider, - {'from': instance.getOwner()}) + providerRole, oracleProvider, {"from": instance.getOwner()} + ) - print('5) deploy oracle by oracle provider {}'.format( - oracleProvider)) + print("5) deploy oracle by oracle provider {}".format(oracleProvider)) self.oracle = AyiiOracle.deploy( s2b32(name), instance.getRegistry(), - {'from': oracleProvider}, - publish_source=publishSource) + {"from": oracleProvider}, + publish_source=publishSource, + ) tx = history[-1] wait_for_confirmations(tx) - print('6) oracle {} proposing to instance by oracle provider {}'.format( - self.oracle, oracleProvider)) + print( + "6) oracle {} proposing to instance by oracle provider {}".format( + self.oracle, oracleProvider + ) + ) - componentOwnerService.propose( - self.oracle, - {'from': oracleProvider}) + componentOwnerService.propose(self.oracle, {"from": oracleProvider}) tx = history[-1] wait_for_confirmations(tx) - print('7) approval of oracle id {} by instance operator {}'.format( - self.oracle.getId(), instance.getOwner())) + print( + "7) approval of oracle id {} by instance operator {}".format( + self.oracle.getId(), instance.getOwner() + ) + ) instanceOperatorService.approve( - self.oracle.getId(), - {'from': instance.getOwner()}) + self.oracle.getId(), {"from": instance.getOwner()} + ) def getId(self) -> int: return self.oracle.getId() @@ -216,7 +246,7 @@ def __init__( oracle: GifAyiiOracle, riskpool: GifAyiiRiskpool, name=PRODUCT_NAME, - publishSource=False + publishSource=False, ): self.policy = instance.getPolicy() self.oracle = oracle @@ -228,19 +258,20 @@ def __init__( componentOwnerService = instance.getComponentOwnerService() registry = instance.getRegistry() - print('------ setting up product ------') + print("------ setting up product ------") productOwnerRole = instanceService.getProductOwnerRole() - print('1) grant product owner role {} to product owner {}'.format( - productOwnerRole, productOwner)) + print( + "1) grant product owner role {} to product owner {}".format( + productOwnerRole, productOwner + ) + ) instanceOperatorService.grantRole( - productOwnerRole, - productOwner, - {'from': instance.getOwner()}) + productOwnerRole, productOwner, {"from": instance.getOwner()} + ) - print('2) deploy product by product owner {}'.format( - productOwner)) + print("2) deploy product by product owner {}".format(productOwner)) self.product = AyiiProduct.deploy( s2b32(name), @@ -249,60 +280,74 @@ def __init__( oracle.getId(), riskpool.getId(), insurer, - {'from': productOwner}, - publish_source=publishSource) + {"from": productOwner}, + publish_source=publishSource, + ) tx = history[-1] wait_for_confirmations(tx) - print('3) product {} proposing to instance by product owner {}'.format( - self.product, productOwner)) + print( + "3) product {} proposing to instance by product owner {}".format( + self.product, productOwner + ) + ) - componentOwnerService.propose( - self.product, - {'from': productOwner}) + componentOwnerService.propose(self.product, {"from": productOwner}) tx = history[-1] wait_for_confirmations(tx) - print('4) approval of product id {} by instance operator {}'.format( - self.product.getId(), instance.getOwner())) + print( + "4) approval of product id {} by instance operator {}".format( + self.product.getId(), instance.getOwner() + ) + ) instanceOperatorService.approve( - self.product.getId(), - {'from': instance.getOwner()}) + 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())) + 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()}) + self.product.getId(), erc20Token, {"from": instance.getOwner()} + ) fixedFee = 0 - fractionalFee = 0 # instanceService.getFeeFractionFullUnit() / 10 # corresponds to 10% + fractionalFee = ( + 0 # instanceService.getFeeFractionFullUnit() / 10 # corresponds to 10% + ) # # set fees to zero # fixedFee = 0 # fractionalFee = 0 - print('6) creating premium fee spec (fixed: {}, fractional: {}) for product id {} by instance operator {}'.format( - fixedFee, fractionalFee, self.product.getId(), instance.getOwner())) + 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()}) + b"", + {"from": instance.getOwner()}, + ) - print('7) setting premium fee spec by instance operator {}'.format( - instance.getOwner())) + print( + "7) setting premium fee spec by instance operator {}".format( + instance.getOwner() + ) + ) - instanceOperatorService.setPremiumFees( - feeSpec, - {'from': instance.getOwner()}) + instanceOperatorService.setPremiumFees(feeSpec, {"from": instance.getOwner()}) def getId(self) -> int: return self.product.getId() @@ -336,8 +381,8 @@ def __init__( erc20Token: Account, riskpoolWallet: Account, collateralizationLevel: int, - baseName='Ayii', - publishSource=False + baseName="Ayii", + publishSource=False, ): self.token = erc20Token @@ -349,14 +394,13 @@ def __init__( riskpoolWallet, investor, collateralizationLevel, - '{}Riskpool'.format(baseName), - publishSource) + "{}Riskpool".format(baseName), + publishSource, + ) self.oracle = GifAyiiOracle( - instance, - oracleProvider, - '{}Oracle'.format(baseName), - publishSource) + instance, oracleProvider, "{}Oracle".format(baseName), publishSource + ) self.product = GifAyiiProduct( instance, @@ -365,8 +409,9 @@ def __init__( insurer, self.oracle, self.riskpool, - '{}Product'.format(baseName), - publishSource) + "{}Product".format(baseName), + publishSource, + ) def getToken(self): return self.token diff --git a/scripts/component.py b/scripts/component.py index b7b50e7..23a8218 100644 --- a/scripts/component.py +++ b/scripts/component.py @@ -7,8 +7,8 @@ from brownie import ( Wei, - interface, - Contract, + interface, + Contract, PolicyController, OracleService, ComponentOwnerService, @@ -27,8 +27,9 @@ class GifComponent(object): - def __init__(self, - componentAddress: Account, + def __init__( + self, + componentAddress: Account, ): self.component = contractFromAddress(interface.IComponent, componentAddress) self.instance = GifInstance(registryAddress=self.component.getRegistry()) @@ -39,4 +40,4 @@ def __init__(self, riskpoolService = self.instance.getRiskpoolService() self.component = contractFromAddress(interface.IComponent, componentAddress) - self.instance = GifInstance(registryAddress=self.component.getRegistry()) \ No newline at end of file + self.instance = GifInstance(registryAddress=self.component.getRegistry()) diff --git a/scripts/const.py b/scripts/const.py index 50880a5..b5c6920 100644 --- a/scripts/const.py +++ b/scripts/const.py @@ -3,54 +3,56 @@ # === GIF platform ========================================================== # # GIF release -GIF_RELEASE = '2.0.0' +GIF_RELEASE = "2.0.0" # GIF modules -ACCESS_NAME = 'Access' -BUNDLE_NAME = 'Bundle' -COMPONENT_NAME = 'Component' +ACCESS_NAME = "Access" +BUNDLE_NAME = "Bundle" +COMPONENT_NAME = "Component" -REGISTRY_CONTROLLER_NAME = 'RegistryController' -REGISTRY_NAME = 'Registry' +REGISTRY_CONTROLLER_NAME = "RegistryController" +REGISTRY_NAME = "Registry" -ACCESS_CONTROLLER_NAME = 'AccessController' -ACCESS_NAME = 'Access' +ACCESS_CONTROLLER_NAME = "AccessController" +ACCESS_NAME = "Access" -LICENSE_CONTROLLER_NAME = 'LicenseController' -LICENSE_NAME = 'License' +LICENSE_CONTROLLER_NAME = "LicenseController" +LICENSE_NAME = "License" -POLICY_CONTROLLER_NAME = 'PolicyController' -POLICY_NAME = 'Policy' +POLICY_CONTROLLER_NAME = "PolicyController" +POLICY_NAME = "Policy" -POLICY_DEFAULT_FLOW_NAME = 'PolicyDefaultFlow' -POOL_NAME = 'Pool' +POLICY_DEFAULT_FLOW_NAME = "PolicyDefaultFlow" +POOL_NAME = "Pool" -QUERY_NAME = 'Query' +QUERY_NAME = "Query" -RISKPOOL_CONTROLLER_NAME = 'RiskpoolController' -RISKPOOL_NAME = 'Riskpool' -TREASURY_NAME = 'Treasury' +RISKPOOL_CONTROLLER_NAME = "RiskpoolController" +RISKPOOL_NAME = "Riskpool" +TREASURY_NAME = "Treasury" # GIF services -COMPONENT_OWNER_SERVICE_NAME = 'ComponentOwnerService' -PRODUCT_SERVICE_NAME = 'ProductService' -RISKPOOL_SERVICE_NAME = 'RiskpoolService' -ORACLE_SERVICE_NAME = 'OracleService' -INSTANCE_OPERATOR_SERVICE_NAME = 'InstanceOperatorService' -INSTANCE_SERVICE_NAME = 'InstanceService' +COMPONENT_OWNER_SERVICE_NAME = "ComponentOwnerService" +PRODUCT_SERVICE_NAME = "ProductService" +RISKPOOL_SERVICE_NAME = "RiskpoolService" +ORACLE_SERVICE_NAME = "OracleService" +INSTANCE_OPERATOR_SERVICE_NAME = "InstanceOperatorService" +INSTANCE_SERVICE_NAME = "InstanceService" # === GIF testing =========================================================== # # ZERO_ADDRESS = accounts.at('0x0000000000000000000000000000000000000000') -ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' -COMPROMISED_ADDRESS = '0x0000000000000000000000000000000000000013' +ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" +COMPROMISED_ADDRESS = "0x0000000000000000000000000000000000000013" # TEST account values -ACCOUNTS_MNEMONIC = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat' +ACCOUNTS_MNEMONIC = ( + "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat" +) # TEST oracle/rikspool/product values -PRODUCT_NAME = 'Test.Product' -RISKPOOL_NAME = 'Test.Riskpool' -ORACLE_NAME = 'Test.Oracle' -ORACLE_INPUT_FORMAT = '(bytes input)' -ORACLE_OUTPUT_FORMAT = '(bool output)' +PRODUCT_NAME = "Test.Product" +RISKPOOL_NAME = "Test.Riskpool" +ORACLE_NAME = "Test.Oracle" +ORACLE_INPUT_FORMAT = "(bytes input)" +ORACLE_OUTPUT_FORMAT = "(bool output)" diff --git a/scripts/context.py b/scripts/context.py index 10c6f7f..2190c79 100644 --- a/scripts/context.py +++ b/scripts/context.py @@ -9,31 +9,31 @@ AyiiProduct, AyiiRiskpool, AyiiOracle, - Wei + Wei, ) from scripts.onepassword import signIn, getItem, getSecret -from scripts.deploy_ayii import (from_registry) -from scripts.util import (contract_from_address, decodeEnum) +from scripts.deploy_ayii import from_registry +from scripts.util import contract_from_address, decodeEnum class Context: networkName = network.show_active() - vault = os.getenv('OP_VAULT') + vault = os.getenv("OP_VAULT") stakeholders = [ - 'fundsOwner', - 'instanceOperator', - 'instanceWallet', - 'oracleProvider', - 'chainlinkNodeOperator', - 'riskpoolKeeper', - 'riskpoolWallet', - 'investor', - 'productOwner', - 'insurer', - 'customer1', - 'customer2' + "fundsOwner", + "instanceOperator", + "instanceWallet", + "oracleProvider", + "chainlinkNodeOperator", + "riskpoolKeeper", + "riskpoolWallet", + "investor", + "productOwner", + "insurer", + "customer1", + "customer2", ] feeCapitalFix = 0 feeCapitalPercentage = 0 @@ -41,30 +41,26 @@ class Context: feePremiumPercentage = 0 def __init__(self): - print('Initializing context, please wait...') - self.secretsItem = f'{self.networkName} secrets' + self.initialize() + + def initialize(self): + print("Initializing context, please wait...") + self.secretsItem = f"{self.networkName} secrets" asyncio.run(signIn()) asyncio.run(getItem(self.vault, self.secretsItem)) - self.registry = getSecret('Addresses', 'registry') - self.usdcAccountingToken = getSecret( - 'Addresses', 'usdc_accounting_token') - self.usdc = contract_from_address( - UsdcAccounting, self.usdcAccountingToken - ) + self.registry = getSecret("Addresses", "registry") + self.usdcAccountingToken = getSecret("Addresses", "usdc_accounting_token") + self.usdc = contract_from_address(UsdcAccounting, self.usdcAccountingToken) self.accounts = { - s: accounts.from_mnemonic(getSecret('Mnemonics', s)) + s: accounts.from_mnemonic(getSecret("Mnemonics", s)) for s in self.stakeholders } - ( - instance, - product, - oracle, - riskpool, - componentController - ) = from_registry(self.registry) + (instance, product, oracle, riskpool, componentController) = from_registry( + self.registry + ) self.instanceService = instance.getInstanceService() self.instance = instance self.treasury = instance.getTreasury() @@ -75,41 +71,49 @@ def __init__(self): self.oracleId = oracle.getId() self.productId = product.getId() self.componentController = componentController - self.instanceOperator = self.accounts['instanceOperator'] + self.instanceOperator = self.accounts["instanceOperator"] assert self.instanceService.getInstanceOperator() == self.instanceOperator assert self.riskpool.getFullCollateralizationLevel() == 1000000000000000000 self.fullCollateralizationLevel = self.riskpool.getFullCollateralizationLevel() - self.noPrint = ['accounts', 'stakeholders', 'noPrint', 'components'] + self.noPrint = ["accounts", "stakeholders", "noPrint", "components"] + + def setContext(self, key, value): + setattr(self, key, value) def printContext(self): - print('Context:') + print("Context:") for k, v in self.__dict__.items(): if k not in self.noPrint: - print(f'{k.ljust(30)}: {v}') - print('Accounts:') + print(f"{k.ljust(30)}: {v}") + + def printAccounts(self): + print("Accounts:") for k, v in self.accounts.items(): print( - f'{k.ljust(30)}: {v.address} {str(Wei(v.balance()).to("ether")).rjust(25)}') + f'{k.ljust(30)}: {v.address} {str(Wei(v.balance()).to("ether")).rjust(25)}' + ) def loadComponents(self): componentController = self.componentController components = componentController.components() result = [] with ProgressBar() as pb: - for componentIndex in pb(range(1, components+1), label="Fetching components..."): + for componentIndex in pb( + range(1, components + 1), label="Fetching components..." + ): cAddress = componentController.getComponent(componentIndex) cType = componentController.getComponentType(componentIndex) cState = componentController.getComponentState(componentIndex) componentData = { - 'address': cAddress, - 'type': cType, - 'state': cState, - 'typeStr': decodeEnum('ComponentType', cType), - 'stateStr': decodeEnum('ComponentState', cState) + "address": cAddress, + "type": cType, + "state": cState, + "typeStr": decodeEnum("ComponentType", cType), + "stateStr": decodeEnum("ComponentState", cState), } try: if cType == 0: # Oracle - componentData['additionalData'] = {} + componentData["additionalData"] = {} elif cType == 1: # Product product = contract_from_address(AyiiProduct, cAddress) risks = product.risks() @@ -117,23 +121,22 @@ def loadComponents(self): for riskIndex in range(0, risks): riskId = product.getRiskId(riskIndex) policies += product.policies(riskId) - componentData['additionalData'] = { - 'erc20Token': product.getToken(), - 'riskpoolId': product.getRiskpoolId(), - 'risks': product.risks(), - 'policies': policies, - 'applications': product.applications() + componentData["additionalData"] = { + "erc20Token": product.getToken(), + "riskpoolId": product.getRiskpoolId(), + "risks": product.risks(), + "policies": policies, + "applications": product.applications(), } elif cType == 2: # Riskpool - riskpool = contract_from_address( - AyiiRiskpool, cAddress) - componentData['additionalData'] = { - 'erc20Token': riskpool.getErc20Token(), - 'capital': riskpool.getCapital(), - 'bundles': riskpool.bundles() + riskpool = contract_from_address(AyiiRiskpool, cAddress) + componentData["additionalData"] = { + "erc20Token": riskpool.getErc20Token(), + "capital": riskpool.getCapital(), + "bundles": riskpool.bundles(), } except Exception as e: - componentData['error'] = str(e) + componentData["error"] = str(e) result.append(componentData) @@ -141,8 +144,8 @@ def loadComponents(self): return result def getComponents(self, type=None, reload=False): - if reload or not hasattr(self, 'components'): + if reload or not hasattr(self, "components"): self.loadComponents() if type is not None: - return list(filter(lambda x: x['type'] == type, self.components)) + return list(filter(lambda x: x["type"] == type, self.components)) return self.components diff --git a/scripts/deploy_ayii.py b/scripts/deploy_ayii.py index f4f7466..41ec1d7 100644 --- a/scripts/deploy_ayii.py +++ b/scripts/deploy_ayii.py @@ -13,39 +13,46 @@ AyiiProduct, AyiiOracle, AyiiRiskpool, - ComponentController + ComponentController, ) from scripts.ayii_product import GifAyiiProductComplete from scripts.instance import GifInstance -from scripts.util import contract_from_address, s2b32, getChainName, utcStr, decodeEnum, fromWei +from scripts.util import ( + contract_from_address, + s2b32, + getChainName, + utcStr, + decodeEnum, + fromWei, +) from scripts.prompt import confirm -INSTANCE_OPERATOR = 'instanceOperator' -INSTANCE_WALLET = 'instanceWallet' -ORACLE_PROVIDER = 'oracleProvider' -NODE_OPERATOR = 'chainlinkNodeOperator' -RISKPOOL_KEEPER = 'riskpoolKeeper' -RISKPOOL_WALLET = 'riskpoolWallet' -INVESTOR = 'investor' -PRODUCT_OWNER = 'productOwner' -INSURER = 'insurer' -CUSTOMER1 = 'customer1' -CUSTOMER2 = 'customer2' - -ERC20_TOKEM = 'erc20Token' -INSTANCE = 'instance' -INSTANCE_SERVICE = 'instanceService' -INSTANCE_OPERATOR_SERVICE = 'instanceOperatorService' -COMPONENT_OWNER_SERVICE = 'componentOwnerService' -PRODUCT = 'product' -ORACLE = 'oracle' -RISKPOOL = 'riskpool' - -RISK_ID1 = 'riskId1' -RISK_ID2 = 'riskId2' -PROCESS_ID1 = 'processId1' -PROCESS_ID2 = 'processId2' +INSTANCE_OPERATOR = "instanceOperator" +INSTANCE_WALLET = "instanceWallet" +ORACLE_PROVIDER = "oracleProvider" +NODE_OPERATOR = "chainlinkNodeOperator" +RISKPOOL_KEEPER = "riskpoolKeeper" +RISKPOOL_WALLET = "riskpoolWallet" +INVESTOR = "investor" +PRODUCT_OWNER = "productOwner" +INSURER = "insurer" +CUSTOMER1 = "customer1" +CUSTOMER2 = "customer2" + +ERC20_TOKEM = "erc20Token" +INSTANCE = "instance" +INSTANCE_SERVICE = "instanceService" +INSTANCE_OPERATOR_SERVICE = "instanceOperatorService" +COMPONENT_OWNER_SERVICE = "componentOwnerService" +PRODUCT = "product" +ORACLE = "oracle" +RISKPOOL = "riskpool" + +RISK_ID1 = "riskId1" +RISK_ID2 = "riskId2" +PROCESS_ID1 = "processId1" +PROCESS_ID2 = "processId2" GAS_PRICE = web3.eth.gas_price GAS_PRICE_SAFETY_FACTOR = 1.25 @@ -62,45 +69,50 @@ REQUIRED_FUNDS = { INSTANCE_OPERATOR: REQUIRED_FUNDS_L, - INSTANCE_WALLET: REQUIRED_FUNDS_S, - PRODUCT_OWNER: REQUIRED_FUNDS_M, - INSURER: REQUIRED_FUNDS_M, - ORACLE_PROVIDER: int(1.2 * 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, + INSTANCE_WALLET: REQUIRED_FUNDS_S, + PRODUCT_OWNER: REQUIRED_FUNDS_M, + INSURER: REQUIRED_FUNDS_M, + ORACLE_PROVIDER: int(1.2 * 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 help(): - print('from scripts.util import s2b, b2s, contract_from_address') - print('from scripts.deploy_ayii import stakeholders_accounts_ganache, check_funds, amend_funds, deploy, deploy_product_riskpool, from_registry, from_component, verify_deploy') + print("from scripts.util import s2b, b2s, contract_from_address") + print( + "from scripts.deploy_ayii import stakeholders_accounts_ganache, check_funds, amend_funds, deploy, deploy_product_riskpool, from_registry, from_component, verify_deploy" + ) print() - print('#--- deploy ganache setup ---------------------------------------#') - print('a = stakeholders_accounts_ganache()') + print("#--- deploy ganache setup ---------------------------------------#") + print("a = stakeholders_accounts_ganache()") print("instance_operator = a['instanceOperator']") print() - print('check_funds(a, token)') - print('# amend_funds(a)') - print('d = deploy(a, token, False)') + print("check_funds(a, token)") + print("# amend_funds(a)") + print("d = deploy(a, token, False)") print() print( - "(instance, product, oracle, riskpool) = from_registry(d['instance'].getRegistry())") + "(instance, product, oracle, riskpool) = from_registry(d['instance'].getRegistry())" + ) print("verify_deploy(a, token, instance.getRegistry())") print() - print('#--- deploy to existing instance --------------------------------#') - print('check_funds(a, token)') + print("#--- deploy to existing instance --------------------------------#") + print("check_funds(a, token)") print("registry_address = d['instance'].getRegistry()") print("registry_address = instance.getRegistry().address") - print('collateralization_level = 0') + print("collateralization_level = 0") print() - print('product_old = product') - print('oracle_old = oracle') - print('riskpool_old = riskpool') + print("product_old = product") + print("oracle_old = oracle") + print("riskpool_old = riskpool") print() - print('(instance, product, oracle, riskpool) = deploy_product_riskpool(registry_address, a, token, collateralization_level)') + print( + "(instance, product, oracle, riskpool) = deploy_product_riskpool(registry_address, a, token, collateralization_level)" + ) print(f"You are on chain {network.show_active()}") @@ -140,30 +152,38 @@ def check_funds(stakeholders_accounts, erc20_token): fundsMissing = 0 for accountName, requiredAmount in REQUIRED_FUNDS.items(): if a[accountName].balance() >= REQUIRED_FUNDS[accountName]: - print('{} funding ok'.format(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() - )) + 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)) + 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 - )) + 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 @@ -171,20 +191,24 @@ def check_funds(stakeholders_accounts, erc20_token): if erc20_token: erc20_success = check_erc20_funds(a, erc20_token) else: - print('WARNING: no erc20 token defined, skipping erc20 funds checking') + 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)) + 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') + 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 @@ -195,27 +219,26 @@ 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)) + 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') + print("re-run check_funds() to verify funding before deploy") def _print_constants(): chainId = web3.eth.chain_id - print('chain id: {} {}'.format(chainId, getChainName(chainId))) - print('gas price [Mwei]: {}'.format(GAS_PRICE/10**6)) - print('gas price safety factor: {}'.format(GAS_PRICE_SAFETY_FACTOR)) + print("chain id: {} {}".format(chainId, getChainName(chainId))) + 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("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)) + 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): @@ -228,44 +251,41 @@ def _get_balances(stakeholders_accounts): def _get_balances_delta(balances_before, balances_after): - balance_delta = {'total': 0} + 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] + 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)) + print("--- {} ---".format(title)) gasPrice = network.gas_price() - print('gas price: {}'.format(gasPrice)) + 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)) + if accountName != "total": + if gasPrice != "auto": + print("account {}: gas {}".format(accountName, amount / gasPrice)) else: - print('account {}: amount {}'.format(accountName, amount)) + print("account {}: amount {}".format(accountName, amount)) - print('-----------------------------') - if gasPrice != 'auto': - print('account total: gas {}'.format( - balances_delta['total'] / gasPrice)) + print("-----------------------------") + if gasPrice != "auto": + print("account total: gas {}".format(balances_delta["total"] / gasPrice)) else: - print('account total: amount {}'.format(balances_delta['total'])) - print('=============================') + print("account total: amount {}".format(balances_delta["total"])) + print("=============================") def deploy_setup_including_token( - stakeholders_accounts, - erc20_token, - publishSource=False + stakeholders_accounts, erc20_token, publishSource=False ): return deploy(stakeholders_accounts, erc20_token, None) @@ -276,7 +296,7 @@ def verify_deploy( registry_address, riskpoolId=0, oracleId=0, - productId=0 + productId=0, ): # define stakeholder accounts a = stakeholders_accounts @@ -287,101 +307,93 @@ def verify_deploy( riskpoolWallet = a[RISKPOOL_WALLET] productOwner = a[PRODUCT_OWNER] - ( - instance, - product, - oracle, - riskpool, - componentController - ) = from_registry( - registry_address, - riskpoolId=riskpoolId, - oracleId=oracleId, - productId=productId + (instance, product, oracle, riskpool, componentController) = from_registry( + registry_address, riskpoolId=riskpoolId, oracleId=oracleId, productId=productId ) instanceService = instance.getInstanceService() + verify_element("Registry", instanceService.getRegistry(), registry_address) verify_element( - 'Registry', instanceService.getRegistry(), registry_address) - verify_element( - 'InstanceOperator', instanceService.getInstanceOperator(), instanceOperator) + "InstanceOperator", instanceService.getInstanceOperator(), instanceOperator + ) verify_element( - 'InstanceWallet', instanceService.getInstanceWallet(), instanceWallet) + "InstanceWallet", instanceService.getInstanceWallet(), instanceWallet + ) + verify_element("RiskpoolId", riskpool.getId(), riskpoolId) + verify_element("RiskpoolType", instanceService.getComponentType(riskpoolId), 2) + verify_element("RiskpoolState", instanceService.getComponentState(riskpoolId), 3) + verify_element("RiskpoolKeeper", riskpool.owner(), riskpoolKeeper) verify_element( - 'RiskpoolId', riskpool.getId(), riskpoolId) - verify_element( - 'RiskpoolType', instanceService.getComponentType(riskpoolId), 2) - verify_element( - 'RiskpoolState', instanceService.getComponentState(riskpoolId), 3) - verify_element( - 'RiskpoolKeeper', riskpool.owner(), riskpoolKeeper) - verify_element( - 'RiskpoolWallet', instanceService.getRiskpoolWallet(riskpoolId), riskpoolWallet) - verify_element( - 'RiskpoolBalance', instanceService.getBalance(riskpoolId), erc20_token.balanceOf(riskpoolWallet)) + "RiskpoolWallet", instanceService.getRiskpoolWallet(riskpoolId), riskpoolWallet + ) verify_element( - 'RiskpoolToken', riskpool.getErc20Token(), erc20_token.address) + "RiskpoolBalance", + instanceService.getBalance(riskpoolId), + erc20_token.balanceOf(riskpoolWallet), + ) + verify_element("RiskpoolToken", riskpool.getErc20Token(), erc20_token.address) - verify_element( - 'OracleId', oracle.getId(), oracleId) - verify_element( - 'OracleType', instanceService.getComponentType(oracleId), 0) - verify_element( - 'OracleState', instanceService.getComponentState(oracleId), 3) - verify_element( - 'OracleProvider', oracle.owner(), oracleProvider) + verify_element("OracleId", oracle.getId(), oracleId) + verify_element("OracleType", instanceService.getComponentType(oracleId), 0) + verify_element("OracleState", instanceService.getComponentState(oracleId), 3) + verify_element("OracleProvider", oracle.owner(), oracleProvider) - verify_element( - 'ProductId', product.getId(), productId) - verify_element( - 'ProductType', instanceService.getComponentType(productId), 1) - verify_element( - 'ProductState', instanceService.getComponentState(productId), 3) - verify_element( - 'ProductOwner', product.owner(), productOwner) - verify_element( - 'ProductToken', product.getToken(), erc20_token.address) - verify_element( - 'ProductRiskpool', product.getRiskpoolId(), riskpoolId) - - print('InstanceWalletBalance {:.2f}'.format(erc20_token.balanceOf( - instanceService.getInstanceWallet())/10**erc20_token.decimals())) - print('RiskpoolWalletTVL {:.2f}'.format( - instanceService.getTotalValueLocked(riskpoolId)/10**erc20_token.decimals())) - print('RiskpoolWalletCapacity {:.2f}'.format( - instanceService.getCapacity(riskpoolId)/10**erc20_token.decimals())) - print('RiskpoolWalletBalance {:.2f}'.format(erc20_token.balanceOf( - instanceService.getRiskpoolWallet(riskpoolId))/10**erc20_token.decimals())) - print('RiskpoolBundles {}'.format(riskpool.bundles())) + verify_element("ProductId", product.getId(), productId) + verify_element("ProductType", instanceService.getComponentType(productId), 1) + verify_element("ProductState", instanceService.getComponentState(productId), 3) + verify_element("ProductOwner", product.owner(), productOwner) + verify_element("ProductToken", product.getToken(), erc20_token.address) + verify_element("ProductRiskpool", product.getRiskpoolId(), riskpoolId) + + print( + "InstanceWalletBalance {:.2f}".format( + erc20_token.balanceOf(instanceService.getInstanceWallet()) + / 10 ** erc20_token.decimals() + ) + ) + print( + "RiskpoolWalletTVL {:.2f}".format( + instanceService.getTotalValueLocked(riskpoolId) + / 10 ** erc20_token.decimals() + ) + ) + print( + "RiskpoolWalletCapacity {:.2f}".format( + instanceService.getCapacity(riskpoolId) / 10 ** erc20_token.decimals() + ) + ) + print( + "RiskpoolWalletBalance {:.2f}".format( + erc20_token.balanceOf(instanceService.getRiskpoolWallet(riskpoolId)) + / 10 ** erc20_token.decimals() + ) + ) + print("RiskpoolBundles {}".format(riskpool.bundles())) # bundle_id = riskpool.getBundleId(0) printBundle(riskpool, 0) - print('ProductRisks {}'.format(product.risks())) - print('ProductApplications {}'.format(product.applications())) + print("ProductRisks {}".format(product.risks())) + print("ProductApplications {}".format(product.applications())) def printBundle(riskpool, bundleId): for key, value in riskpool.getBundle(bundleId).dict().items(): - if key in ['createdAt', 'updatedAt']: # Apply conversion for specific keys + if key in ["createdAt", "updatedAt"]: # Apply conversion for specific keys value = utcStr(value) - if key in ['state']: - value = decodeEnum('BundleState', value) - if key in ['balance', 'lockedCapital', 'capital']: + if key in ["state"]: + value = decodeEnum("BundleState", value) + if key in ["balance", "lockedCapital", "capital"]: value = fromWei(value, 6) print(f" {key:<15}: {value}") -def verify_element( - element, - value, - expected_value -): +def verify_element(element, value, expected_value): if value == expected_value: - print('{} OK {}'.format(element, value)) + print("{} OK {}".format(element, value)) else: - print('{} ERROR {} expected {}'.format(element, value, expected_value)) + print("{} ERROR {} expected {}".format(element, value, expected_value)) def deploy_product_with_oracle_riskpool( @@ -389,7 +401,7 @@ def deploy_product_with_oracle_riskpool( stakeholders_accounts, erc20_token, collateralizationLevel, - publishSource=False + publishSource=False, ): if not confirm(f"deployment with product and riskpool"): return @@ -405,24 +417,31 @@ def deploy_product_with_oracle_riskpool( insurer = a[INSURER] # create basename including unix timestamp - baseName = 'Ayii_{}_'.format(int(datetime.now().timestamp())) + baseName = "Ayii_{}_".format(int(datetime.now().timestamp())) if not check_funds(a, erc20_token): - print('ERROR: insufficient funding, aborting deploy') + print("ERROR: insufficient funding, aborting deploy") return if not erc20_token: - print('ERROR: no erc20 defined, aborting deploy') + print("ERROR: no erc20 defined, aborting deploy") return - print('====== setting erc20 token to {} ======'.format(erc20_token)) + print("====== setting erc20 token to {} ======".format(erc20_token)) erc20Token = erc20_token - print('====== getting instance from registry address {} ======'.format( - registry_address)) + print( + "====== getting instance from registry address {} ======".format( + registry_address + ) + ) (instance, product, oracle, riskpool) = from_registry(registry_address) - print("====== deploy ayii product /w base name '{}' and riskpool collateralization level {} ======".format(baseName, collateralizaionLevel)) + print( + "====== deploy ayii product /w base name '{}' and riskpool collateralization level {} ======".format( + baseName, collateralizaionLevel + ) + ) ayiiDeploy = GifAyiiProductComplete( instance, productOwner, @@ -434,7 +453,7 @@ def deploy_product_with_oracle_riskpool( riskpoolWallet, collateralizationLevel, baseName=baseName, - publishSource=publishSource + publishSource=publishSource, ) ayiiProduct = ayiiDeploy.getProduct() @@ -446,27 +465,18 @@ def deploy_product_with_oracle_riskpool( riskpool = ayiiRiskpool.getContract() print("====== create bundle for investor {} ======".format(investor)) - initial_funding = 10**erc20Token.decimals() - bundle_filter = b'' - erc20Token.transfer(investor, initial_funding, {'from': instanceOperator}) + initial_funding = 10 ** erc20Token.decimals() + bundle_filter = b"" + erc20Token.transfer(investor, initial_funding, {"from": instanceOperator}) erc20Token.approve( - instance.getTreasury().address, - initial_funding, {'from': investor}) - riskpool.createBundle(bundle_filter, initial_funding, {'from': investor}) - - return ( - instance, - product, - oracle, - riskpool + instance.getTreasury().address, initial_funding, {"from": investor} ) + riskpool.createBundle(bundle_filter, initial_funding, {"from": investor}) + return (instance, product, oracle, riskpool) -def deploy( - stakeholders_accounts, - erc20_token, - publishSource=False -): + +def deploy(stakeholders_accounts, erc20_token, publishSource=False): if not confirm("full deployment"): return @@ -484,32 +494,42 @@ def deploy( customer2 = a[CUSTOMER2] if not check_funds(a, erc20_token): - print('ERROR: insufficient funding, aborting deploy') + 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') + print("ERROR: no erc20 defined, aborting deploy") return - print('====== setting erc20 token to {} ======'.format(erc20_token)) + print("====== setting erc20 token to {} ======".format(erc20_token)) erc20Token = erc20_token - print('====== deploy gif instance ======') + print("====== deploy gif instance ======") instance = GifInstance( - instanceOperator, instanceWallet=instanceWallet, publishSource=publishSource) + instanceOperator, instanceWallet=instanceWallet, publishSource=publishSource + ) instanceService = instance.getInstanceService() instanceOperatorService = instance.getInstanceOperatorService() componentOwnerService = instance.getComponentOwnerService() - print('====== deploy ayii product ======') + print("====== deploy ayii product ======") collateralizationLevel = instanceService.getFullCollateralizationLevel() ayiiDeploy = GifAyiiProductComplete( - instance, productOwner, insurer, oracleProvider, chainlinkNodeOperator, - riskpoolKeeper, investor, erc20Token, riskpoolWallet, collateralizationLevel, - publishSource=publishSource) + instance, + productOwner, + insurer, + oracleProvider, + chainlinkNodeOperator, + riskpoolKeeper, + investor, + erc20Token, + riskpoolWallet, + collateralizationLevel, + publishSource=publishSource, + ) # assess balances at beginning of deploy balances_after_deploy = _get_balances(stakeholders_accounts) @@ -522,38 +542,37 @@ def deploy( oracle = ayiiOracle.getContract() riskpool = ayiiRiskpool.getContract() - print('====== create initial setup ======') + print("====== create initial setup ======") bundleInitialFunding = INITIAL_ERC20_BUNDLE_FUNDING - print('1) investor {} funding (transfer/approve) with {} token for erc20 {}'.format( - investor, bundleInitialFunding, erc20Token)) + 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}) + 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())) + print( + "2) riskpool wallet {} approval for instance treasury {}".format( + riskpoolWallet, instance.getTreasury() + ) + ) erc20Token.approve( - instance.getTreasury(), bundleInitialFunding, { - 'from': riskpoolWallet}) + instance.getTreasury(), bundleInitialFunding, {"from": riskpoolWallet} + ) - print('3) riskpool bundle creation by investor {}'.format( - investor)) + print("3) riskpool bundle creation by investor {}".format(investor)) applicationFilter = bytes(0) - riskpool.createBundle( - applicationFilter, - bundleInitialFunding, - {'from': investor}) + riskpool.createBundle(applicationFilter, bundleInitialFunding, {"from": investor}) # create risks - projectId = s2b32('2022.kenya.wfp.ayii') - uaiId = [s2b32('1234'), s2b32('2345')] - cropId = s2b32('mixed') + projectId = s2b32("2022.kenya.wfp.ayii") + uaiId = [s2b32("1234"), s2b32("2345")] + cropId = s2b32("mixed") triggerFloat = 0.75 exitFloat = 0.1 @@ -566,40 +585,47 @@ def deploy( tsi = multiplier * tsiFloat aph = [multiplier * aphFloat[0], multiplier * aphFloat[1]] - print('4) risk creation (2x) by insurer {}'.format( - insurer)) + print("4) risk creation (2x) by insurer {}".format(insurer)) tx = [None, None] tx[0] = product.createRisk( - projectId, uaiId[0], cropId, trigger, exit_, tsi, aph[0], {'from': insurer}) + projectId, uaiId[0], cropId, trigger, exit_, tsi, aph[0], {"from": insurer} + ) tx[1] = product.createRisk( - projectId, uaiId[1], cropId, trigger, exit_, tsi, aph[1], {'from': insurer}) + projectId, uaiId[1], cropId, trigger, exit_, tsi, aph[1], {"from": insurer} + ) - riskId1 = tx[0].events['LogAyiiRiskDataCreated']['riskId'] - riskId2 = tx[1].events['LogAyiiRiskDataCreated']['riskId'] + riskId1 = tx[0].events["LogAyiiRiskDataCreated"]["riskId"] + riskId2 = tx[1].events["LogAyiiRiskDataCreated"]["riskId"] customerFunding = 1000 - print('5) customer {} funding (transfer/approve) with {} token for erc20 {}'.format( - customer, customerFunding, erc20Token)) + 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}) + 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 {}, {} by insurer {}'.format( - customer, customer2, insurer)) + print( + "6) policy creation (2x) for customers {}, {} by insurer {}".format( + customer, customer2, insurer + ) + ) tx[0] = product.applyForPolicy( - customer, premium[0], sumInsured[0], riskId1, {'from': insurer}) + customer, premium[0], sumInsured[0], riskId1, {"from": insurer} + ) tx[1] = product.applyForPolicy( - customer2, premium[1], sumInsured[1], riskId2, {'from': insurer}) + customer2, premium[1], sumInsured[1], riskId2, {"from": insurer} + ) - processId1 = tx[0].events['LogAyiiPolicyCreated']['policyId'] - processId2 = tx[1].events['LogAyiiPolicyCreated']['policyId'] + processId1 = tx[0].events["LogAyiiPolicyCreated"]["policyId"] + processId2 = tx[1].events["LogAyiiPolicyCreated"]["policyId"] deploy_result = { INSTANCE_OPERATOR: instanceOperator, @@ -615,8 +641,12 @@ def deploy( 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), + INSTANCE_OPERATOR_SERVICE: contract_from_address( + InstanceOperatorService, instanceOperatorService + ), + COMPONENT_OWNER_SERVICE: contract_from_address( + ComponentOwnerService, componentOwnerService + ), PRODUCT: contract_from_address(AyiiProduct, product), ORACLE: contract_from_address(AyiiOracle, oracle), RISKPOOL: contract_from_address(AyiiRiskpool, riskpool), @@ -626,40 +656,39 @@ def deploy( PROCESS_ID2: processId2, } - print('deploy_result: {}'.format(deploy_result)) + print("deploy_result: {}".format(deploy_result)) - print('====== deploy and setup creation complete ======') - print('') + 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)) + 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_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 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 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("--------------------------------------------------------------------") + print("total deploy + setup{}".format(delta_total["total"])) + print("deploy + setup{}".format(delta_total)) - print('--------------------------------------------------------------------') + print("--------------------------------------------------------------------") - _pretty_print_delta('gas usage deploy', delta_deploy) - _pretty_print_delta('gas usage total', delta_total) + _pretty_print_delta("gas usage deploy", delta_deploy) + _pretty_print_delta("gas usage total", delta_total) return deploy_result @@ -670,11 +699,7 @@ def from_component(componentAddress): def from_registry( - registryAddress, - productId=0, - oracleId=0, - riskpoolId=0, - verbose=False + registryAddress, productId=0, oracleId=0, riskpoolId=0, verbose=False ): instance = GifInstance(registryAddress=registryAddress) registry = instance.getRegistry() @@ -692,11 +717,11 @@ def from_registry( if productId > 0: componentId = productId else: - componentId = instanceService.getProductId(products-1) + componentId = instanceService.getProductId(products - 1) if verbose and products > 1: - print('1 product expected, {} products available'.format(products)) - print('returning last product available') + print("1 product expected, {} products available".format(products)) + print("returning last product available") componentAddress = instanceService.getComponent(componentId) product = contract_from_address(AyiiProduct, componentAddress) @@ -704,22 +729,25 @@ def from_registry( if product.getType() != 1: product = None if verbose: - print('component (type={}) with id {} is not product'.format( - product.getType(), componentId)) - print('no product returned (None)') + print( + "component (type={}) with id {} is not product".format( + product.getType(), componentId + ) + ) + print("no product returned (None)") elif verbose: - print('1 product expected, no product available') - print('no product returned (None)') + print("1 product expected, no product available") + print("no product returned (None)") if oracles >= 1: if oracleId > 0: componentId = oracleId else: - componentId = instanceService.getOracleId(oracles-1) + componentId = instanceService.getOracleId(oracles - 1) if verbose and oracles > 1: - print('1 oracle expected, {} oracles available'.format(oracles)) - print('returning last oracle available') + print("1 oracle expected, {} oracles available".format(oracles)) + print("returning last oracle available") componentAddress = instanceService.getComponent(componentId) oracle = contract_from_address(AyiiOracle, componentAddress) @@ -727,22 +755,25 @@ def from_registry( if oracle.getType() != 0: oracle = None if verbose: - print('component (type={}) with id {} is not oracle'.format( - component.getType(), componentId)) - print('no oracle returned (None)') + print( + "component (type={}) with id {} is not oracle".format( + component.getType(), componentId + ) + ) + print("no oracle returned (None)") elif verbose: - print('1 oracle expected, no oracles available') - print('no oracle returned (None)') + print("1 oracle expected, no oracles available") + print("no oracle returned (None)") if riskpools >= 1: if riskpoolId > 0: componentId = riskpoolId else: - componentId = instanceService.getRiskpoolId(riskpools-1) + componentId = instanceService.getRiskpoolId(riskpools - 1) if verbose and riskpools > 1: - print('1 riskpool expected, {} riskpools available'.format(riskpools)) - print('returning last riskpool available') + print("1 riskpool expected, {} riskpools available".format(riskpools)) + print("returning last riskpool available") componentAddress = instanceService.getComponent(componentId) riskpool = contract_from_address(AyiiRiskpool, componentAddress) @@ -750,15 +781,19 @@ def from_registry( if riskpool.getType() != 2: riskpool = None if verbose: - print('component (type={}) with id {} is not riskpool'.format( - component.getType(), componentId)) - print('no riskpool returned (None)') + print( + "component (type={}) with id {} is not riskpool".format( + component.getType(), componentId + ) + ) + print("no riskpool returned (None)") elif verbose: - print('1 riskpool expected, no riskpools available') - print('no riskpool returned (None)') + print("1 riskpool expected, no riskpools available") + print("no riskpool returned (None)") componentController = contract_from_address( - ComponentController, registry.getContract(s2b32('Component'))) + ComponentController, registry.getContract(s2b32("Component")) + ) return (instance, product, oracle, riskpool, componentController) @@ -767,42 +802,22 @@ def dry_run_create_risks(product, insurer): if not confirm("dry run create risks"): return - project = '2022.kenya.wfp.ayii' - crop = 'maize' + project = "2022.kenya.wfp.ayii" + crop = "maize" trigger = 0.75 tsi = 0.9 exit_ = 0.1 - aez = [ - 22, - 23, - 26, - 29, - 30, - 32, - 34, - 38, - 39, - 40] - - aph = [ - 2.28, - 2.42, - 2.14, - 2.01, - 3.03, - 3.05, - 2.38, - 1.84, - 2.60, - 2.30] + aez = [22, 23, 26, 29, 30, 32, 34, 38, 39, 40] + + aph = [2.28, 2.42, 2.14, 2.01, 3.03, 3.05, 2.38, 1.84, 2.60, 2.30] print("project, aez, crop, trigger, exit, tsi, aph, riskId") for i in range(len(aez)): riskId = create_risk( - product, insurer, project, - aez[i], crop, trigger, exit_, tsi, aph[i]) + product, insurer, project, aez[i], crop, trigger, exit_, tsi, aph[i] + ) print(project, aez[i], crop, trigger, exit_, tsi, aph[i], riskId) @@ -825,7 +840,7 @@ def create_risk(product, insurer, project, uai, crop, trigger, exit_, tsi, aph): exitInt, tsiInt, aphInt, - {'from': insurer} + {"from": insurer}, ) - return tx.events['LogAyiiRiskDataCreated']['riskId'] + return tx.events["LogAyiiRiskDataCreated"]["riskId"] diff --git a/scripts/instance.py b/scripts/instance.py index f1551b2..c166b94 100644 --- a/scripts/instance.py +++ b/scripts/instance.py @@ -30,7 +30,7 @@ PolicyDefaultFlow, InstanceOperatorService, InstanceService, - network + network, ) from scripts.const import ( @@ -53,39 +53,39 @@ class GifRegistry(object): - def __init__( - self, - owner: Account, - publishSource: bool = False - ): + def __init__(self, owner: Account, publishSource: bool = False): controller = RegistryController.deploy( - {'from': owner}, - publish_source=publishSource) + {"from": owner}, publish_source=publishSource + ) encoded_initializer = encode_function_data( - s2b32(GIF_RELEASE), - initializer=controller.initializeRegistry) + s2b32(GIF_RELEASE), initializer=controller.initializeRegistry + ) proxy = CoreProxy.deploy( controller.address, encoded_initializer, - {'from': owner}, - publish_source=publishSource) + {"from": owner}, + publish_source=publishSource, + ) self.owner = owner self.registry = contractFromAddress(RegistryController, proxy.address) - print('owner {}'.format(owner)) - print('controller.address {}'.format(controller.address)) - print('proxy.address {}'.format(proxy.address)) - print('registry.address {}'.format(self.registry.address)) - print('registry.getContract(InstanceOperatorService) {}'.format( - self.registry.getContract(s2h("InstanceOperatorService")))) - + print("owner {}".format(owner)) + print("controller.address {}".format(controller.address)) + print("proxy.address {}".format(proxy.address)) + print("registry.address {}".format(self.registry.address)) + print( + "registry.getContract(InstanceOperatorService) {}".format( + self.registry.getContract(s2h("InstanceOperatorService")) + ) + ) + + self.registry.register(s2b32("Registry"), proxy.address, {"from": owner}) self.registry.register( - s2b32("Registry"), proxy.address, {'from': owner}) - self.registry.register(s2b32("RegistryController"), - controller.address, {'from': owner}) + s2b32("RegistryController"), controller.address, {"from": owner} + ) def getOwner(self) -> Account: return self.owner @@ -102,88 +102,104 @@ def __init__( instanceWallet: Account = None, registryAddress=None, publishSource: bool = False, - setInstanceWallet: bool = True + setInstanceWallet: bool = True, ): if registryAddress: self.fromRegistryAddress(registryAddress) self.owner = owner elif owner: - super().__init__( - owner, - publishSource) + super().__init__(owner, publishSource) - self.deployWithRegistry( - self.registry, - owner, - publishSource) + self.deployWithRegistry(self.registry, owner, publishSource) if setInstanceWallet: self.instanceOperatorService.setInstanceWallet( - instanceWallet, - {'from': owner}) + instanceWallet, {"from": owner} + ) else: - raise ValueError( - 'either owner or registry_address need to be provided') + raise ValueError("either owner or registry_address need to be provided") def __str__(self): - return 'GifInstance class with registry = {}'.format(self.registry.address) + return "GifInstance class with registry = {}".format(self.registry.address) def deployWithRegistry( - self, - registry: GifRegistry, - owner: Account, - publishSource: bool + self, registry: GifRegistry, owner: Account, publishSource: bool ): # gif instance tokens self.bundleToken = deployGifToken( - "BundleToken", BundleToken, registry, owner, publishSource) + "BundleToken", BundleToken, registry, owner, publishSource + ) self.riskpoolToken = deployGifToken( - "RiskpoolToken", RiskpoolToken, registry, owner, publishSource) + "RiskpoolToken", RiskpoolToken, registry, owner, publishSource + ) # modules (need to be deployed first) # deploy order needs to respect module dependencies self.access = deployGifModuleV2( - "Access", AccessController, registry, owner, publishSource) + "Access", AccessController, registry, owner, publishSource + ) self.component = deployGifModuleV2( - "Component", ComponentController, registry, owner, publishSource) + "Component", ComponentController, registry, owner, publishSource + ) self.query = deployGifModuleV2( - "Query", QueryModule, registry, owner, publishSource) + "Query", QueryModule, registry, owner, publishSource + ) self.license = deployGifModuleV2( - "License", LicenseController, registry, owner, publishSource) + "License", LicenseController, registry, owner, publishSource + ) self.policy = deployGifModuleV2( - "Policy", PolicyController, registry, owner, publishSource) + "Policy", PolicyController, registry, owner, publishSource + ) self.bundle = deployGifModuleV2( - "Bundle", BundleController, registry, owner, publishSource) + "Bundle", BundleController, registry, owner, publishSource + ) self.pool = deployGifModuleV2( - "Pool", PoolController, registry, owner, publishSource) + "Pool", PoolController, registry, owner, publishSource + ) self.treasury = deployGifModuleV2( - "Treasury", TreasuryModule, registry, owner, publishSource) + "Treasury", TreasuryModule, registry, owner, publishSource + ) # TODO these contracts do not work with proxy pattern self.policyFlow = deployGifService( - PolicyDefaultFlow, registry, owner, publishSource) + PolicyDefaultFlow, registry, owner, publishSource + ) # services self.instanceService = deployGifModuleV2( - "InstanceService", InstanceService, registry, owner, publishSource) + "InstanceService", InstanceService, registry, owner, publishSource + ) self.componentOwnerService = deployGifModuleV2( - "ComponentOwnerService", ComponentOwnerService, registry, owner, publishSource) + "ComponentOwnerService", + ComponentOwnerService, + registry, + owner, + publishSource, + ) self.oracleService = deployGifModuleV2( - "OracleService", OracleService, registry, owner, publishSource) + "OracleService", OracleService, registry, owner, publishSource + ) self.riskpoolService = deployGifModuleV2( - "RiskpoolService", RiskpoolService, registry, owner, publishSource) + "RiskpoolService", RiskpoolService, registry, owner, publishSource + ) # TODO these contracts do not work with proxy pattern self.productService = deployGifService( - ProductService, registry, owner, publishSource) + ProductService, registry, owner, publishSource + ) # needs to be the last module to register as it will # perform some post deploy wirings and changes the address # of the instance operator service to its true address self.instanceOperatorService = deployGifModuleV2( - "InstanceOperatorService", InstanceOperatorService, registry, owner, publishSource) + "InstanceOperatorService", + InstanceOperatorService, + registry, + owner, + publishSource, + ) # post deploy wiring steps # self.bundleToken.setBundleModule(self.bundle) @@ -192,36 +208,37 @@ def deployWithRegistry( assert 32 == registry.contracts() def fromRegistryAddress(self, registry_address): - self.registry = contractFromAddress( - RegistryController, registry_address) + self.registry = contractFromAddress(RegistryController, registry_address) self.access = self.contractFromGifRegistry(AccessController, "Access") - self.component = self.contractFromGifRegistry( - AccessController, "Component") + self.component = self.contractFromGifRegistry(AccessController, "Component") self.query = self.contractFromGifRegistry(QueryModule, "Query") - self.license = self.contractFromGifRegistry( - LicenseController, "License") + self.license = self.contractFromGifRegistry(LicenseController, "License") self.policy = self.contractFromGifRegistry(PolicyController, "Policy") self.bundle = self.contractFromGifRegistry(BundleController, "Bundle") self.pool = self.contractFromGifRegistry(PoolController, "Pool") - self.treasury = self.contractFromGifRegistry( - TreasuryModule, "Treasury") + self.treasury = self.contractFromGifRegistry(TreasuryModule, "Treasury") self.instanceService = self.contractFromGifRegistry( - InstanceService, "InstanceService") + InstanceService, "InstanceService" + ) self.oracleService = self.contractFromGifRegistry( - OracleService, "OracleService") + OracleService, "OracleService" + ) self.riskpoolService = self.contractFromGifRegistry( - RiskpoolService, "RiskpoolService") + RiskpoolService, "RiskpoolService" + ) self.productService = self.contractFromGifRegistry( - ProductService, "ProductService") + ProductService, "ProductService" + ) self.policyFlow = self.contractFromGifRegistry( - PolicyDefaultFlow, "PolicyDefaultFlow") - self.componentOwnerService = self.contractFromGifRegistry( - ComponentOwnerService) + PolicyDefaultFlow, "PolicyDefaultFlow" + ) + self.componentOwnerService = self.contractFromGifRegistry(ComponentOwnerService) self.instanceOperatorService = self.contractFromGifRegistry( - InstanceOperatorService) + InstanceOperatorService + ) def contractFromGifRegistry(self, contractClass, name=None): if not name: @@ -289,14 +306,12 @@ def getOracleService(self) -> OracleService: def dump_sources(registryAddress=None): - dump_sources_summary_dir = './dump_sources/{}'.format( - network.show_active()) - dump_sources_summary_file = '{}/contracts.txt'.format( - dump_sources_summary_dir) + dump_sources_summary_dir = "./dump_sources/{}".format(network.show_active()) + dump_sources_summary_file = "{}/contracts.txt".format(dump_sources_summary_dir) # create parent dir try: - os.mkdir('./dump_sources') + os.mkdir("./dump_sources") except OSError: pass @@ -313,34 +328,28 @@ def dump_sources(registryAddress=None): contracts = [] contracts.append(dump_single(CoreProxy, "Registry", instance)) - contracts.append(dump_single(RegistryController, - "RegistryController", instance)) + contracts.append(dump_single(RegistryController, "RegistryController", instance)) contracts.append(dump_single(BundleToken, "BundleToken", instance)) contracts.append(dump_single(RiskpoolToken, "RiskpoolToken", instance)) contracts.append(dump_single(CoreProxy, "Access", instance)) - contracts.append(dump_single(AccessController, - "AccessController", instance)) + contracts.append(dump_single(AccessController, "AccessController", instance)) contracts.append(dump_single(CoreProxy, "Component", instance)) - contracts.append(dump_single(ComponentController, - "ComponentController", instance)) + contracts.append(dump_single(ComponentController, "ComponentController", instance)) contracts.append(dump_single(CoreProxy, "Query", instance)) contracts.append(dump_single(QueryModule, "QueryModule", instance)) contracts.append(dump_single(CoreProxy, "License", instance)) - contracts.append(dump_single(LicenseController, - "LicenseController", instance)) + contracts.append(dump_single(LicenseController, "LicenseController", instance)) contracts.append(dump_single(CoreProxy, "Policy", instance)) - contracts.append(dump_single(PolicyController, - "PolicyController", instance)) + contracts.append(dump_single(PolicyController, "PolicyController", instance)) contracts.append(dump_single(CoreProxy, "Bundle", instance)) - contracts.append(dump_single(BundleController, - "BundleController", instance)) + contracts.append(dump_single(BundleController, "BundleController", instance)) contracts.append(dump_single(CoreProxy, "Pool", instance)) contracts.append(dump_single(PoolController, "PoolController", instance)) @@ -348,59 +357,62 @@ def dump_sources(registryAddress=None): contracts.append(dump_single(CoreProxy, "Treasury", instance)) contracts.append(dump_single(TreasuryModule, "TreasuryModule", instance)) - contracts.append(dump_single(PolicyDefaultFlow, - "PolicyDefaultFlow", instance)) + contracts.append(dump_single(PolicyDefaultFlow, "PolicyDefaultFlow", instance)) contracts.append(dump_single(CoreProxy, "InstanceService", instance)) - contracts.append(dump_single( - InstanceService, "InstanceServiceController", instance)) + contracts.append( + dump_single(InstanceService, "InstanceServiceController", instance) + ) contracts.append(dump_single(CoreProxy, "ComponentOwnerService", instance)) - contracts.append(dump_single(ComponentOwnerService, - "ComponentOwnerServiceController", instance)) + contracts.append( + dump_single(ComponentOwnerService, "ComponentOwnerServiceController", instance) + ) contracts.append(dump_single(CoreProxy, "OracleService", instance)) - contracts.append(dump_single( - OracleService, "OracleServiceController", instance)) + contracts.append(dump_single(OracleService, "OracleServiceController", instance)) contracts.append(dump_single(CoreProxy, "RiskpoolService", instance)) - contracts.append(dump_single( - RiskpoolService, "RiskpoolServiceController", instance)) + contracts.append( + dump_single(RiskpoolService, "RiskpoolServiceController", instance) + ) contracts.append(dump_single(ProductService, "ProductService", instance)) - contracts.append(dump_single( - CoreProxy, "InstanceOperatorService", instance)) - contracts.append(dump_single(InstanceOperatorService, - "InstanceOperatorServiceController", instance)) + contracts.append(dump_single(CoreProxy, "InstanceOperatorService", instance)) + contracts.append( + dump_single( + InstanceOperatorService, "InstanceOperatorServiceController", instance + ) + ) - with open(dump_sources_summary_file, 'w') as f: - f.write('\n'.join(contracts)) - f.write('\n') + with open(dump_sources_summary_file, "w") as f: + f.write("\n".join(contracts)) + f.write("\n") - print('\n'.join(contracts)) - print('\nfor contract json files see directory {}'.format( - dump_sources_summary_dir)) + print("\n".join(contracts)) + print("\nfor contract json files see directory {}".format(dump_sources_summary_dir)) def dump_single(contract, registryName, instance=None) -> str: info = contract.get_verification_info() netw = network.show_active() - compiler = info['compiler_version'] - optimizer = info['optimizer_enabled'] - runs = info['optimizer_runs'] - license = info['license_identifier'] - address = 'no_address' - name = info['contract_name'] + compiler = info["compiler_version"] + optimizer = info["optimizer_enabled"] + runs = info["optimizer_runs"] + license = info["license_identifier"] + address = "no_address" + name = info["contract_name"] if instance: nameB32 = s2b32(registryName) address = instance.registry.getContract(nameB32) - dump_sources_contract_file = './dump_sources/{}/{}.json'.format(netw, name) - with open(dump_sources_contract_file, 'w') as f: - f.write(json.dumps(contract.get_verification_info() - ['standard_json_input'])) + dump_sources_contract_file = "./dump_sources/{}/{}.json".format(netw, name) + with open(dump_sources_contract_file, "w") as f: + f.write(json.dumps(contract.get_verification_info()["standard_json_input"])) - return '{} {} {} {} {} {} {}'.format(netw, compiler, optimizer, runs, license, address, name) + return "{} {} {} {} {} {} {}".format( + netw, compiler, optimizer, runs, license, address, name + ) diff --git a/scripts/interactive copy.py b/scripts/interactive copy.py new file mode 100644 index 0000000..9a39b0f --- /dev/null +++ b/scripts/interactive copy.py @@ -0,0 +1,130 @@ +import asyncio +import os + +from brownie import ( + interface, + network, + accounts, + InstanceService, + InstanceOperatorService, + ComponentOwnerService, + AyiiProduct, + AyiiOracle, + AyiiRiskpool, + ComponentController, + UsdcAccounting, +) + +from scripts.onepassword import signIn, getItem, getSecret, getLastItem +from scripts.deploy_ayii import ( + confirm, + stakeholders_accounts_ganache, + check_funds, + amend_funds, + deploy, + from_registry, + from_component, + deploy_product_riskpool, + verify_deploy, +) +from scripts.util import s2b, b2s, h2sLeft, contract_from_address, decodeEnum + +################################################################################# +# +# Step 6 - avax-test end-to-end testing +# +# +# Step 6.1 - setup stakeholders + amounts +# +""" instanceOperator = a['instanceOperator'] +investor = a['investor'] +funding = 1000 * 10 ** usdc.decimals() # 1000$ + +insurer = a['insurer'] +customer = a['customer1'] +""" +# +# Step 6. 2 - create risk (see https://lccc.etherisc.com/risks for risk data) +# +""" +projectId = s2b('5413') +uaiId = s2b('125') +cropId = s2b('greengrams') + +mul = product.getPercentageMultiplier() +(trigger, exit, tsi, aph) = (mul * 0.7, mul * 0, mul * 0.7, mul * 0.17) +tx = product.createRisk(projectId, uaiId, cropId, trigger, + exit, tsi, aph, {'from': insurer}) +riskId = dict(tx.events['LogAyiiRiskDataCreated'])['riskId'] +""" +# +# Step 6.3 - fund riskpool (bundle) +# +""" +usdc.transfer(investor, funding, {'from': instanceOperator}) +usdc.approve(instanceService.getTreasuryAddress(), funding, {'from': investor}) + +bundleId = riskpool.getActiveBundleId(0) +riskpool.fundBundle(bundleId, funding, {'from': investor}) +""" +# +# Step 6.4 - create policy +# +""" +usdc.transfer(customer, premium, {'from': instanceOperator}) +usdc.approve(instanceService.getTreasuryAddress(), premium, {'from': customer}) + +tx = product.applyForPolicy( + customer, premium, sumInsured, riskId, {'from': insurer}) +policyId = dict(tx.events['LogAyiiPolicyCreated'])['policyId'] +""" +# +# Step 6.5 - trigger oracle request +# +""" +tx = product.triggerOracle(policyId, {'from': insurer}) +requestId = dict(tx.events['LogAyiiRiskDataRequested'])['requestId'] +""" +# +# Step 6.6 - check risk (aaay value according to expectations, responseAt > 0) +# +""" +product.getRisk(riskId).dict() + +""" +# Step 6.6 - check policies linked to risk +# + +product.policies(riskId) +product.getPolicyId(riskId, 0) +instanceService.getPolicy(product.getPolicyId(riskId, 0)).dict() + +# +# Step 6.7 - check riskpool funds and allowance +# + +rpw = instanceService.getRiskpoolWallet(riskpool.getId()) +usdc.balanceOf(rpw) / 10 ** usdc.decimals() +usdc.allowance(rpw, instanceService.getTreasuryAddress()) / 10 ** usdc.decimals() + +# +# Step 6.8 - set/increase allowance for treasury if necessary +# + +usdc.approve(instanceService.getTreasuryAddress(), sumInsured, {"from": rpw}) + +# +# Step 6.9 - process policies for risk (bach size = 1) +# + + +product.processPoliciesForRisk(riskId, 1, {"from": insurer}) + +# +# Step 6.10 - check payout +# + +policyId = dict(history[-1].events["LogAyiiPolicyProcessed"])["policyId"] +instanceService.getApplication(policyId).dict() +instanceService.getPolicy(policyId).dict() +usdc.balanceOf(customer) / 10 ** usdc.decimals() diff --git a/scripts/interactive.py b/scripts/interactive.py index 481ae92..bbea468 100644 --- a/scripts/interactive.py +++ b/scripts/interactive.py @@ -4,14 +4,11 @@ AyiiRiskpool, GenericOracle, GenericRiskpool, - GenericProduct + GenericProduct, ) -from scripts.deploy_ayii import (verify_deploy, printBundle) -from scripts.util import ( - contract_from_address, - decodeEnum, b2s, s2b, utcStr, fromWei -) +from scripts.deploy_ayii import verify_deploy, printBundle +from scripts.util import contract_from_address, decodeEnum, b2s, s2b, utcStr, fromWei from scripts.context import Context from scripts.prompt import prompt @@ -23,7 +20,7 @@ def listComponents(): components = context.getComponents() for componentIndex, componentData in enumerate(components): info = ( - f'{componentIndex:>3} : ' + f"{componentIndex:>3} : " f'{componentData["typeStr"].ljust(12)}\n' f' Address: {componentData["address"]}\n' f' Status: {componentData["stateStr"].ljust(12)}' @@ -32,7 +29,7 @@ def listComponents(): pass elif componentData["type"] == 1: # Product info += ( - '\n' + "\n" f' Risks: {componentData["additionalData"]["risks"]}\n' f' RiskpoolId: {componentData["additionalData"]["riskpoolId"]}\n' f' Token: {componentData["additionalData"]["erc20Token"]}\n' @@ -41,7 +38,7 @@ def listComponents(): ) elif componentData["type"] == 2: # Riskpool info += ( - '\n' + "\n" f' Token: {componentData["additionalData"]["erc20Token"]}\n' f' Capital(wei): {componentData["additionalData"]["capital"]}\n' f' Bundles: {componentData["additionalData"]["bundles"]}' @@ -54,9 +51,10 @@ def selectComponent(type): components = context.getComponents(type) ct = [context.oracle.address, context.product.address, context.riskpool.address] options = [ - f"{i:>3}: {component['address']} {'< in context' if component['address'] in ct else ''}" for i, component in enumerate(components, 1) + f"{i:>3}: {component['address']} {'< in context' if component['address'] in ct else ''}" + for i, component in enumerate(components, 1) ] - options.append('Cancel') + options.append("Cancel") print(f'Select {decodeEnum("ComponentType", type)}:') selection = options.index(prompt.menu(options)) if selection == len(options) - 1: @@ -65,11 +63,9 @@ def selectComponent(type): def selectComponentType(): - options = [ - f"{i}: {decodeEnum('ComponentType', i)}" for i in range(3) - ] - options.append('Cancel') - print('Select Component Type:') + options = [f"{i}: {decodeEnum('ComponentType', i)}" for i in range(3)] + options.append("Cancel") + print("Select Component Type:") selection = options.index(prompt.menu(options)) if selection == len(options) - 1: return None @@ -83,20 +79,19 @@ def selectTypeAndComponent(): component = selectComponent(type) if component is None: return - print(( - f'Selected: {component["typeStr"]} ' - f'Address: {component["address"]} ' - f'State: {component["stateStr"]}' - )) - if component['type'] == 0: # Oracle - context.oracle = contract_from_address( - GenericOracle, component['address']) - elif component['type'] == 1: # Product - context.product = contract_from_address( - AyiiProduct, component['address']) - elif component['type'] == 2: # Riskpool - context.riskpool = contract_from_address( - AyiiRiskpool, component['address']) + print( + ( + f'Selected: {component["typeStr"]} ' + f'Address: {component["address"]} ' + f'State: {component["stateStr"]}' + ) + ) + if component["type"] == 0: # Oracle + context.oracle = contract_from_address(GenericOracle, component["address"]) + elif component["type"] == 1: # Product + context.product = contract_from_address(AyiiProduct, component["address"]) + elif component["type"] == 2: # Riskpool + context.riskpool = contract_from_address(AyiiRiskpool, component["address"]) def verifyDeploy(): @@ -106,7 +101,7 @@ def verifyDeploy(): context.registry, context.riskpoolId.getId(), context.oracle.getId(), - context.product.getId() + context.product.getId(), ) @@ -117,10 +112,11 @@ def setFeeCapital(): context.riskpool.getId(), context.feeCapitalFix, context.feeCapitalPercentage, - b'' + b"", ) instanceOperatorService.setCapitalFees( - feeRiskpool, {'from': context.instanceOperator}) + feeRiskpool, {"from": context.instanceOperator} + ) def setFeePremium(): @@ -130,11 +126,12 @@ def setFeePremium(): context.product.getId(), context.feePremiumFix, context.feePremiumPercentage, - b'' + b"", ) instanceOperatorService.setPremiumFees( - feeProduct, {'from': context.instanceOperator}) + feeProduct, {"from": context.instanceOperator} + ) def getFeeSpecification(): @@ -150,10 +147,10 @@ def getComponent(componentId): def createRisk(): - insurer = a['insurer'] - projectId = prompt.enterString("Enter the project ID:", r'^\d{1,10}$') - uaiId = prompt.enterString("Enter the UAI ID:", r'^\d{1,10}$') - cropId = prompt.enterString("Enter the crop ID:", r'^[a-zA-Z]+$') + insurer = a["insurer"] + projectId = prompt.enterString("Enter the project ID:", r"^\d{1,10}$") + uaiId = prompt.enterString("Enter the UAI ID:", r"^\d{1,10}$") + cropId = prompt.enterString("Enter the crop ID:", r"^[a-zA-Z]+$") trigger = prompt.enterFloat("Enter the trigger:", 0, 1) exit = prompt.enterFloat("Enter the exit:", 0, 1) tsi = prompt.enterFloat("Enter the TSI:", 0, 1) @@ -172,11 +169,16 @@ def createRisk(): mul = context.product.getPercentageMultiplier() (trigger, exit, tsi, aph) = (mul * trigger, mul * exit, mul * tsi, mul * aph) tx = context.product.createRisk( - s2b(projectId), s2b(uaiId), s2b(cropId), - trigger, exit, tsi, aph, - {'from': insurer} + s2b(projectId), + s2b(uaiId), + s2b(cropId), + trigger, + exit, + tsi, + aph, + {"from": insurer}, ) - riskId = dict(tx.events['LogAyiiRiskDataCreated'])['riskId'] + riskId = dict(tx.events["LogAyiiRiskDataCreated"])["riskId"] print(f"Risk created with ID: {riskId}") @@ -188,17 +190,19 @@ def getRisks(): riskId = product.getRiskId(riskIndex) risk = product.getRisk(riskId) mul = product.getPercentageMultiplier() - result.append({ - 'riskId': riskId, - 'risk': risk, - 'mul': mul, - 'trigger': risk["trigger"]/mul, - 'exit': risk["exit"]/mul, - 'tsi': risk["tsi"]/mul, - 'aph': risk["aph"]/mul, - 'aaay': risk["aaay"]/mul, - 'payoutPercentage': risk["payoutPercentage"]/mul - }) + result.append( + { + "riskId": riskId, + "risk": risk, + "mul": mul, + "trigger": risk["trigger"] / mul, + "exit": risk["exit"] / mul, + "tsi": risk["tsi"] / mul, + "aph": risk["aph"] / mul, + "aaay": risk["aaay"] / mul, + "payoutPercentage": risk["payoutPercentage"] / mul, + } + ) return result @@ -207,31 +211,33 @@ def listRisks(): riskCount = len(riskData) for riskIndex in range(0, riskCount): data = riskData[riskIndex] - risk = data['risk'] - print(( - f'Index: {riskIndex}\n' - f'RiskId: {data["riskId"]}\n' - f'ProjectId: {b2s(risk["projectId"]).ljust(10)}\n' - f'UAI: {b2s(risk["uaiId"]).ljust(10)}\n' - f'CropId: {b2s(risk["cropId"]).ljust(10)}\n' - f'Trigger: {data["trigger"]:.5f}\n' - f'Exit: {data["exit"]:.5f}\n' - f'TSI: {data["tsi"]:.5f}\n' - f'APH: {data["aph"]:.5f}\n' - f'AAAY: {data["aaay"]:.5f}\n' - f'Payout Pct: {data["payoutPercentage"]:.5f}\n' - f'Triggered: {risk["requestTriggered"]}\n' - f'RequestId: {risk["requestId"]}\n' - f'Response at: {utcStr(risk["responseAt"])}\n' - f'Created: {utcStr(risk["createdAt"])}\n' - f'Updated: {utcStr(risk["updatedAt"])}\n' - )) + risk = data["risk"] + print( + ( + f"Index: {riskIndex}\n" + f'RiskId: {data["riskId"]}\n' + f'ProjectId: {b2s(risk["projectId"]).ljust(10)}\n' + f'UAI: {b2s(risk["uaiId"]).ljust(10)}\n' + f'CropId: {b2s(risk["cropId"]).ljust(10)}\n' + f'Trigger: {data["trigger"]:.5f}\n' + f'Exit: {data["exit"]:.5f}\n' + f'TSI: {data["tsi"]:.5f}\n' + f'APH: {data["aph"]:.5f}\n' + f'AAAY: {data["aaay"]:.5f}\n' + f'Payout Pct: {data["payoutPercentage"]:.5f}\n' + f'Triggered: {risk["requestTriggered"]}\n' + f'RequestId: {risk["requestId"]}\n' + f'Response at: {utcStr(risk["responseAt"])}\n' + f'Created: {utcStr(risk["createdAt"])}\n' + f'Updated: {utcStr(risk["updatedAt"])}\n' + ) + ) def listRiskShort(): riskData = getRisks() return [ - f'{i:>3}: ' + f"{i:>3}: " f'{b2s(risk["risk"]["projectId"]).ljust(10)} ' f'{b2s(risk["risk"]["uaiId"]).ljust(10)} ' f'{b2s(risk["risk"]["cropId"]).ljust(10)} ' @@ -253,24 +259,18 @@ def getPolicies(): for policyIndex in range(0, policyCount): policyId = product.getPolicyId(riskId, policyIndex) policy = context.instanceService.getPolicy(policyId) - result.append({ - 'policyId': policyId, - 'policy': policy, - 'riskId': riskId - }) + result.append({"policyId": policyId, "policy": policy, "riskId": riskId}) return result def selectPolicy(): policies = getPolicies() if len(policies) == 0: - print('No policies available') + print("No policies available") return None - options = [ - f"{i:>3}: {p['policyId']}" for i, p in enumerate(policies) - ] - options.append('Cancel') - print('Select Policy:') + options = [f"{i:>3}: {p['policyId']}" for i, p in enumerate(policies)] + options.append("Cancel") + print("Select Policy:") selection = options.index(prompt.menu(options)) if selection == len(options) - 1: return None @@ -280,16 +280,14 @@ def selectPolicy(): def selectBundle(): bundles = context.riskpool.bundles() if bundles == 0: - print('No bundles available') + print("No bundles available") return None elif bundles == 1: - print('Only one bundle available - selecting it') + print("Only one bundle available - selecting it") return 0 - options = [ - f"{i:>3}: {context.riskpool.getBundle(i)}" for i in range(bundles) - ] - options.append('Cancel') - print('Select Bundle:') + options = [f"{i:>3}: {context.riskpool.getBundle(i)}" for i in range(bundles)] + options.append("Cancel") + print("Select Bundle:") selection = options.index(prompt.menu(options)) if selection == len(options) - 1: return None @@ -299,14 +297,14 @@ def selectBundle(): def selectRisk(): risks = context.product.risks() if risks == 0: - print('No risks available') + print("No risks available") return None elif risks == 1: - print('Only one risk available - selecting it') + print("Only one risk available - selecting it") return 0 options = listRiskShort() - options.append('Cancel') - print('Select Risk:') + options.append("Cancel") + print("Select Risk:") selection = options.index(prompt.menu(options)) if selection == len(options) - 1: return None @@ -320,39 +318,36 @@ def fundBundle(): printBundle(context.riskpool, bundleId) bundle = context.riskpool.getBundle(bundleId) - if bundle['state'] != 0: - print('Bundle is not active - aborting') + if bundle["state"] != 0: + print("Bundle is not active - aborting") return funding = prompt.enterCurrency( - "Enter the funding amount:", - context.usdc.decimals(), - lowerBound=0 + "Enter the funding amount:", context.usdc.decimals(), lowerBound=0 ) if funding == 0: return - if not prompt.confirm(f"Fund bundle with {fromWei(funding, context.usdc.decimals())} USDC"): + if not prompt.confirm( + f"Fund bundle with {fromWei(funding, context.usdc.decimals())} USDC" + ): return - investor = context.accounts['investor'] + investor = context.accounts["investor"] if context.usdc.balanceOf(investor) < funding: - print('Insufficient funds at investor - supplying more') - context.usdc.transfer( - investor, - funding, - {'from': context.instanceOperator} - ) - if context.usdc.allowance(investor, context.instanceService.getTreasuryAddress()) < funding: - print('Approving USDC transfer from investor to treasury') + print("Insufficient funds at investor - supplying more") + context.usdc.transfer(investor, funding, {"from": context.instanceOperator}) + if ( + context.usdc.allowance(investor, context.instanceService.getTreasuryAddress()) + < funding + ): + print("Approving USDC transfer from investor to treasury") context.usdc.approve( - context.instanceService.getTreasuryAddress(), - funding, - {'from': investor} + context.instanceService.getTreasuryAddress(), funding, {"from": investor} ) - context.riskpool.fundBundle(bundle['id'], funding, {'from': investor}) - print('Bundle successfully funded:') + context.riskpool.fundBundle(bundle["id"], funding, {"from": investor}) + print("Bundle successfully funded:") printBundle(context.riskpool, bundleId) @@ -363,55 +358,49 @@ def createPolicy(): return premium = prompt.enterCurrency( - "Enter the premium amount:", - context.usdc.decimals(), - lowerBound=0 + "Enter the premium amount:", context.usdc.decimals(), lowerBound=0 ) if premium == 0: return sumInsured = prompt.enterCurrency( - "Enter the sum insured amount:", - context.usdc.decimals(), - lowerBound=0 + "Enter the sum insured amount:", context.usdc.decimals(), lowerBound=0 ) if sumInsured == 0: return - customer = context.accounts['customer1'] - insurer = context.accounts['insurer'] + customer = context.accounts["customer1"] + insurer = context.accounts["insurer"] if context.usdc.balanceOf(customer) < premium: - print('Insufficient funds at customer - supplying more') - context.usdc.transfer( - customer, - premium, - {'from': context.instanceOperator} - ) - if context.usdc.allowance(customer, context.instanceService.getTreasuryAddress()) < premium: - print('Approving USDC transfer from customer to treasury') + print("Insufficient funds at customer - supplying more") + context.usdc.transfer(customer, premium, {"from": context.instanceOperator}) + if ( + context.usdc.allowance(customer, context.instanceService.getTreasuryAddress()) + < premium + ): + print("Approving USDC transfer from customer to treasury") context.usdc.approve( - context.instanceService.getTreasuryAddress(), - premium, - {'from': customer} + context.instanceService.getTreasuryAddress(), premium, {"from": customer} ) - print(f'Creating policy for riskId: {riskId}') + print(f"Creating policy for riskId: {riskId}") tx = context.product.applyForPolicy( - customer, premium, sumInsured, riskId, {'from': insurer}) - policyId = dict(tx.events['LogAyiiPolicyCreated'])['policyId'] - print(f'Policy created with ID: {policyId}') + customer, premium, sumInsured, riskId, {"from": insurer} + ) + policyId = dict(tx.events["LogAyiiPolicyCreated"])["policyId"] + print(f"Policy created with ID: {policyId}") def triggerOracle(): policy = selectPolicy() if policy is None: return - policyId = policy['policyId'] - insurer = context.accounts['insurer'] - if not prompt.confirm(f'Trigger oracle request for policyId: {policyId}'): + policyId = policy["policyId"] + insurer = context.accounts["insurer"] + if not prompt.confirm(f"Trigger oracle request for policyId: {policyId}"): return - print(f'Triggering oracle request for policyId: {policyId}') - tx = context.product.triggerOracle(policyId, {'from': insurer}) - requestId = dict(tx.events['LogAyiiRiskDataRequested'])['requestId'] - print(f'Oracle request triggered with requestId: {requestId}') + print(f"Triggering oracle request for policyId: {policyId}") + tx = context.product.triggerOracle(policyId, {"from": insurer}) + requestId = dict(tx.events["LogAyiiRiskDataRequested"])["requestId"] + print(f"Oracle request triggered with requestId: {requestId}") diff --git a/scripts/menu.py b/scripts/menu.py index 33a5f88..39c15e9 100644 --- a/scripts/menu.py +++ b/scripts/menu.py @@ -7,7 +7,7 @@ selectTypeAndComponent, fundBundle, createPolicy, - triggerOracle + triggerOracle, ) from scripts.deploy_ayii import ( @@ -15,7 +15,7 @@ amend_funds, deploy, deploy_product_with_oracle_riskpool, - verify_deploy + verify_deploy, ) @@ -23,51 +23,45 @@ def menu(): run = True while run: options = { - 'Check Funds': { - 'function': check_funds, - 'args': [context.accounts, context.usdc] + "Check Funds": { + "function": check_funds, + "args": [context.accounts, context.usdc], }, - 'Amend Funds': { - 'function': amend_funds, - 'args': [context.accounts] - }, - 'Deploy': { - 'function': deploy, - 'args': [ - context.accounts, - context.usdc - ]}, - 'Deploy Product, Oracle and Riskpool': { - 'function': deploy_product_with_oracle_riskpool, - 'args': [ + "Amend Funds": {"function": amend_funds, "args": [context.accounts]}, + "Deploy": {"function": deploy, "args": [context.accounts, context.usdc]}, + "Deploy Product, Oracle and Riskpool": { + "function": deploy_product_with_oracle_riskpool, + "args": [ context.registry, context.accounts, context.usdc, - context.fullCollateralizationLevel - ]}, - 'Verify Deploy': { - 'function': verify_deploy, - 'args': [ + context.fullCollateralizationLevel, + ], + }, + "Verify Deploy": { + "function": verify_deploy, + "args": [ context.accounts, context.usdc, context.registry, context.riskpoolId, context.oracleId, - context.productId - ] + context.productId, + ], }, - 'Context': context.printContext, - 'List Components': listComponents, - 'Create Risk': createRisk, - 'List Risks': listRisks, - 'Select Type and Component': selectTypeAndComponent, - 'Fund Bundle': fundBundle, - 'Create Policy': createPolicy, - 'Trigger Oracle': triggerOracle, - 'Exit': None + "Context": context.printContext, + "Accounts": context.printAccounts, + "List Components": listComponents, + "Create Risk": createRisk, + "List Risks": listRisks, + "Select Type and Component": selectTypeAndComponent, + "Fund Bundle": fundBundle, + "Create Policy": createPolicy, + "Trigger Oracle": triggerOracle, + "Exit": None, } selection = prompt.dictMenu(options) - run = selection != 'Exit' + run = selection != "Exit" menu() diff --git a/scripts/onepassword.py b/scripts/onepassword.py index d41f4f8..ebea5b3 100644 --- a/scripts/onepassword.py +++ b/scripts/onepassword.py @@ -1,6 +1,12 @@ import os from onepassword.client import Client -from onepassword import ItemCreateParams, ItemCategory, ItemSection, ItemField, ItemFieldType +from onepassword import ( + ItemCreateParams, + ItemCategory, + ItemSection, + ItemField, + ItemFieldType, +) client = None lastItem = None @@ -12,7 +18,9 @@ async def signIn(): token = os.getenv("OP_SERVICE_ACCOUNT_TOKEN") # print(token) # Connects to 1Password. Fill in your own integration name and version. - client = await Client.authenticate(auth=token, integration_name="Test", integration_version="v1.0.0") + client = await Client.authenticate( + auth=token, integration_name="Test", integration_version="v1.0.0" + ) async def listVaults(): diff --git a/scripts/product.py b/scripts/product.py index 25137e3..fa4e820 100644 --- a/scripts/product.py +++ b/scripts/product.py @@ -7,7 +7,7 @@ from brownie import ( Wei, - Contract, + Contract, PolicyController, OracleService, ComponentOwnerService, @@ -41,15 +41,16 @@ class GifTestRiskpool(object): - def __init__(self, - instance: GifInstance, - riskpoolKeeper: Account, + def __init__( + self, + instance: GifInstance, + riskpoolKeeper: Account, erc20Token: Account, - riskpoolWallet: Account, - collateralization:int, - name=RISKPOOL_NAME, + riskpoolWallet: Account, + collateralization: int, + name=RISKPOOL_NAME, publishSource=False, - setRiskpoolWallet=True + setRiskpoolWallet=True, ): instanceService = instance.getInstanceService() operatorService = instance.getInstanceOperatorService() @@ -59,68 +60,65 @@ def __init__(self, # 1) add role to keeper keeperRole = instanceService.getRiskpoolKeeperRole() operatorService.grantRole( - keeperRole, - riskpoolKeeper, - {'from': instance.getOwner()}) + keeperRole, riskpoolKeeper, {"from": instance.getOwner()} + ) # 2) keeper deploys riskpool if not setRiskpoolWallet: - name += '_NO_WALLET' - + name += "_NO_WALLET" + self.riskpool = TestRiskpool.deploy( s2b32(name), collateralization, erc20Token, riskpoolWallet, instance.getRegistry(), - {'from': riskpoolKeeper}, - publish_source=publishSource) + {"from": riskpoolKeeper}, + publish_source=publishSource, + ) # 3) riskpool keeperproposes oracle to instance - componentOwnerService.propose( - self.riskpool, - {'from': riskpoolKeeper}) + componentOwnerService.propose(self.riskpool, {"from": riskpoolKeeper}) # 4) instance operator approves riskpool - operatorService.approve( - self.riskpool.getId(), - {'from': instance.getOwner()}) + operatorService.approve(self.riskpool.getId(), {"from": instance.getOwner()}) # 5) instance operator assigns riskpool wallet if setRiskpoolWallet: operatorService.setRiskpoolWallet( - self.riskpool.getId(), - riskpoolWallet, - {'from': instance.getOwner()}) + self.riskpool.getId(), riskpoolWallet, {"from": instance.getOwner()} + ) # 6) setup capital fees fixedFee = 42 - fractionalFee = instanceService.getFeeFractionFullUnit() / 20 # corresponds to 5% + fractionalFee = ( + instanceService.getFeeFractionFullUnit() / 20 + ) # corresponds to 5% feeSpec = operatorService.createFeeSpecification( self.riskpool.getId(), fixedFee, fractionalFee, - b'', - {'from': instance.getOwner()}) + b"", + {"from": instance.getOwner()}, + ) + + operatorService.setCapitalFees(feeSpec, {"from": instance.getOwner()}) - operatorService.setCapitalFees( - feeSpec, - {'from': instance.getOwner()}) - def getId(self) -> int: return self.riskpool.getId() - + def getContract(self) -> TestRiskpool: return self.riskpool class GifTestOracle(object): - def __init__(self, - instance: GifInstance, - oracleOwner: Account, - name=ORACLE_NAME, - publishSource=False + def __init__( + self, + instance: GifInstance, + oracleOwner: Account, + name=ORACLE_NAME, + publishSource=False, ): instanceService = instance.getInstanceService() operatorService = instance.getInstanceOperatorService() @@ -130,45 +128,42 @@ def __init__(self, # 1) add oracle provider role to owner providerRole = instanceService.getOracleProviderRole() operatorService.grantRole( - providerRole, - oracleOwner, - {'from': instance.getOwner()}) + providerRole, oracleOwner, {"from": instance.getOwner()} + ) # 2) oracle provider creates oracle self.oracle = TestOracle.deploy( s2b32(name), instance.getRegistry(), - {'from': oracleOwner}, - publish_source=publishSource) + {"from": oracleOwner}, + publish_source=publishSource, + ) # 3) oracle owner proposes oracle to instance - componentOwnerService.propose( - self.oracle, - {'from': oracleOwner}) + componentOwnerService.propose(self.oracle, {"from": oracleOwner}) # 4) instance operator approves oracle - operatorService.approve( - self.oracle.getId(), - {'from': instance.getOwner()}) - + operatorService.approve(self.oracle.getId(), {"from": instance.getOwner()}) + def getId(self) -> int: return self.oracle.getId() - + def getContract(self) -> TestOracle: return self.oracle class GifTestProduct(object): - def __init__(self, - instance: GifInstance, - token: Account, - capitalOwner: Account, - productOwner: Account, - oracle: GifTestOracle, - riskpool: GifTestRiskpool, - name=PRODUCT_NAME, - publishSource=False + def __init__( + self, + instance: GifInstance, + token: Account, + capitalOwner: Account, + productOwner: Account, + oracle: GifTestOracle, + riskpool: GifTestRiskpool, + name=PRODUCT_NAME, + publishSource=False, ): self.policy = instance.getPolicy() self.oracle = oracle @@ -182,9 +177,8 @@ def __init__(self, # 1) add oracle provider role to owner ownerRole = instanceService.getProductOwnerRole() operatorService.grantRole( - ownerRole, - productOwner, - {'from': instance.getOwner()}) + ownerRole, productOwner, {"from": instance.getOwner()} + ) # 2) product owner creates product self.product = TestProduct.deploy( @@ -194,40 +188,36 @@ def __init__(self, oracle.getId(), riskpool.getId(), instance.getRegistry(), - {'from': productOwner}, - publish_source=publishSource) + {"from": productOwner}, + publish_source=publishSource, + ) # 3) product owner proposes product to instance - componentOwnerService.propose( - self.product, - {'from': productOwner}) + componentOwnerService.propose(self.product, {"from": productOwner}) # 4) instance operator approves product - operatorService.approve( - self.product.getId(), - {'from': instance.getOwner()}) + operatorService.approve(self.product.getId(), {"from": instance.getOwner()}) # 5) instance owner sets token in treasury operatorService.setProductToken( - self.product.getId(), - token, - {'from': instance.getOwner()}) + self.product.getId(), token, {"from": instance.getOwner()} + ) # 5) instance owner creates and sets product fee spec fixedFee = 3 - fractionalFee = instanceService.getFeeFractionFullUnit() / 10 # corresponds to 10% + fractionalFee = ( + instanceService.getFeeFractionFullUnit() / 10 + ) # corresponds to 10% feeSpec = operatorService.createFeeSpecification( self.product.getId(), fixedFee, fractionalFee, - b'', - {'from': instance.getOwner()}) + b"", + {"from": instance.getOwner()}, + ) - operatorService.setPremiumFees( - feeSpec, - {'from': instance.getOwner()}) + operatorService.setPremiumFees(feeSpec, {"from": instance.getOwner()}) - def getId(self) -> int: return self.product.getId() @@ -236,7 +226,7 @@ def getOracle(self) -> GifTestOracle: def getRiskpool(self) -> GifTestRiskpool: return self.riskpool - + def getContract(self) -> TestProduct: return self.product diff --git a/scripts/prompt.py b/scripts/prompt.py index 3410ad5..883a0d9 100644 --- a/scripts/prompt.py +++ b/scripts/prompt.py @@ -13,17 +13,15 @@ def executeFunctionOrObject(self, param): if callable(param): # If param is a function, call it without parameters return param() - elif isinstance(param, dict) and 'function' in param and 'args' in param: + elif isinstance(param, dict) and "function" in param and "args" in param: # If param is a dictionary with 'function' and 'args', execute the function with arguments - func = param['function'] - args = param['args'] + func = param["function"] + args = param["args"] if not callable(func): - raise ValueError( - "'function' in the dictionary must be callable") + raise ValueError("'function' in the dictionary must be callable") if not isinstance(args, (list, tuple)): - raise ValueError( - "'args' in the dictionary must be a list or tuple") + raise ValueError("'args' in the dictionary must be a list or tuple") return func(*args) # else: @@ -44,8 +42,10 @@ def dictMenu(self, dictOptions): self.executeFunctionOrObject(selected_function) # Invoke the method return selection - def qText(self, prompt, validate): - response = questionary.text(prompt.ljust(30), validate=validate).ask() + def qText(self, prompt, validate, default=""): + response = questionary.text( + prompt.ljust(30), validate=validate, default=default + ).ask() return response def enterCurrency(self, prompt, scale=1, lowerBound=0.1, upperBound=100000): @@ -57,12 +57,14 @@ def validateCurrency(value): if lowerBound <= number <= upperBound: return True else: - return f"Please enter a number between {lowerBound} and {upperBound}." + return ( + f"Please enter a number between {lowerBound} and {upperBound}." + ) except ValueError: return "Invalid input. Please enter a valid number." response = self.qText(prompt, validate=validateCurrency) - return int(round(float(response) * 10 ** scale)) + return int(round(float(response) * 10**scale)) def enterNumber(self, prompt, lowerBound, upperBound): def validateNumber(value): @@ -73,7 +75,9 @@ def validateNumber(value): if lowerBound <= number <= upperBound: return True else: - return f"Please enter a number between {lowerBound} and {upperBound}." + return ( + f"Please enter a number between {lowerBound} and {upperBound}." + ) except ValueError: return "Invalid input. Please enter a valid number." @@ -89,20 +93,23 @@ def validateFloat(value): if lowerBound <= number <= upperBound: return True else: - return f"Please enter a number between {lowerBound} and {upperBound}." + return ( + f"Please enter a number between {lowerBound} and {upperBound}." + ) except ValueError: return "Invalid input. Please enter a valid number." response = self.qText(prompt, validate=validateFloat) return float(response) - def enterString(self, prompt, regex): + def enterString(self, prompt, regex, default=""): def validateString(value): r = re.compile(regex) if r.match(value): return True return "Invalid input. Please enter a valid string." - response = self.qText(prompt, validate=validateString) + + response = self.qText(prompt, validate=validateString, default=default) return response def setInteractive(self, interactive): @@ -110,12 +117,14 @@ def setInteractive(self, interactive): def confirm(self, text): if self.INTERACTIVE: - return click.confirm(( - f"This action will alter the state of " - f"the blockchain <{network.show_active()}>. \n" - f"The action is '{text}'. \n" - f"Do you want to proceed?" - )) + return click.confirm( + ( + f"This action will alter the state of " + f"the blockchain <{network.show_active()}>. \n" + f"The action is '{text}'. \n" + f"Do you want to proceed?" + ) + ) else: return True diff --git a/scripts/setup.py b/scripts/setup.py index 18cfb1a..3d7689d 100644 --- a/scripts/setup.py +++ b/scripts/setup.py @@ -8,74 +8,65 @@ from scripts.instance import GifInstance from scripts.ayii_product import GifAyiiProductComplete + def fund_riskpool( - instance: GifInstance, + instance: GifInstance, owner: Account, capitalOwner: Account, riskpool, bundleOwner: Account, coin, amount: int, - createBundle: bool = True + createBundle: bool = True, ): # transfer funds to riskpool keeper and create allowance safetyFactor = 2 - coin.transfer(bundleOwner, safetyFactor * amount, {'from': owner}) - coin.approve(instance.getTreasury(), safetyFactor * amount, {'from': bundleOwner}) + coin.transfer(bundleOwner, safetyFactor * amount, {"from": owner}) + coin.approve(instance.getTreasury(), safetyFactor * amount, {"from": bundleOwner}) # create approval for treasury from capital owner to allow for withdrawls - maxUint256 = 2**256-1 - coin.approve(instance.getTreasury(), maxUint256, {'from': capitalOwner}) + maxUint256 = 2**256 - 1 + coin.approve(instance.getTreasury(), maxUint256, {"from": capitalOwner}) applicationFilter = bytes(0) bundleId = None - if (createBundle): - tx = riskpool.createBundle( - applicationFilter, - amount, - {'from': bundleOwner}) + if createBundle: + tx = riskpool.createBundle(applicationFilter, amount, {"from": bundleOwner}) bundleId = tx.return_value - + return bundleId def fund_customer( - instance: GifInstance, - owner: Account, - account: Account, - coin, - amount: int + instance: GifInstance, owner: Account, account: Account, coin, amount: int ): - coin.transfer(account, amount, {'from': owner}) - coin.approve(instance.getTreasury(), amount, {'from': account}) + coin.transfer(account, amount, {"from": owner}) + coin.approve(instance.getTreasury(), amount, {"from": account}) def apply_for_policy( - instance: GifInstance, + instance: GifInstance, owner: Account, - product, + product, customer: Account, coin, premium: int, - sumInsured: int + sumInsured: int, ): # transfer premium funds to customer and create allowance - coin.transfer(customer, premium, {'from': owner}) - coin.approve(instance.getTreasury(), premium, {'from': customer}) + coin.transfer(customer, premium, {"from": owner}) + coin.approve(instance.getTreasury(), premium, {"from": customer}) # create minimal policy application metaData = bytes(0) applicationData = bytes(0) tx = product.applyForPolicy( - premium, - sumInsured, - metaData, - applicationData, - {'from': customer}) - + premium, sumInsured, metaData, applicationData, {"from": customer} + ) + # print(tx.events) # returns policy id diff --git a/scripts/util.py b/scripts/util.py index 5556037..468f3a7 100644 --- a/scripts/util.py +++ b/scripts/util.py @@ -25,27 +25,32 @@ CHAIN_ID_MAINNET = 1 CHAIN_IDS_REQUIRING_CONFIRMATIONS = [ - CHAIN_ID_MUMBAI, CHAIN_ID_FUJI, CHAIN_ID_GOERLI, CHAIN_ID_AVAX, CHAIN_ID_MAINNET] + CHAIN_ID_MUMBAI, + CHAIN_ID_FUJI, + CHAIN_ID_GOERLI, + CHAIN_ID_AVAX, + CHAIN_ID_MAINNET, +] def s2h(text: str) -> str: - return Web3.toHex(text.encode('ascii')) + return Web3.to_hex(text.encode("ascii")) def h2s(hex: str) -> str: - return Web3.toText(hex).split('\x00')[-1] + return Web3.to_text(hex).split("\x00")[-1] def h2sLeft(hex: str) -> str: - return Web3.toText(hex).split('\x00')[0] + return Web3.to_text(hex).split("\x00")[0] def s2b32(text: str): - return '{:0<66}'.format(Web3.to_hex(text.encode('ascii')))[:66] + return "{:0<66}".format(Web3.to_hex(text.encode("ascii")))[:66] def b322s(b32: bytes): - return b32.decode().split('\x00')[0] + return b32.decode().split("\x00")[0] def s2b(text: str): @@ -57,27 +62,24 @@ def b2s(b32: bytes): def keccak256(text: str): - return Web3.solidityKeccak(['string'], [text]).hex() + return Web3.solidity_keccak(["string"], [text]).hex() def get_account(mnemonic: str, account_offset: int) -> Account: - return accounts.from_mnemonic( - mnemonic, - count=1, - offset=account_offset) + return accounts.from_mnemonic(mnemonic, count=1, offset=account_offset) def wait_for_confirmations(tx): if web3.chain_id in CHAIN_IDS_REQUIRING_CONFIRMATIONS: if not is_forked_network(): - print('waiting for confirmations ...') + print("waiting for confirmations ...") tx.wait(2) else: - print('not waiting for confirmations in a forked network...') + print("not waiting for confirmations in a forked network...") def is_forked_network(): - return 'fork' in network.show_active() + return "fork" in network.show_active() # source: https://github.com/brownie-mix/upgrades-mix/blob/main/scripts/helpful_scripts.py @@ -93,131 +95,94 @@ def encode_function_data(*args, initializer=None): [bytes]: Return the encoded bytes. """ if not len(args): - args = b'' + args = b"" if initializer: return initializer.encode_input(*args) - return b'' + return b"" + # generic upgradable gif module deployment -def deployGifModule( - controllerClass, - storageClass, - registry, - owner, - publishSource -): +def deployGifModule(controllerClass, storageClass, registry, owner, publishSource): controller = controllerClass.deploy( - registry.address, - {'from': owner}, - publish_source=publishSource) + registry.address, {"from": owner}, publish_source=publishSource + ) storage = storageClass.deploy( - registry.address, - {'from': owner}, - publish_source=publishSource) + registry.address, {"from": owner}, publish_source=publishSource + ) - controller.assignStorage(storage.address, {'from': owner}) - storage.assignController(controller.address, {'from': owner}) + controller.assignStorage(storage.address, {"from": owner}) + storage.assignController(controller.address, {"from": owner}) - registry.register( - controller.NAME.call(), - controller.address, {'from': owner}) - registry.register(storage.NAME.call(), storage.address, {'from': owner}) + registry.register(controller.NAME.call(), controller.address, {"from": owner}) + registry.register(storage.NAME.call(), storage.address, {"from": owner}) return contractFromAddress(controllerClass, storage.address) + # gif token deployment -def deployGifToken( - tokenName, - tokenClass, - registry, - owner, - publishSource -): - print('token {} deploy'.format(tokenName)) - token = tokenClass.deploy( - {'from': owner}, - publish_source=publishSource) +def deployGifToken(tokenName, tokenClass, registry, owner, publishSource): + print("token {} deploy".format(tokenName)) + token = tokenClass.deploy({"from": owner}, publish_source=publishSource) tokenNameB32 = s2b32(tokenName) - print('token {} register'.format(tokenName)) - registry.register(tokenNameB32, token.address, {'from': owner}) + print("token {} register".format(tokenName)) + registry.register(tokenNameB32, token.address, {"from": owner}) return token # generic open zeppelin upgradable gif module deployment -def deployGifModuleV2( - moduleName, - controllerClass, - registry, - owner, - publishSource -): - print('module {} deploy controller'.format(moduleName)) - controller = controllerClass.deploy( - {'from': owner}, - publish_source=publishSource) +def deployGifModuleV2(moduleName, controllerClass, registry, owner, publishSource): + print("module {} deploy controller".format(moduleName)) + controller = controllerClass.deploy({"from": owner}, publish_source=publishSource) encoded_initializer = encode_function_data( - registry.address, - initializer=controller.initialize) + registry.address, initializer=controller.initialize + ) - print('module {} deploy proxy'.format(moduleName)) + print("module {} deploy proxy".format(moduleName)) proxy = CoreProxy.deploy( controller.address, encoded_initializer, - {'from': owner}, - publish_source=publishSource) + {"from": owner}, + publish_source=publishSource, + ) moduleNameB32 = s2b32(moduleName) - controllerNameB32 = s2b32('{}Controller'.format(moduleName))[:32] + controllerNameB32 = s2b32("{}Controller".format(moduleName))[:32] - print('module {} ({}) register controller'.format( - moduleName, controllerNameB32)) - registry.register(controllerNameB32, controller.address, {'from': owner}) - print('module {} ({}) register proxy'.format(moduleName, moduleNameB32)) - registry.register(moduleNameB32, proxy.address, {'from': owner}) + print("module {} ({}) register controller".format(moduleName, controllerNameB32)) + registry.register(controllerNameB32, controller.address, {"from": owner}) + print("module {} ({}) register proxy".format(moduleName, moduleNameB32)) + registry.register(moduleNameB32, proxy.address, {"from": owner}) return contractFromAddress(controllerClass, proxy.address) # generic upgradable gif service deployment -def deployGifService( - serviceClass, - registry, - owner, - publishSource -): +def deployGifService(serviceClass, registry, owner, publishSource): service = serviceClass.deploy( - registry.address, - {'from': owner}, - publish_source=publishSource) + registry.address, {"from": owner}, publish_source=publishSource + ) - registry.register(service.NAME.call(), service.address, {'from': owner}) + registry.register(service.NAME.call(), service.address, {"from": owner}) return service -def deployGifServiceV2( - serviceName, - serviceClass, - registry, - owner, - publishSource -): +def deployGifServiceV2(serviceName, serviceClass, registry, owner, publishSource): service = serviceClass.deploy( - registry.address, - {'from': owner}, - publish_source=publishSource) + registry.address, {"from": owner}, publish_source=publishSource + ) - registry.register(s2b32(serviceName), service.address, {'from': owner}) + registry.register(s2b32(serviceName), service.address, {"from": owner}) return service @@ -232,54 +197,62 @@ def contract_from_address(contractClass, contractAddress): def save_json(contract_class, file_name=None): vi = contract_class.get_verification_info() - sji = vi['standard_json_input'] + sji = vi["standard_json_input"] if not file_name or len(file_name) == 0: - file_name = './{}.json'.format(contract_class._name) + file_name = "./{}.json".format(contract_class._name) - print('writing standard json input file {}'.format(file_name)) + print("writing standard json input file {}".format(file_name)) with open(file_name, "w") as json_file: json.dump(sji, json_file) enums = { - 'ComponentType': ['Oracle', 'Product', 'Riskpool'], - 'ComponentState': ['Created', 'Proposed', 'Declined', 'Active', 'Paused', 'Suspended', 'Archived'], - 'BundleState': ['Active', 'Locked', 'Closed', 'Burned'], - 'PolicyFlowState': ['Started', 'Active', 'Finished'], - 'ApplicationState': ['Applied', 'Revoked', 'Underwritten', 'Declined'], - 'PolicyState': ['Active', 'Expired', 'Closed'], - 'ClaimState': ['Applied', 'Confirmed', 'Declined', 'Closed'], - 'PayoutState': ['Expected', 'PaidOut'] + "ComponentType": ["Oracle", "Product", "Riskpool"], + "ComponentState": [ + "Created", + "Proposed", + "Declined", + "Active", + "Paused", + "Suspended", + "Archived", + ], + "BundleState": ["Active", "Locked", "Closed", "Burned"], + "PolicyFlowState": ["Started", "Active", "Finished"], + "ApplicationState": ["Applied", "Revoked", "Underwritten", "Declined"], + "PolicyState": ["Active", "Expired", "Closed"], + "ClaimState": ["Applied", "Confirmed", "Declined", "Closed"], + "PayoutState": ["Expected", "PaidOut"], } def decodeEnum(enum_name, value): if enum_name not in enums: - return 'invalid' + return "invalid" return enums[enum_name][value] def getChainName(chainId): try: # Fetch the list of chains from ChainList - response = requests.get('https://chainid.network/chains.json') + response = requests.get("https://chainid.network/chains.json") response.raise_for_status() # Raise an error for bad status codes chains = response.json() # Search for the chain ID in the list for chain in chains: - if chain['chainId'] == chainId: - return chain['name'] + if chain["chainId"] == chainId: + return chain["name"] - return 'ChainId not found' + return "ChainId not found" except requests.RequestException as e: - return f'Error fetching chain name for chainId={chainId} {e}' + return f"Error fetching chain name for chainId={chainId} {e}" def utcStr(timestamp): dt_object = datetime.fromtimestamp(timestamp, tz=timezone.utc) - date_string = dt_object.strftime('%Y-%m-%d %H:%M:%S') + date_string = dt_object.strftime("%Y-%m-%d %H:%M:%S") return date_string From fb1e7e1609b484ba3a3d5edca78f3743c7551bae Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Sat, 1 Feb 2025 17:58:52 +0000 Subject: [PATCH 17/18] finish interactive python scripts --- .solhint.json | 23 +- brownie-config.yaml | 3 +- contracts/examples/AyiiOracle.sol | 70 +++- contracts/generic/ProtectedMulticall.sol | 41 +++ scripts/context.py | 43 ++- scripts/deploy_ayii.py | 23 +- scripts/farmerFunding.py | 446 +++++++++++++++++++++++ scripts/interactive.py | 275 ++++++++++---- scripts/logger.py | 28 ++ scripts/menu.py | 46 ++- scripts/prompt.py | 100 +++-- 11 files changed, 970 insertions(+), 128 deletions(-) create mode 100644 contracts/generic/ProtectedMulticall.sol create mode 100644 scripts/farmerFunding.py create mode 100644 scripts/logger.py diff --git a/.solhint.json b/.solhint.json index 3fdeddd..1523887 100644 --- a/.solhint.json +++ b/.solhint.json @@ -2,8 +2,23 @@ "extends": "solhint:recommended", "plugins": [], "rules": { - "compiler-version": ["error", "0.8.2"], - "func-visibility": ["warn", {"ignoreConstructors": true}], - "reason-string": ["warn",{"maxLength": 64}] + "compiler-version": [ + "error", + "0.8.2" + ], + "func-visibility": [ + "warn", + { + "ignoreConstructors": true + } + ], + "reason-string": [ + "warn", + { + "maxLength": 64 + } + ], + "custom-errors": "off", + "no-global-import": "off" } -} +} \ No newline at end of file diff --git a/brownie-config.yaml b/brownie-config.yaml index e3a53f8..02e14b7 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -23,7 +23,8 @@ compiler: - "@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.7.3" - "@chainlink=smartcontractkit/chainlink@1.6.0" - "@etherisc/gif-interface=etherisc/gif-interface@3b0002a" - + metadata: + useLiteralContent: true # packages below will be added to brownie # you may use 'brownie pm list' after 'brownie compile' # to list the packages installed via the dependency list below diff --git a/contracts/examples/AyiiOracle.sol b/contracts/examples/AyiiOracle.sol index fde79a8..6a87e08 100644 --- a/contracts/examples/AyiiOracle.sol +++ b/contracts/examples/AyiiOracle.sol @@ -1,14 +1,33 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.2; -// NEU: mit Chainlink Funktion +// new: Implementation with Chainlink Functions. import "./strings.sol"; import "./AyiiClf.sol"; - -// import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol"; import "@etherisc/gif-interface/contracts/components/Oracle.sol"; +/// @title AyiiOracle +/// @notice This contract is the implementation of the Oracle contract that uses the Chainlink DON network. +/// @custom:security +/// - We assume the security of the Chainlink DON network and the AyiiClfConsumer contract. +/// - The contract owner should be the `oracleProvider` account. +/// - We assume that the owner of this contract is trustworthy, +/// as they can set the address of the AyiiClfConsumer contract anytime. +/// - The contract is automatically registered in the registry +/// when deployed by inheriting the Oracle contract. +/// - Only the contract owner can set the address of the AyiiClfConsumer contract. +/// - The request function can only be called by the GIF framework, i.e. the Query module. +/// - The fulfillClfRequest function can only be called by the AyiiClfConsumer contract, +/// which in turn can only be called by the Chainlink DON framework. +/// @custom:deployment +/// - The contract is deployed by the `oracleProvider` account. +/// - Deployment sequence: +/// The contract must be deployed before the deployment of the product contract, +/// because the product contract deployment requires the address of the oracle contract. +/// - The address of the AyiiClfConsumer contract must be set after deployment, this +/// can be done anytime after deployment, but before the first request is sent. + contract AyiiOracle is Oracle { using strings for bytes32; @@ -21,6 +40,7 @@ contract AyiiOracle is Oracle { _; } + /// @dev Mapping of Chainlink request IDs to GIF request IDs. mapping(bytes32 /* Chainlink request ID */ => uint256 /* GIF request ID */) public gifRequests; @@ -32,14 +52,34 @@ contract AyiiOracle is Oracle { bytes response ); + event LogAyiiFulfillError( + uint256 requestId, + bytes32 chainlinkRequestId, + bytes err + ); + constructor(bytes32 _name, address _registry) Oracle(_name, _registry) { // NOOP } + /// @dev Here we set the address of the AyiiClfConsumer contract. + /// The AyiiClfConsumer contract is the one that will send the request to the Chainlink DON network. + /// AyiiClfConsumer is maintained in a separate repository because it uses the 0.8.19 version of Solidity + /// which is incompatible with this repo, which uses the 0.8.2 version of Solidity. + /// @param _ayiiClf The address of the AyiiClfConsumer contract. + function setClfGateWay(address _ayiiClf) external onlyOwner { ayiiClf = AyiiClf(_ayiiClf); } + /// @dev This function is called by the GIF framework and sends a request to the Chainlink DON network. + /// @param gifRequestId The ID of the request. + /// @param input The input data for the request. + /// input data is abi encoded (bytes32, bytes32, bytes32) where: + /// - the first bytes32 is the projectId e.g. "1234" + /// - the second bytes32 is the uaiId e.g. "134" + /// - the third bytes32 is the cropId e.g. "Greengrams" + /// The abi encoding is already done in the product contract so we don't need to do it here. function request( uint256 gifRequestId, bytes calldata input @@ -54,6 +94,13 @@ contract AyiiOracle is Oracle { emit LogAyiiRequest(gifRequestId, chainlinkRequestId); } + /// @dev This function is called by the AyiiClfConsumer contract when it receives a response from the Chainlink DON network. + /// @param requestId The chainlink functions request ID of the request. + /// @param response The response from the Chainlink DON network. + /// @param err The error message if the request failed. + /// @notice Either response or err will be filled. + /// In case of an error, the error message will be logged and the function will return so the request can be retried. + /// In this case, the request has to be cancelled in the product with the cancelOracleRequest function. function fulfillClfRequest( bytes32 requestId, bytes memory response, @@ -63,13 +110,24 @@ contract AyiiOracle is Oracle { if (gifRequest == 0) { revert("ERROR:AYII-003:UNEXPECTED_REQUEST_ID"); } + if (err.length > 0) { + // don't revert so we can log the error + emit LogAyiiFulfillError(gifRequest, requestId, err); + return; + } _respond(gifRequest, response); delete gifRequests[requestId]; emit LogAyiiFulfill(gifRequest, requestId, response); } - function cancel(uint256 requestId) external override onlyQuery { - // not implemented - } + /// @dev This function needs to exist due to the interface, but it is not used. + /// not implemented / no implementation needed + /// to cancel a request on our side, use product.cancelOracleRequest(policyId) + /// to cancel a request on the Chainlink side, use functions.chainlink.com + /// Chainlink functions request run in a timeout after 5 minutes, so no need to cancel them; + /// the timeout needs to be confirmed in the Functions web interface. + + // solhint-disable-next-line no-empty-blocks + function cancel(uint256 requestId) external override onlyQuery {} } diff --git a/contracts/generic/ProtectedMulticall.sol b/contracts/generic/ProtectedMulticall.sol new file mode 100644 index 0000000..1f2e8b9 --- /dev/null +++ b/contracts/generic/ProtectedMulticall.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.2; + +/// @title Multicall - Aggregate results from multiple read-only function calls +/// @author Michael Elliot +/// @author Joshua Levine +/// @author Nick Johnson +/// @notice added onlyOwner so we can restrict access to this contract +contract ProtectedMulticall { + address public owner; + + modifier onlyOwner() { + require(msg.sender == owner, "ProtectedMulticall: only owner"); + _; + } + struct Call { + address target; + bytes callData; + } + + constructor() { + owner = msg.sender; + } + function aggregate( + Call[] calldata calls + ) + public + onlyOwner + returns (uint256 blockNumber, bytes[] memory returnData) + { + blockNumber = block.number; + returnData = new bytes[](calls.length); + for (uint256 i = 0; i < calls.length; i++) { + (bool success, bytes memory ret) = calls[i].target.call( + calls[i].callData + ); + require(success); + returnData[i] = ret; + } + } +} diff --git a/scripts/context.py b/scripts/context.py index 2190c79..31eebcc 100644 --- a/scripts/context.py +++ b/scripts/context.py @@ -8,7 +8,7 @@ UsdcAccounting, AyiiProduct, AyiiRiskpool, - AyiiOracle, + ProtectedMulticall, Wei, ) @@ -34,6 +34,7 @@ class Context: "insurer", "customer1", "customer2", + "escrow", ] feeCapitalFix = 0 feeCapitalPercentage = 0 @@ -52,12 +53,23 @@ def initialize(self): self.registry = getSecret("Addresses", "registry") self.usdcAccountingToken = getSecret("Addresses", "usdc_accounting_token") + self.clfConsumer = getSecret("Addresses", "clf_consumer") + self.multicallAddress = getSecret("Addresses", "multicall") self.usdc = contract_from_address(UsdcAccounting, self.usdcAccountingToken) + self.multicall = contract_from_address( + ProtectedMulticall, self.multicallAddress + ) + self.decimals = self.usdc.decimals() self.accounts = { s: accounts.from_mnemonic(getSecret("Mnemonics", s)) for s in self.stakeholders } + self.instanceOperator = self.accounts["instanceOperator"] + self.insurer = self.accounts["insurer"] + self.escrow = self.accounts["escrow"] + self.farmer_hd_base = "insurer" + (instance, product, oracle, riskpool, componentController) = from_registry( self.registry ) @@ -71,15 +83,19 @@ def initialize(self): self.oracleId = oracle.getId() self.productId = product.getId() self.componentController = componentController - self.instanceOperator = self.accounts["instanceOperator"] + assert self.instanceService.getInstanceOperator() == self.instanceOperator assert self.riskpool.getFullCollateralizationLevel() == 1000000000000000000 self.fullCollateralizationLevel = self.riskpool.getFullCollateralizationLevel() self.noPrint = ["accounts", "stakeholders", "noPrint", "components"] + self.components = [] def setContext(self, key, value): setattr(self, key, value) + def getHdWallet(self, name, index): + return accounts.from_mnemonic(getSecret("Mnemonics", name), 1, index) + def printContext(self): print("Context:") for k, v in self.__dict__.items(): @@ -90,7 +106,12 @@ def printAccounts(self): print("Accounts:") for k, v in self.accounts.items(): print( - f'{k.ljust(30)}: {v.address} {str(Wei(v.balance()).to("ether")).rjust(25)}' + ( + f"{k.ljust(30)}: " + f"{v.address[:8]} " + f'{str(Wei(v.balance()).to("ether"))[-22:-16].rjust(8)} ' + f"{str(round(self.usdc.balanceOf(v)/10**self.decimals,2)).rjust(12)}" + ) ) def loadComponents(self): @@ -105,6 +126,7 @@ def loadComponents(self): cType = componentController.getComponentType(componentIndex) cState = componentController.getComponentState(componentIndex) componentData = { + "index": componentIndex, "address": cAddress, "type": cType, "state": cState, @@ -144,8 +166,21 @@ def loadComponents(self): return result def getComponents(self, type=None, reload=False): - if reload or not hasattr(self, "components"): + if reload or not hasattr(self, "components") or len(self.components) == 0: self.loadComponents() if type is not None: return list(filter(lambda x: x["type"] == type, self.components)) return self.components + + +context = Context() + +# Convenience: + +usdc = context.usdc +a = context.accounts +iop = context.instanceOperator +insurer = context.insurer +escrow = context.escrow +getHdWallet = context.getHdWallet +multicall = context.multicall diff --git a/scripts/deploy_ayii.py b/scripts/deploy_ayii.py index 41ec1d7..65da73b 100644 --- a/scripts/deploy_ayii.py +++ b/scripts/deploy_ayii.py @@ -435,11 +435,11 @@ def deploy_product_with_oracle_riskpool( registry_address ) ) - (instance, product, oracle, riskpool) = from_registry(registry_address) + (instance, product, oracle, riskpool, _) = from_registry(registry_address) print( "====== deploy ayii product /w base name '{}' and riskpool collateralization level {} ======".format( - baseName, collateralizaionLevel + baseName, collateralizationLevel ) ) ayiiDeploy = GifAyiiProductComplete( @@ -522,7 +522,6 @@ def deploy(stakeholders_accounts, erc20_token, publishSource=False): productOwner, insurer, oracleProvider, - chainlinkNodeOperator, riskpoolKeeper, investor, erc20Token, @@ -753,14 +752,14 @@ def from_registry( oracle = contract_from_address(AyiiOracle, componentAddress) if oracle.getType() != 0: - oracle = None if verbose: print( "component (type={}) with id {} is not oracle".format( - component.getType(), componentId + oracle.getType(), componentId ) ) print("no oracle returned (None)") + oracle = None elif verbose: print("1 oracle expected, no oracles available") print("no oracle returned (None)") @@ -779,14 +778,14 @@ def from_registry( riskpool = contract_from_address(AyiiRiskpool, componentAddress) if riskpool.getType() != 2: - riskpool = None if verbose: print( "component (type={}) with id {} is not riskpool".format( - component.getType(), componentId + riskpool.getType(), componentId ) ) print("no riskpool returned (None)") + riskpool = None elif verbose: print("1 riskpool expected, no riskpools available") print("no riskpool returned (None)") @@ -844,3 +843,13 @@ def create_risk(product, insurer, project, uai, crop, trigger, exit_, tsi, aph): ) return tx.events["LogAyiiRiskDataCreated"]["riskId"] + + +def setClfConsumer(context): + if not confirm("set clf consumer in oracle"): + return + + oracleProvider = context.accounts["oracleProvider"] + clfConsumer = context.clfConsumer + tx = context.oracle.setClfGateWay(clfConsumer, {"from": oracleProvider}) + print(tx) diff --git a/scripts/farmerFunding.py b/scripts/farmerFunding.py new file mode 100644 index 0000000..8da182a --- /dev/null +++ b/scripts/farmerFunding.py @@ -0,0 +1,446 @@ +from brownie import accounts, web3, Contract +from eth_account.messages import encode_typed_data +from eth_account import Account +from eth_utils import to_bytes +from time import time + +from scripts.context import context, a, usdc, getHdWallet, multicall +from scripts.prompt import prompt + +# Multicall contract, see https://www.multicall3.com/ + + +class FarmerFunding: + + partners = ["Lemonade", "WFP", "Farmer"] + currencies = ["KES", "USDC"] + token_spender_names = ["escrow", "treasury"] + + def __init__(self): + self.usdc_conversion_rate = 130 + self.instanceOperator = a["instanceOperator"] + self.insurer = a["insurer"] + self.dry_run = True + self.token_spenders = [ + getattr(context, spender) for spender in self.token_spender_names + ] + self.prompt_parameters() + + def conv_kes_usdc(self, kes): + return round(kes * 10 ** usdc.decimals() / self.usdc_conversion_rate) + + def set_dry_run(self): + self.dry_run = prompt.enterBool("Dry run?") + + def prompt_parameters(self): + self.farmer_count = prompt.enterNumber( + "Enter the number of farmers", + default=5052, + len=42, + ) + self.farmer_hd_start_index = prompt.enterNumber( + "Enter the Farmer HD wallet start index", + default=20000, + len=42, + ) + self.escrow_hd_start_index = prompt.enterNumber( + "Enter the Escrow HD wallet start index", + default=10, + len=42, + ) + self.batch_size = prompt.enterNumber( + "Enter the batch size for permits", + default=100, + len=42, + ) + self.approval_amount = prompt.enterNumber( + "Enter the approval amount for permits", + default=1000 * 10 ** usdc.decimals(), + len=42, + ) + + self.farmer_hd_last_index = None + self.farmer_hd_max_index = self.farmer_hd_start_index + self.farmer_count + + self.partnerWallets = { + partner: { + "index": index + self.escrow_hd_start_index, + "wallet": getHdWallet("escrow", index + self.escrow_hd_start_index), + } + for index, partner in enumerate(self.partners) + } + + self.funding = { + "single": { + "KES": { + "Lemonade": prompt.enterNumber( + "Enter the Lemonade funding amount in KES", + default=1000, + len=42, + ), + "WFP": prompt.enterNumber( + "Enter the WFP funding amount in KES", + default=1000, + len=42, + ), + "Farmer": prompt.enterNumber( + "Enter the Farmer funding amount in KES", + default=500, + len=42, + ), + } + } + } + self.funding["single"]["USDC"] = { + key: self.conv_kes_usdc(value) + for key, value in self.funding["single"]["KES"].items() + } + self.funding["total"] = { + "farmer": { + keyCurr: sum(value for value in valueCurr.values()) + for keyCurr, valueCurr in self.funding["single"].items() + }, + "partner": { + keyCurr: { + keyPartner: value * self.farmer_count + for keyPartner, value in valueCurr.items() + } + for keyCurr, valueCurr in self.funding["single"].items() + }, + } + + def print_parameters(self): + data = self.funding + """ + Prints a formatted table of the funding parameters. + """ + + # Extracting and processing data + rows = [] + + # Processing 'single' category + for currency, entities in data["single"].items(): + for entity, value in entities.items(): + rows.append(["single", "", currency, entity, value]) + + # Processing 'total' category + for category, currencies in data["total"].items(): + for currency, entities in currencies.items(): + if isinstance(entities, int): # Extract value directly + rows.append(["total", category, currency, "", entities]) + else: + for entity, value in entities.items(): + rows.append(["total", category, currency, entity, value]) + + # Find max widths for each column for alignment + col_widths = [ + max( + len(str(row[i])) + for row in rows + + [["Category", "Subcategory", "Currency", "Entity", "Value"]] + ) + for i in range(5) + ] + + # Print header + print( + f"{'Category':<{col_widths[0]}} {'Subcategory':<{col_widths[1]}} {'Currency':<{col_widths[2]}} {'Entity':<{col_widths[3]}} {'Value':>{col_widths[4]}}" + ) + print("-" * (sum(col_widths) + 10)) # Line separator + + # Print rows with optimized display (avoiding duplicates) + prev_category, prev_subcategory, prev_currency = None, None, None + for category, subcategory, currency, entity, value in rows: + category_str = ( + category if category != prev_category else "" + ) # Print only if different + subcategory_str = ( + subcategory if subcategory != prev_subcategory else "" + ) # Print only if different + currency_str = ( + currency if currency != prev_currency else "" + ) # Print only if different + print( + f"{category_str:<{col_widths[0]}} {subcategory_str:<{col_widths[1]}} {currency_str:<{col_widths[2]}} {entity:<{col_widths[3]}} {value:>{col_widths[4]}}" + ) + prev_category, prev_subcategory, prev_currency = ( + category, + subcategory, + currency, + ) + + def check_parameters(self): + return ( + all( + hasattr(self, element) + for element in [ + "farmer_count", + "farmer_hd_start_index", + "escrow_hd_start_index", + "funding", + ] + ) + and all( + hasattr(self.funding, keySingleTotal) + for keySingleTotal in ["single", "total"] + ) + and all( + [ + hasattr(self.funding[keySingleTotal], keyCurr) + for keyCurr in self.currencies + for keySingleTotal in ["single", "total"] + ] + ) + and all( + [ + hasattr( + self.funding["single"][keyCurr], + keyPartner, + ) + for keyPartner in self.partners + for keyCurr in self.currencies + ] + ) + and all( + [ + hasattr(self.funding["total"][keyCurr], keyPartner) + for keyCurr in self.currencies + for keyPartner in self.partners + ] + ) + ) + + def fund_escrows(self): + if self.check_parameters(): + for i in range( + self.hd_start_index, self.hd_start_index + self.farmer_count + ): + pass + + else: + print("Please set the parameters first.") + return + + def fund_escrows(self): + for partner, wallet in self.partnerWallets.items(): + targetFunding = self.funding["total"]["partner"]["USDC"][partner] + balance = usdc.balanceOf(wallet["wallet"]) + if balance < targetFunding: + print( + f"Insufficient funds in {partner} wallet. Current balance: {balance}, required: {targetFunding}" + ) + if self.dry_run: + print( + ( + f"Dry run: Transfer " + f"{targetFunding - balance} USDC to {partner} wallet" + ) + ) + continue + usdc.transfer( + wallet["wallet"], + targetFunding - balance, + {"from": self.instanceOperator}, + ) + else: + print( + ( + f"{partner} wallet has sufficient funds" + f" (target = {targetFunding}, balance: {balance})." + ) + ) + + def approve_escrows(self): + spender = context.multicallAddress + for partner, wallet in self.partnerWallets.items(): + targetFunding = self.funding["total"]["partner"]["USDC"][partner] + print( + ( + f"{'Dry run: ' if self.dry_run else ''}Approve {partner} wallet " + f"for {targetFunding} USDC to {spender}" + ) + ) + if self.dry_run: + continue + + if wallet["wallet"].balance() < 0.05 * 10**18: + print(f"Insufficient funds in {partner} wallet - funding") + self.instanceOperator.transfer(wallet["wallet"], 0.05 * 10**18) + + usdc.approve(spender, targetFunding, {"from": wallet["wallet"]}) + + # Generate EIP-712 Permit signature + def get_permit_calldata(self, wallet, spender, amount, nonce, deadline): + + domain = { + "name": usdc.name(), + "version": "1", + "chainId": web3.eth.chain_id, + "verifyingContract": usdc.address, + } + + types = { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"}, + ], + "Permit": [ + {"name": "owner", "type": "address"}, + {"name": "spender", "type": "address"}, + {"name": "value", "type": "uint256"}, + {"name": "nonce", "type": "uint256"}, + {"name": "deadline", "type": "uint256"}, + ], + } + + message = { + "owner": wallet.address, + "spender": spender.address, + "value": amount, + "nonce": nonce, + "deadline": deadline, + } + + # Encode and sign the permit data + typed_data = { + "types": types, + "domain": domain, + "primaryType": "Permit", + "message": message, + } + + signature = Account.sign_message( + encode_typed_data(full_message=typed_data), wallet.private_key + ) + + calldata = usdc.permit.encode_input( + wallet.address, + spender.address, + self.approval_amount, + deadline, + signature.v, + signature.r, + signature.s, + ) + + return calldata + + def relayCalls(self, multicall_data): + tx = multicall.aggregate(multicall_data, {"from": self.insurer}) + tx.wait(1) + + # Send batched permits via multicall + def batch_process(self, num_wallets=1, get_call_data=lambda x: None): + multicall_data = [] + if self.farmer_hd_last_index is not None: + self.farmer_hd_start_index = self.farmer_hd_last_index + 1 + else: + self.farmer_hd_last_index = self.farmer_hd_start_index + last_index = self.farmer_hd_start_index + try: + for w_idx in range( + self.farmer_hd_start_index, + min( + self.farmer_hd_start_index + num_wallets, + self.farmer_hd_max_index + 1, + ), + ): + self.farmer_hd_last_index = w_idx + + multicall_data.extend(get_call_data(w_idx)) + + # Break into batches + if len(multicall_data) >= self.batch_size: + print( + ( + f"Sending batch of {len(multicall_data)} permits " + f"(starting from {last_index} to {w_idx})" + ) + ) + last_index = w_idx + self.relayCalls(multicall_data) + multicall_data = [] # Reset batch + + self.farmer_hd_last_index = w_idx + + # Send remaining transactions + if len(multicall_data) > 0: + print( + ( + f"Sending batch of {len(multicall_data)} permits " + f"(starting from {self.farmer_hd_last_index} to {w_idx})" + ) + ) + self.relayCalls(multicall_data) + + except Exception as e: + print(f"Error: {e}") + + def get_multi_permit_calldata(self, index): + wallet = getHdWallet(context.farmer_hd_base, index) + multicall_data = [] + nonce = usdc.nonces(wallet.address) # Get current nonce + for s_idx, spender in enumerate(self.token_spenders): + deadline = int(time()) + 3600 # 1 hour from now + + # Encode permit call + permit_calldata = self.get_permit_calldata( + wallet, spender, self.approval_amount, nonce + s_idx, deadline + ) + + multicall_data.append((usdc.address, permit_calldata)) + return multicall_data + + def get_airdrop_calldata(self, index): + wallet = getHdWallet(context.farmer_hd_base, index) + multicall_data = [] + for partner, funding in self.funding["single"]["USDC"].items(): + calldata = usdc.transferFrom.encode_input( + self.partnerWallets[partner]["wallet"].address, wallet.address, funding + ) + multicall_data.append((usdc.address, calldata)) + return multicall_data + + def batch_permits(self, num_wallets=1): + self.batch_process(num_wallets, self.get_multi_permit_calldata) + + def batch_airdrop(self, num_wallets=1): + self.batch_process(num_wallets, self.get_airdrop_calldata) + + def check_allowance(self, start, stop, verbose=False): + if verbose: + print( + (f"{'Allowances:'.ljust(40)}" f"{' '.join(self.token_spender_names)}") + ) + for i in range(start, stop): + + wallet = getHdWallet(context.farmer_hd_base, i) + values = [ + usdc.allowance(wallet.address, spender.address) + for spender in self.token_spenders + ] + allowances = " ".join([str(value).ljust(20) for value in values]) + if verbose: + print(f"Allowance for wallet {wallet.address} ({i}): {allowances}") + else: + if i % 100 == 0: + print(f"Checking wallet {i}-{i+100}...") + if any(value != self.approval_amount for value in values): + print(f"Allowance for wallet {wallet.address} ({i}): {allowances}") + + def check_balances(self, start, stop, verbose=False): + for i in range(start, stop): + wallet = getHdWallet(context.farmer_hd_base, i) + bal = usdc.balanceOf(wallet.address) + if verbose: + print(f"Balance for wallet {wallet.address} ({i}): {bal}") + else: + if i % 100 == 0: + print(f"Checking wallet {i}-{i+100}...") + if bal != self.funding["total"]["farmer"]["USDC"]: + print(f"Balance for wallet {wallet.address} ({i}): {bal}") + + +farmer_funding = FarmerFunding() diff --git a/scripts/interactive.py b/scripts/interactive.py index bbea468..79be6a4 100644 --- a/scripts/interactive.py +++ b/scripts/interactive.py @@ -1,26 +1,22 @@ from brownie import ( AyiiProduct, - AyiiOracle, AyiiRiskpool, GenericOracle, GenericRiskpool, GenericProduct, ) -from scripts.deploy_ayii import verify_deploy, printBundle +from scripts.deploy_ayii import verify_deploy, printBundle, setClfConsumer from scripts.util import contract_from_address, decodeEnum, b2s, s2b, utcStr, fromWei -from scripts.context import Context +from scripts.context import context, a from scripts.prompt import prompt -context = Context() -a = context.accounts - def listComponents(): components = context.getComponents() - for componentIndex, componentData in enumerate(components): + for index, componentData in enumerate(components): info = ( - f"{componentIndex:>3} : " + f'{componentData["index"]:>3} : ' f'{componentData["typeStr"].ljust(12)}\n' f' Address: {componentData["address"]}\n' f' Status: {componentData["stateStr"].ljust(12)}' @@ -146,26 +142,8 @@ def getComponent(componentId): return contract_from_address(contract, address) -def createRisk(): +def createRisk(projectId, uaiId, cropId, trigger, exit, tsi, aph): insurer = a["insurer"] - projectId = prompt.enterString("Enter the project ID:", r"^\d{1,10}$") - uaiId = prompt.enterString("Enter the UAI ID:", r"^\d{1,10}$") - cropId = prompt.enterString("Enter the crop ID:", r"^[a-zA-Z]+$") - trigger = prompt.enterFloat("Enter the trigger:", 0, 1) - exit = prompt.enterFloat("Enter the exit:", 0, 1) - tsi = prompt.enterFloat("Enter the TSI:", 0, 1) - aph = prompt.enterFloat("Enter the APH:", 0, 1) - print("Creating risk:") - print(f"projectId: {projectId}") - print(f"uaiId : {uaiId}") - print(f"cropId : {cropId}") - print(f"Trigger : {trigger}") - print(f"Exit : {exit}") - print(f"TSI : {tsi}") - print(f"APH : {aph}") - if not prompt.confirm("create risk"): - return - mul = context.product.getPercentageMultiplier() (trigger, exit, tsi, aph) = (mul * trigger, mul * exit, mul * tsi, mul * aph) tx = context.product.createRisk( @@ -180,6 +158,41 @@ def createRisk(): ) riskId = dict(tx.events["LogAyiiRiskDataCreated"])["riskId"] print(f"Risk created with ID: {riskId}") + return riskId + + +def queryRiskParameters(): + projectId = prompt.enterString("Enter the project ID:", r"^\d{1,10}$") + uaiId = prompt.enterString("Enter the UAI ID :", r"^\d{1,10}$") + cropId = prompt.enterString("Enter the crop ID :", r"^[a-zA-Z]+$") + trigger = prompt.enterFloat( + "Enter the trigger :", lowerBound=0, upperBound=1, default=0.7 + ) + exit = prompt.enterFloat( + "Enter the exit :", lowerBound=0, upperBound=1, default=0.0 + ) + tsi = prompt.enterFloat( + "Enter the TSI :", lowerBound=0, upperBound=1, default=0.7 + ) + aph = prompt.enterFloat( + "Enter the APH :", lowerBound=0, upperBound=1, default=0.9876 + ) + return (projectId, uaiId, cropId, trigger, exit, tsi, aph) + + +def createRiskInteractive(): + (projectId, uaiId, cropId, trigger, exit, tsi, aph) = queryRiskParameters() + print("Creating risk:") + print(f"projectId: {projectId}") + print(f"uaiId : {uaiId}") + print(f"cropId : {cropId}") + print(f"Trigger : {trigger}") + print(f"Exit : {exit}") + print(f"TSI : {tsi}") + print(f"APH : {aph}") + if not prompt.confirm("create risk"): + return + createRisk(projectId, uaiId, cropId, trigger, exit, tsi, aph) def getRisks(): @@ -209,6 +222,9 @@ def getRisks(): def listRisks(): riskData = getRisks() riskCount = len(riskData) + if riskCount == 0: + print("No risks available") + return for riskIndex in range(0, riskCount): data = riskData[riskIndex] risk = data["risk"] @@ -234,19 +250,27 @@ def listRisks(): ) -def listRiskShort(): +def listRiskShort(filter=lambda x: True): riskData = getRisks() - return [ - f"{i:>3}: " - f'{b2s(risk["risk"]["projectId"]).ljust(10)} ' - f'{b2s(risk["risk"]["uaiId"]).ljust(10)} ' - f'{b2s(risk["risk"]["cropId"]).ljust(10)} ' - f'{risk["trigger"]:.5f} ' - f'{risk["exit"]:.5f} ' - f'{risk["tsi"]:.5f} ' - f'{risk["aph"]:.5f} ' + return { + ( + f"{i:>3} :" + f'{b2s(risk["risk"]["projectId"]).ljust(10)}:' + f'{b2s(risk["risk"]["uaiId"]).ljust(10)}:' + f'{b2s(risk["risk"]["cropId"]).ljust(10)}:' + f'{risk["trigger"]:.5f}:' + f'{risk["exit"]:.5f}:' + f'{risk["tsi"]:.5f}:' + f'{risk["aph"]:.5f}:' + f'{risk["aaay"]:.5f}:' + f'{risk["risk"]["requestTriggered"]}:' + f'{utcStr(risk["risk"]["responseAt"])}:' + f'{utcStr(risk["risk"]["createdAt"])}:' + f'{utcStr(risk["risk"]["updatedAt"])}:' + ): risk["riskId"] for i, risk in enumerate(riskData) - ] + if filter(risk) + } def getPolicies(): @@ -301,12 +325,12 @@ def selectRisk(): return None elif risks == 1: print("Only one risk available - selecting it") - return 0 + return context.product.getRiskId(0) options = listRiskShort() - options.append("Cancel") + options["Cancel"] = None print("Select Risk:") - selection = options.index(prompt.menu(options)) - if selection == len(options) - 1: + selection = prompt.dictMenu(options) + if selection is None: return None return context.product.getRiskId(selection) @@ -323,7 +347,11 @@ def fundBundle(): return funding = prompt.enterCurrency( - "Enter the funding amount:", context.usdc.decimals(), lowerBound=0 + "Enter the funding amount:", + context.usdc.decimals(), + lowerBound=0, + upperBound=10000000, + default=0.0, ) if funding == 0: @@ -351,23 +379,7 @@ def fundBundle(): printBundle(context.riskpool, bundleId) -def createPolicy(): - - riskId = selectRisk() - if riskId is None: - return - - premium = prompt.enterCurrency( - "Enter the premium amount:", context.usdc.decimals(), lowerBound=0 - ) - if premium == 0: - return - - sumInsured = prompt.enterCurrency( - "Enter the sum insured amount:", context.usdc.decimals(), lowerBound=0 - ) - if sumInsured == 0: - return +def createPolicy(riskId, premium, sumInsured): customer = context.accounts["customer1"] insurer = context.accounts["insurer"] @@ -390,17 +402,146 @@ def createPolicy(): ) policyId = dict(tx.events["LogAyiiPolicyCreated"])["policyId"] print(f"Policy created with ID: {policyId}") + return policyId -def triggerOracle(): - policy = selectPolicy() - if policy is None: +def queryPolicyParameters(): + premium = prompt.enterCurrency( + "Enter the premium amount:", context.usdc.decimals(), lowerBound=0, default=15.0 + ) + if premium == 0: return - policyId = policy["policyId"] - insurer = context.accounts["insurer"] - if not prompt.confirm(f"Trigger oracle request for policyId: {policyId}"): + + sumInsured = prompt.enterCurrency( + "Enter the sum insured amount:", + context.usdc.decimals(), + lowerBound=0, + default=150.0, + ) + if sumInsured == 0: + return + return (premium, sumInsured) + + +def createPolicyInteractive(): + riskId = selectRisk() + if riskId is None: return + (premium, sumInsured) = queryPolicyParameters() + createPolicy(riskId, premium, sumInsured) + + +def triggerOracle(policyId): print(f"Triggering oracle request for policyId: {policyId}") - tx = context.product.triggerOracle(policyId, {"from": insurer}) + insurer = context.accounts["insurer"] + tx = context.product.triggerOracle( + policyId, + { + "from": insurer + # "gas_limit": 1000000, + # "allow_revert": True + }, + ) requestId = dict(tx.events["LogAyiiRiskDataRequested"])["requestId"] print(f"Oracle request triggered with requestId: {requestId}") + + +def triggerOracleInteractive(): + risks = listRiskShort( + lambda r: not r["risk"]["requestTriggered"] and r["risk"]["responseAt"] == 0 + ) + if len(risks.keys()) == 0: + print("No oracle requests available") + return + options = risks + options["Cancel"] = None + print("Select Risk Request:") + print( + ( + " Idx |ProjectId | UaId | " + "CropId | Trig | Exit | TSI | " + "APH | AAAY |Trig|Response | " + "Created | Updated" + ) + ) + riskId = prompt.dictMenu(options) + if riskId is None: + return + try: + policyCount = context.product.policies(riskId) + if policyCount == 0: + print("No policies available for this risk") + return + + policyId = context.product.getPolicyId(riskId, 0) + + if not prompt.confirm(f"Trigger oracle request for policyId: {policyId}"): + return + print(f"Triggering oracle request for policyId: {policyId}") + triggerOracle(policyId) + + except Exception as e: + print(f"Error triggering oracle request: {e}") + + +def doSetClfConsumer(): + setClfConsumer(context) + + +def fullCycle(): + count = prompt.enterNumber("Enter the number of policies to create:", 1, 100) + (projectId, uaiId, cropId, trigger, exit, tsi, aph) = queryRiskParameters() + (premium, sumInsured) = queryPolicyParameters() + + for i in range(count): + uaiId2 = f"{int(uaiId)+i}" + print( + f"Creating risk with projectId: {projectId} / uaiId: {uaiId2} / cropId: {cropId}" + ) + riskId = createRisk(projectId, uaiId2, cropId, trigger, exit, tsi, aph) + policyId = createPolicy(riskId, premium, sumInsured) + triggerOracle(policyId) + + +def cancelOracleRequestInteractive(): + risks = listRiskShort( + lambda r: r["risk"]["requestTriggered"] and r["risk"]["responseAt"] == 0 + ) + if len(risks.keys()) == 0: + print("No oracle requests available") + return + options = risks + options["Cancel"] = None + print("Select Oracle Request:") + print( + ( + " Idx |ProjectId | UaId | " + "CropId | Trig | Exit | TSI | " + "APH | AAAY |Trig|Response | " + "Created | Updated" + ) + ) + riskId = prompt.dictMenu(options) + if riskId is None: + return + + try: + policyCount = context.product.policies(riskId) + if policyCount == 0: + print("No policies available for this risk") + return + policyId = context.product.getPolicyId(riskId, 0) + if not prompt.confirm(f"Cancel oracle request for policyId: {policyId}"): + return + tx = context.product.cancelOracleRequest( + policyId, {"from": context.accounts["insurer"]} + ) + print(f"Oracle request for policyId: {policyId} canceled") + print(tx) + + except Exception as e: + print(f"Error canceling oracle request: {e}") + + +def initializeContext(): + context.initialize() diff --git a/scripts/logger.py b/scripts/logger.py new file mode 100644 index 0000000..d4da38d --- /dev/null +++ b/scripts/logger.py @@ -0,0 +1,28 @@ +class LoggerWrapper: + def __init__(self, base_instance): + """ + Initialize the wrapper with the base instance. + :param base_instance: Instance of the base class to wrap. + """ + self._base_instance = base_instance + + def __getattr__(self, name): + """ + Intercept attribute access and add logging for methods. + :param name: Name of the attribute being accessed. + :return: The wrapped method or the original attribute. + """ + attr = getattr(self._base_instance, name) + + # If it's a callable (method), wrap it with logging + if callable(attr): + + def logged_method(*args, **kwargs): + print(f"Calling method: {name}") + print(f"Arguments: {args}, Keyword Arguments: {kwargs}") + result = attr(*args, **kwargs) + print(f"Result: {result}") + return result + + return logged_method + return attr diff --git a/scripts/menu.py b/scripts/menu.py index 39c15e9..e7b0651 100644 --- a/scripts/menu.py +++ b/scripts/menu.py @@ -2,12 +2,18 @@ from scripts.interactive import ( context, listComponents, - createRisk, + createRiskInteractive, listRisks, + listRiskShort, selectTypeAndComponent, fundBundle, - createPolicy, - triggerOracle, + getFeeSpecification, + createPolicyInteractive, + triggerOracleInteractive, + cancelOracleRequestInteractive, + doSetClfConsumer, + fullCycle, + initializeContext, ) from scripts.deploy_ayii import ( @@ -19,6 +25,23 @@ ) +def executeFunctionOrObject(param): + if callable(param): + # If param is a function, call it without parameters + return param() + elif isinstance(param, dict) and "function" in param and "args" in param: + # If param is a dictionary with 'function' and 'args', execute the function with arguments + func = param["function"] + args = param["args"] + + if not callable(func): + raise ValueError("'function' in the dictionary must be callable") + if not isinstance(args, (list, tuple)): + raise ValueError("'args' in the dictionary must be a list or tuple") + + return func(*args) + + def menu(): run = True while run: @@ -50,18 +73,27 @@ def menu(): ], }, "Context": context.printContext, + "Initialize Context": initializeContext, "Accounts": context.printAccounts, "List Components": listComponents, - "Create Risk": createRisk, + "Create Risk": createRiskInteractive, "List Risks": listRisks, + "List Risk Short": listRiskShort, "Select Type and Component": selectTypeAndComponent, "Fund Bundle": fundBundle, - "Create Policy": createPolicy, - "Trigger Oracle": triggerOracle, + "Create Policy": createPolicyInteractive, + "Trigger Oracle": triggerOracleInteractive, + "Cancel Oracle Request": cancelOracleRequestInteractive, + "Show Fee Specification": getFeeSpecification, + "Set Clf Consumer": doSetClfConsumer, + "Full Cycle": fullCycle, "Exit": None, } selection = prompt.dictMenu(options) - run = selection != "Exit" + if selection is None: + run = False + else: + executeFunctionOrObject(selection) menu() diff --git a/scripts/prompt.py b/scripts/prompt.py index 883a0d9..c9cfb0a 100644 --- a/scripts/prompt.py +++ b/scripts/prompt.py @@ -9,25 +9,6 @@ class Prompt: INTERACTIVE = True - def executeFunctionOrObject(self, param): - if callable(param): - # If param is a function, call it without parameters - return param() - elif isinstance(param, dict) and "function" in param and "args" in param: - # If param is a dictionary with 'function' and 'args', execute the function with arguments - func = param["function"] - args = param["args"] - - if not callable(func): - raise ValueError("'function' in the dictionary must be callable") - if not isinstance(args, (list, tuple)): - raise ValueError("'args' in the dictionary must be a list or tuple") - - return func(*args) - # else: - # raise ValueError( - # "Parameter must be a function or a dictionary with 'function' and 'args' keys") - def menu(self, options): terminal_menu = TerminalMenu(options) menu_entry_index = terminal_menu.show() @@ -38,17 +19,28 @@ def dictMenu(self, dictOptions): # Convert keys to list and make a menu selection = self.menu(list(dictOptions.keys())) # Get the method that corresponds to the selected key - selected_function = dictOptions.get(selection) - self.executeFunctionOrObject(selected_function) # Invoke the method - return selection + selected = dictOptions.get(selection) + return selected - def qText(self, prompt, validate, default=""): + def qText(self, prompt, validate, default="", len=20, fill=" "): response = questionary.text( - prompt.ljust(30), validate=validate, default=default + self.r_just_prompt(prompt, len, fill), validate=validate, default=default ).ask() return response - def enterCurrency(self, prompt, scale=1, lowerBound=0.1, upperBound=100000): + def r_just_prompt(self, prompt, len=20, fill=" "): + return prompt.ljust(len, fill) + ": " + + def enterCurrency( + self, + prompt, + scale=1, + lowerBound=0.1, + upperBound=100000, + default=0, + len=20, + fill=" ", + ): def validateCurrency(value): try: # Convert the input to a number @@ -63,10 +55,24 @@ def validateCurrency(value): except ValueError: return "Invalid input. Please enter a valid number." - response = self.qText(prompt, validate=validateCurrency) + response = self.qText( + prompt, + validate=validateCurrency, + default=f"{default:.2f}", + len=len, + fill=fill, + ) return int(round(float(response) * 10**scale)) - def enterNumber(self, prompt, lowerBound, upperBound): + def enterNumber( + self, + prompt, + lowerBound=0, + upperBound=2**64, + default=0, + len=20, + fill=" ", + ): def validateNumber(value): try: # Convert the input to a number @@ -81,10 +87,24 @@ def validateNumber(value): except ValueError: return "Invalid input. Please enter a valid number." - response = self.qText(prompt, validate=validateNumber) + response = self.qText( + prompt, + validate=validateNumber, + default=f"{default}", + len=len, + fill=fill, + ) return int(response) - def enterFloat(self, prompt, lowerBound, upperBound): + def enterFloat( + self, + prompt, + lowerBound=0.0, + upperBound=10**9, + default=0.0, + len=20, + fill=" ", + ): def validateFloat(value): try: # Convert the input to a number @@ -99,17 +119,33 @@ def validateFloat(value): except ValueError: return "Invalid input. Please enter a valid number." - response = self.qText(prompt, validate=validateFloat) + response = self.qText( + prompt, validate=validateFloat, default=f"{default:.2f}", len=len, fill=fill + ) return float(response) - def enterString(self, prompt, regex, default=""): + def enterString(self, prompt, regex=r".*", default=""): def validateString(value): r = re.compile(regex) if r.match(value): return True return "Invalid input. Please enter a valid string." - response = self.qText(prompt, validate=validateString, default=default) + response = self.qText( + prompt, validate=validateString, default=default, len=20, fill=" " + ) + return response + + def enterBool( + self, + prompt, + default=True, + len=20, + fill=" ", + ): + response = questionary.confirm( + self.r_just_prompt(prompt, len=len, fill=fill), default=default + ).ask() return response def setInteractive(self, interactive): From 7c448c0be5377edcbe9ec854c90d406cf932cefe Mon Sep 17 00:00:00 2001 From: Christoph Mussenbrock Date: Mon, 3 Feb 2025 12:25:05 +0000 Subject: [PATCH 18/18] Update github actions to v4 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5221af5..8953ca8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,14 +13,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python 3.x uses: actions/setup-python@v4 with: python-version: '3.x' - name: Setup node environment - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 16