diff --git a/.gitleaksignore b/.gitleaksignore index 33c8c27cf..95964f4b2 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -29,4 +29,7 @@ d8149e9b5b7bd6a7ed7bc1039900702f1d4f287b:operate/services/manage.py:generic-api- 99c0f139b037da2587708212fcf6d0e20786d0ba:operate/services/manage.py:generic-api-key:455 91ec07457f69e9a29f63693ac8ef887e4b5f49f0:operate/services/manage.py:generic-api-key:454 410bea2bd02ff54da69387fe8f3b58793e09f7b0:operate/services/manage.py:generic-api-key:421 -410bea2bd02ff54da69387fe8f3b58793e09f7b0:operate/services/manage.py:generic-api-key:422 \ No newline at end of file +410bea2bd02ff54da69387fe8f3b58793e09f7b0:operate/services/manage.py:generic-api-key:422 +467e8e64f51fb3659e5af17ba53ab587ec24fc30:operate/services/manage.py:generic-api-key:290 +64afe2ea92daafa2515c054f0e09931622d99f31:operate/services/manage.py:generic-api-key:290 +64afe2ea92daafa2515c054f0e09931622d99f31:operate/services/manage.py:generic-api-key:289 \ No newline at end of file diff --git a/electron/install.js b/electron/install.js index a43856a9b..861bf44ed 100644 --- a/electron/install.js +++ b/electron/install.js @@ -14,7 +14,7 @@ const { paths } = require('./constants'); * - use "" (nothing as a suffix) for latest release candidate, for example "0.1.0rc26" * - use "alpha" for alpha release, for example "0.1.0rc26-alpha" */ -const OlasMiddlewareVersion = '0.1.0rc117'; +const OlasMiddlewareVersion = '0.1.0rc120'; const path = require('path'); const { app } = require('electron'); diff --git a/frontend/components/MainPage/sections/AddFundsSection.tsx b/frontend/components/MainPage/sections/AddFundsSection.tsx index 97bf85641..ad3e17053 100644 --- a/frontend/components/MainPage/sections/AddFundsSection.tsx +++ b/frontend/components/MainPage/sections/AddFundsSection.tsx @@ -18,7 +18,6 @@ import styled from 'styled-components'; import { UNICODE_SYMBOLS } from '@/constants/symbols'; import { COW_SWAP_GNOSIS_XDAI_OLAS_URL } from '@/constants/urls'; import { useWallet } from '@/hooks/useWallet'; -import { Address } from '@/types/Address'; import { copyToClipboard } from '@/utils/copyToClipboard'; import { truncateAddress } from '@/utils/truncate'; @@ -35,23 +34,6 @@ const CustomizedCardSection = styled(CardSection)<{ border?: boolean }>` export const AddFundsSection = () => { const [isAddFundsVisible, setIsAddFundsVisible] = useState(false); - const { masterSafeAddress } = useWallet(); - - const fundingAddress: Address | undefined = masterSafeAddress; - - const truncatedFundingAddress: string | undefined = useMemo( - () => fundingAddress && truncateAddress(fundingAddress), - [fundingAddress], - ); - - const handleCopyAddress = useCallback( - () => - fundingAddress && - copyToClipboard(fundingAddress).then(() => - message.success('Copied successfully!'), - ), - [fundingAddress], - ); return ( <> @@ -75,17 +57,36 @@ export const AddFundsSection = () => { - {isAddFundsVisible && ( - <> - - - - - )} + {isAddFundsVisible && } + + ); +}; + +export const OpenAddFundsSection = () => { + const { masterSafeAddress } = useWallet(); + + const truncatedFundingAddress: string | undefined = useMemo( + () => masterSafeAddress && truncateAddress(masterSafeAddress), + [masterSafeAddress], + ); + + const handleCopyAddress = useCallback( + () => + masterSafeAddress && + copyToClipboard(masterSafeAddress).then(() => + message.success('Copied successfully!'), + ), + [masterSafeAddress], + ); + return ( + <> + + + ); }; diff --git a/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx b/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx index f2c1cf6cd..7df340884 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/alerts.tsx @@ -6,28 +6,35 @@ import { UNICODE_SYMBOLS } from '@/constants/symbols'; const { Text } = Typography; export const AlertInsufficientMigrationFunds = ({ - totalOlasBalance, + masterSafeOlasBalance, + stakedOlasBalance, + totalOlasRequiredForStaking, }: { - totalOlasBalance: number; -}) => ( - - - Insufficient amount of funds to switch - + masterSafeOlasBalance: number; + stakedOlasBalance: number; + totalOlasRequiredForStaking: number; +}) => { + const requiredOlasDeposit = + totalOlasRequiredForStaking - (stakedOlasBalance + masterSafeOlasBalance); - Add funds to your account to meet the program requirements. - - Your current OLAS balance:{' '} - {totalOlasBalance} OLAS - - - } - /> -); + return ( + + + An additional {requiredOlasDeposit} OLAS is required to switch + + + Add {requiredOlasDeposit} OLAS to your account to + meet the contract requirements and switch. + + + } + /> + ); +}; export const AlertNoSlots = () => ( { const { goto } = usePageState(); - const { setServiceStatus, serviceStatus, setIsServicePollingPaused } = - useServices(); + const { + setServiceStatus, + serviceStatus, + setIsServicePollingPaused, + updateServiceStatus, + } = useServices(); const { serviceTemplate } = useServiceTemplates(); const { setMigrationModalOpen } = useModals(); const { activeStakingProgram, defaultStakingProgram, updateStakingProgram } = useStakingProgram(); - const { stakingContractInfoRecord } = useStakingContractInfo(); const { token } = useToken(); - const { totalOlasBalance, isBalanceLoaded } = useBalance(); - const { isServiceStakedForMinimumDuration } = useStakingContractInfo(); + const { safeBalance, totalOlasStakedBalance, isBalanceLoaded } = useBalance(); + const { isServiceStakedForMinimumDuration, stakingContractInfoRecord } = + useStakingContractInfo(); + const [isFundingSectionOpen, setIsFundingSectionOpen] = useState(false); + + const stakingContractAddress = + SERVICE_STAKING_TOKEN_MECH_USAGE_CONTRACT_ADDRESSES[Chain.GNOSIS][ + stakingProgram + ]; const stakingContractInfoForStakingProgram = stakingContractInfoRecord?.[stakingProgram]; @@ -87,10 +96,15 @@ export const StakingContractSection = ({ ); const hasEnoughOlasToMigrate = useMemo(() => { - if (totalOlasBalance === undefined) return false; - if (!minimumOlasRequiredToMigrate) return false; - return totalOlasBalance >= minimumOlasRequiredToMigrate; - }, [minimumOlasRequiredToMigrate, totalOlasBalance]); + if (safeBalance?.OLAS === undefined || totalOlasStakedBalance === undefined) + return false; + + const balanceForMigration = safeBalance.OLAS + totalOlasStakedBalance; + + if (minimumOlasRequiredToMigrate === undefined) return false; + + return balanceForMigration >= minimumOlasRequiredToMigrate; + }, [minimumOlasRequiredToMigrate, safeBalance?.OLAS, totalOlasStakedBalance]); const hasEnoughSlots = stakingContractInfoForStakingProgram?.maxNumServices && @@ -167,6 +181,7 @@ export const StakingContractSection = ({ isBalanceLoaded, isSelected, isServiceStakedForMinimumDuration, + minimumOlasRequiredToMigrate, serviceStatus, ]); @@ -179,9 +194,17 @@ export const StakingContractSection = ({ return ; } - if (!hasEnoughOlasToMigrate) { + if ( + !hasEnoughOlasToMigrate && + safeBalance?.OLAS !== undefined && + totalOlasStakedBalance !== undefined + ) { return ( - + ); } @@ -191,10 +214,12 @@ export const StakingContractSection = ({ }, [ isSelected, isBalanceLoaded, - totalOlasBalance, hasEnoughSlots, hasEnoughOlasToMigrate, isAppVersionCompatible, + safeBalance?.OLAS, + totalOlasStakedBalance, + minimumOlasRequiredToMigrate, ]); const contractTagStatus = useMemo(() => { @@ -213,40 +238,41 @@ export const StakingContractSection = ({ }, [activeStakingProgram, defaultStakingProgram, stakingProgram]); return ( - - {/* Title */} - - {`${activeStakingProgramMeta.name} contract`} - {/* TODO: pass `status` attribute */} - - {!isSelected && ( - // here instead of isSelected we should check that the contract is not the old staking contract - // but the one from staking factory (if we want to open govern) - - Contract details {UNICODE_SYMBOLS.EXTERNAL_LINK} - - )} - - - {/* TODO: fix */} - - {/* Contract details + <> + + {/* Title */} + + {`${activeStakingProgramMeta.name} contract`} + + {!isSelected && ( + // here instead of isSelected we should check that the contract is not the old staking contract + // but the one from staking factory (if we want to open govern) + + Contract details {UNICODE_SYMBOLS.EXTERNAL_LINK} + + )} + + + {/* TODO: redisplay once bugs resolved */} + + {/* Contract details {stakingContractInfo?.availableRewards && ( )} */} - {cantMigrateAlert} - {/* Switch to program button */} - {!isSelected && ( - + {cantMigrateAlert} + {/* Switch to program button */} + {stakingProgram !== StakingProgram.Alpha && ( + + + + )} + {stakingProgram === StakingProgram.Beta && ( - + )} + + {stakingProgram === StakingProgram.Beta && isFundingSectionOpen && ( + )} - + ); }; diff --git a/frontend/components/ManageStakingPage/index.tsx b/frontend/components/ManageStakingPage/index.tsx index 310d19eca..82938256f 100644 --- a/frontend/components/ManageStakingPage/index.tsx +++ b/frontend/components/ManageStakingPage/index.tsx @@ -1,8 +1,6 @@ import { CloseOutlined } from '@ant-design/icons'; import { Button, Card } from 'antd'; -import { Chain } from '@/client'; -import { SERVICE_STAKING_TOKEN_MECH_USAGE_CONTRACT_ADDRESSES } from '@/constants/contractAddresses'; import { Pages } from '@/enums/PageState'; import { StakingProgram } from '@/enums/StakingProgram'; import { usePageState } from '@/hooks/usePageState'; @@ -26,15 +24,8 @@ export const ManageStakingPage = () => { } > - {Object.entries( - SERVICE_STAKING_TOKEN_MECH_USAGE_CONTRACT_ADDRESSES[Chain.GNOSIS], - ).map(([stakingProgram, contractAddress]) => ( - - ))} + + ); }; diff --git a/operate/cli.py b/operate/cli.py index 7d5285d13..99cabe0f1 100644 --- a/operate/cli.py +++ b/operate/cli.py @@ -530,8 +530,8 @@ async def _create_services(request: Request) -> JSONResponse: if template.get("deploy", False): def _fn() -> None: + # deploy_service_onchain_from_safe includes stake_service_on_chain_from_safe manager.deploy_service_onchain_from_safe(hash=service.hash) - # manager.stake_service_on_chain_from_safe(hash=service.hash) # Done inside deploy_service_onchain manager.fund_service(hash=service.hash) manager.deploy_service_locally(hash=service.hash) @@ -556,8 +556,9 @@ async def _update_services(request: Request) -> JSONResponse: ) if template.get("deploy", False): manager = operate.service_manager() + + # deploy_service_onchain_from_safe includes stake_service_on_chain_from_safe manager.deploy_service_onchain_from_safe(hash=service.hash) - # manager.stake_service_on_chain_from_safe(hash=service.hash) # Done in deploy_service_onchain_from_safe manager.fund_service(hash=service.hash) manager.deploy_service_locally(hash=service.hash) schedule_funding_job(service=service.hash) @@ -675,7 +676,7 @@ async def _start_service_locally(request: Request) -> JSONResponse: def _fn() -> None: manager.deploy_service_onchain(hash=service) - # manager.stake_service_on_chain(hash=service) + manager.stake_service_on_chain(hash=service) manager.fund_service(hash=service) manager.deploy_service_locally(hash=service, force=True) diff --git a/operate/services/manage.py b/operate/services/manage.py index 6acf0b462..076701949 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -23,6 +23,7 @@ import logging import os import shutil +import time import traceback import typing as t from collections import Counter @@ -115,21 +116,26 @@ def json(self) -> t.List[t.Dict]: self.logger.warning( f"Failed to load service: {path.name}. Exception: {e}" ) - # delete the invalid path - shutil.rmtree(path) - self.logger.info(f"Deleted invalid service: {path.name}") + # rename the invalid path + timestamp = int(time.time()) + invalid_path = path.parent / f"invalid_{timestamp}_{path.name}" + os.rename(path, invalid_path) + self.logger.info( + f"Renamed invalid service: {path.name} to {invalid_path.name}" + ) + return data def exists(self, service: str) -> bool: """Check if service exists.""" return (self.path / service).exists() - def get_on_chain_manager(self, service: Service) -> OnChainManager: + def get_on_chain_manager(self, ledger_config: LedgerConfig) -> OnChainManager: """Get OnChainManager instance.""" return OnChainManager( - rpc=service.ledger_config.rpc, - wallet=self.wallet_manager.load(service.ledger_config.type), - contracts=CONTRACTS[service.ledger_config.chain], + rpc=ledger_config.rpc, + wallet=self.wallet_manager.load(ledger_config.type), + contracts=CONTRACTS[ledger_config.chain], ) def get_eth_safe_tx_builder(self, ledger_config: LedgerConfig) -> EthSafeTxBuilder: @@ -159,7 +165,9 @@ def load_or_create( service = Service.load(path=path) if service_template is not None: - service.update_user_params_from_template(service_template=service_template) + service.update_user_params_from_template( + service_template=service_template + ) return service @@ -184,22 +192,21 @@ def load_or_create( return service - def _get_on_chain_state(self, chain_config: ChainConfig) -> OnChainState: + def _get_on_chain_state(self, service: Service, chain_id: str) -> OnChainState: + chain_config = service.chain_configs[chain_id] chain_data = chain_config.chain_data ledger_config = chain_config.ledger_config if chain_data.token == NON_EXISTENT_TOKEN: service_state = OnChainState.NON_EXISTENT chain_data.on_chain_state = service_state - # TODO save service state - # service.store() + service.store() return service_state sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config) info = sftxb.info(token_id=chain_data.token) service_state = OnChainState(info["service_state"]) chain_data.on_chain_state = service_state - # TODO save service state - # service.store() + service.store() return service_state def _get_on_chain_hash(self, chain_config: ChainConfig) -> t.Optional[str]: @@ -218,10 +225,28 @@ def _get_on_chain_hash(self, chain_config: ChainConfig) -> t.Optional[str]: f"Something went wrong while trying to get the code uri from IPFS: {res}" ) - def deploy_service_onchain( # pylint: disable=too-many-statements + def deploy_service_onchain( # pylint: disable=too-many-statements,too-many-locals self, hash: str, - update: bool = False, + ) -> None: + """ + Deploy as service on-chain + + :param hash: Service hash + """ + # TODO This method has not been thoroughly reviewed. Deprecated usage in favour of Safe version. + + service = self.load_or_create(hash=hash) + for chain_id in service.chain_configs.keys(): + self._deploy_service_onchain( + hash=hash, + chain_id=chain_id, + ) + + def _deploy_service_onchain( # pylint: disable=too-many-statements,too-many-locals + self, + hash: str, + chain_id: str, ) -> None: """ Deploy as service on-chain @@ -229,49 +254,73 @@ def deploy_service_onchain( # pylint: disable=too-many-statements :param hash: Service hash :param update: Update the existing deployment """ - self.logger.info("Loading service") + # TODO This method has not been thoroughly reviewed. Deprecated usage in favour of Safe version. + + self.logger.info(f"_deploy_service_onchain_from_safe {chain_id=}") service = self.load_or_create(hash=hash) - user_params = service.chain_data.user_params + chain_config = service.chain_configs[chain_id] + ledger_config = chain_config.ledger_config + chain_data = chain_config.chain_data + user_params = chain_config.chain_data.user_params keys = service.keys instances = [key.address for key in keys] - ocm = self.get_on_chain_manager(service=service) - if user_params.use_staking and not ocm.staking_slots_available( - staking_contract=STAKING[service.ledger_config.chain] - ): - raise ValueError("No staking slots available") + ocm = self.get_on_chain_manager(ledger_config=ledger_config) - if user_params.use_staking and not ocm.staking_rewards_available( - staking_contract=STAKING[service.ledger_config.chain] - ): - raise ValueError("No staking rewards available") + # TODO fix this + os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc + os.environ[ + "OPEN_AUTONOMY_SUBGRAPH_URL" + ] = "https://subgraph.autonolas.tech/subgraphs/name/autonolas-staging" - if service.chain_data.token > -1: + current_agent_id = None + if chain_data.token > -1: self.logger.info("Syncing service state") - info = ocm.info(token_id=service.chain_data.token) - service.chain_data.on_chain_state = OnChainState(info["service_state"]) - service.chain_data.instances = info["instances"] - service.chain_data.multisig = info["multisig"] + info = ocm.info(token_id=chain_data.token) + chain_data.on_chain_state = OnChainState(info["service_state"]) + chain_data.instances = info["instances"] + chain_data.multisig = info["multisig"] service.store() - self.logger.info(f"Service state: {service.chain_data.on_chain_state.name}") + self.logger.info(f"Service state: {chain_data.on_chain_state.name}") + + if user_params.use_staking: + staking_params = ocm.get_staking_params( + staking_contract=STAKING[ledger_config.chain][ + user_params.staking_program_id + ], + ) + else: # TODO fix this - using pearl beta params + staking_params = dict( # nosec + agent_ids=[25], + service_registry="0x9338b5153AE39BB89f50468E608eD9d764B755fD", # nosec + staking_token="0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f", # nosec + service_registry_token_utility="0xa45E64d13A30a51b91ae0eb182e88a40e9b18eD8", # nosec + min_staking_deposit=20000000000000000000, + activity_checker="0x155547857680A6D51bebC5603397488988DEb1c8", # nosec + ) if user_params.use_staking: self.logger.info("Checking staking compatibility") - if service.chain_data.on_chain_state in ( + + # TODO: Missing check when the service is currently staked in a program, but needs to be staked + # in a different target program. The In this case, balance = currently staked balance + safe balance + + if chain_data.on_chain_state in ( OnChainState.NON_EXISTENT, OnChainState.PRE_REGISTRATION, ): required_olas = ( - user_params.olas_cost_of_bond + user_params.olas_required_to_stake + staking_params["min_staking_deposit"] + + staking_params["min_staking_deposit"] # bond = staking ) - elif service.chain_data.on_chain_state == OnChainState.ACTIVE_REGISTRATION: - required_olas = user_params.olas_required_to_stake + elif chain_data.on_chain_state == OnChainState.ACTIVE_REGISTRATION: + required_olas = staking_params["min_staking_deposit"] else: required_olas = 0 balance = ( registry_contracts.erc20.get_instance( ledger_api=ocm.ledger_api, - contract_address=OLAS[service.ledger_config.chain], + contract_address=OLAS[ledger_config.chain], ) .functions.balanceOf(ocm.crypto.address) .call() @@ -282,91 +331,108 @@ def deploy_service_onchain( # pylint: disable=too-many-statements f"required olas: {required_olas}; your balance {balance}" ) - if service.chain_data.on_chain_state == OnChainState.NON_EXISTENT: + on_chain_hash = self._get_on_chain_hash(chain_config=chain_config) + current_agent_bond = staking_params[ + "min_staking_deposit" + ] # TODO fixme, read from service registry token utility contract + is_first_mint = ( + self._get_on_chain_state(service=service, chain_id=chain_id) + == OnChainState.NON_EXISTENT + ) + is_update = ( + (not is_first_mint) + and (on_chain_hash is not None) + and ( + on_chain_hash != service.hash + or current_agent_id != staking_params["agent_ids"][0] + or current_agent_bond != staking_params["min_staking_deposit"] + ) + ) + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, ocm # type: ignore # FIXME + ) + + self.logger.info(f"{current_staking_program=}") + self.logger.info(f"{user_params.staking_program_id=}") + self.logger.info(f"{on_chain_hash=}") + self.logger.info(f"{service.hash=}") + self.logger.info(f"{current_agent_id=}") + self.logger.info(f"{staking_params['agent_ids'][0]=}") + self.logger.info(f"{is_first_mint=}") + self.logger.info(f"{is_update=}") + + if chain_data.on_chain_state == OnChainState.NON_EXISTENT: self.logger.info("Minting service") - service.chain_data.token = t.cast( + chain_data.token = t.cast( int, ocm.mint( package_path=service.service_path, - agent_id=user_params.agent_id, + agent_id=staking_params["agent_ids"][0], number_of_slots=service.helper.config.number_of_agents, cost_of_bond=( - user_params.olas_cost_of_bond + staking_params["min_staking_deposit"] if user_params.use_staking else user_params.cost_of_bond ), threshold=user_params.threshold, nft=IPFSHash(user_params.nft), - update_token=service.chain_data.token if update else None, + update_token=chain_data.token if is_update else None, token=( - OLAS[service.ledger_config.chain] - if user_params.use_staking - else None + OLAS[ledger_config.chain] if user_params.use_staking else None ), ).get("token"), ) - service.chain_data.on_chain_state = OnChainState.PRE_REGISTRATION + chain_data.on_chain_state = OnChainState.PRE_REGISTRATION service.store() - info = ocm.info(token_id=service.chain_data.token) - service.chain_data.on_chain_state = OnChainState(info["service_state"]) + info = ocm.info(token_id=chain_data.token) + chain_data.on_chain_state = OnChainState(info["service_state"]) - if service.chain_data.on_chain_state == OnChainState.PRE_REGISTRATION: + if chain_data.on_chain_state == OnChainState.PRE_REGISTRATION: self.logger.info("Activating service") ocm.activate( - service_id=service.chain_data.token, - token=( - OLAS[service.ledger_config.chain] - if user_params.use_staking - else None - ), + service_id=chain_data.token, + token=(OLAS[ledger_config.chain] if user_params.use_staking else None), ) - service.chain_data.on_chain_state = OnChainState.ACTIVE_REGISTRATION + chain_data.on_chain_state = OnChainState.ACTIVE_REGISTRATION service.store() - info = ocm.info(token_id=service.chain_data.token) - service.chain_data.on_chain_state = OnChainState(info["service_state"]) + info = ocm.info(token_id=chain_data.token) + chain_data.on_chain_state = OnChainState(info["service_state"]) - if service.chain_data.on_chain_state == OnChainState.ACTIVE_REGISTRATION: + if chain_data.on_chain_state == OnChainState.ACTIVE_REGISTRATION: self.logger.info("Registering agent instances") + agent_id = staking_params["agent_ids"][0] ocm.register( - service_id=service.chain_data.token, + service_id=chain_data.token, instances=instances, - agents=[user_params.agent_id for _ in instances], - token=( - OLAS[service.ledger_config.chain] - if user_params.use_staking - else None - ), + agents=[agent_id for _ in instances], + token=(OLAS[ledger_config.chain] if user_params.use_staking else None), ) - service.chain_data.on_chain_state = OnChainState.FINISHED_REGISTRATION + chain_data.on_chain_state = OnChainState.FINISHED_REGISTRATION service.store() - info = ocm.info(token_id=service.chain_data.token) - service.chain_data.on_chain_state = OnChainState(info["service_state"]) + info = ocm.info(token_id=chain_data.token) + chain_data.on_chain_state = OnChainState(info["service_state"]) - if service.chain_data.on_chain_state == OnChainState.FINISHED_REGISTRATION: + if chain_data.on_chain_state == OnChainState.FINISHED_REGISTRATION: self.logger.info("Deploying service") ocm.deploy( - service_id=service.chain_data.token, - reuse_multisig=update, - token=( - OLAS[service.ledger_config.chain] - if user_params.use_staking - else None - ), + service_id=chain_data.token, + reuse_multisig=is_update, + token=(OLAS[ledger_config.chain] if user_params.use_staking else None), ) - service.chain_data.on_chain_state = OnChainState.DEPLOYED + chain_data.on_chain_state = OnChainState.DEPLOYED service.store() - info = ocm.info(token_id=service.chain_data.token) - service.chain_data = OnChainData( - token=service.chain_data.token, + info = ocm.info(token_id=chain_data.token) + chain_data = OnChainData( + token=chain_data.token, instances=info["instances"], multisig=info["multisig"], staked=False, - on_chain_state=service.chain_data.on_chain_state, - user_params=service.chain_data.user_params, + on_chain_state=chain_data.on_chain_state, + user_params=chain_data.user_params, ) service.store() @@ -410,7 +476,9 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to # TODO fix this os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc - os.environ["OPEN_AUTONOMY_SUBGRAPH_URL"] = "https://subgraph.autonolas.tech/subgraphs/name/autonolas-staging" + os.environ[ + "OPEN_AUTONOMY_SUBGRAPH_URL" + ] = "https://subgraph.autonolas.tech/subgraphs/name/autonolas-staging" current_agent_id = None if chain_data.token > -1: @@ -425,16 +493,18 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to if user_params.use_staking: staking_params = sftxb.get_staking_params( - staking_contract=STAKING[ledger_config.chain][user_params.staking_program_id], + staking_contract=STAKING[ledger_config.chain][ + user_params.staking_program_id + ], ) else: # TODO fix this - using pearl beta params - staking_params = dict( + staking_params = dict( # nosec agent_ids=[25], service_registry="0x9338b5153AE39BB89f50468E608eD9d764B755fD", # nosec staking_token="0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f", # nosec service_registry_token_utility="0xa45E64d13A30a51b91ae0eb182e88a40e9b18eD8", # nosec min_staking_deposit=20000000000000000000, - activity_checker="0x155547857680A6D51bebC5603397488988DEb1c8" # nosec + activity_checker="0x155547857680A6D51bebC5603397488988DEb1c8", # nosec ) if user_params.use_staking: @@ -448,7 +518,8 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to OnChainState.PRE_REGISTRATION, ): required_olas = ( - staking_params["min_staking_deposit"] + staking_params["min_staking_deposit"] # bond = staking + staking_params["min_staking_deposit"] + + staking_params["min_staking_deposit"] # bond = staking ) elif chain_data.on_chain_state == OnChainState.ACTIVE_REGISTRATION: required_olas = staking_params["min_staking_deposit"] @@ -470,13 +541,25 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to ) on_chain_hash = self._get_on_chain_hash(chain_config=chain_config) - is_first_mint = self._get_on_chain_state(chain_config=chain_config) == OnChainState.NON_EXISTENT + current_agent_bond = staking_params[ + "min_staking_deposit" + ] # TODO fixme, read from service registry token utility contract + is_first_mint = ( + self._get_on_chain_state(service=service, chain_id=chain_id) + == OnChainState.NON_EXISTENT + ) is_update = ( (not is_first_mint) and (on_chain_hash is not None) - and (on_chain_hash != service.hash or current_agent_id != staking_params["agent_ids"][0]) + and ( + on_chain_hash != service.hash + or current_agent_id != staking_params["agent_ids"][0] + or current_agent_bond != staking_params["min_staking_deposit"] + ) + ) + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, sftxb ) - current_staking_program = self._get_current_staking_program(chain_data, ledger_config, sftxb) self.logger.info(f"{current_staking_program=}") self.logger.info(f"{user_params.staking_program_id=}") @@ -488,13 +571,13 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to self.logger.info(f"{is_update=}") if is_update: - self._terminate_service_on_chain_from_safe( - hash=hash, - chain_id=chain_id - ) + self._terminate_service_on_chain_from_safe(hash=hash, chain_id=chain_id) # Update service - if self._get_on_chain_state(chain_config=chain_config) == OnChainState.PRE_REGISTRATION: + if ( + self._get_on_chain_state(service=service, chain_id=chain_id) + == OnChainState.PRE_REGISTRATION + ): self.logger.info("Updating service") receipt = ( sftxb.new_tx() @@ -533,10 +616,14 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to service.store() # Mint service - if self._get_on_chain_state(chain_config=chain_config) == OnChainState.NON_EXISTENT: - + if ( + self._get_on_chain_state(service=service, chain_id=chain_id) + == OnChainState.NON_EXISTENT + ): if user_params.use_staking and not sftxb.staking_slots_available( - staking_contract=STAKING[ledger_config.chain][user_params.staking_program_id] + staking_contract=STAKING[ledger_config.chain][ + user_params.staking_program_id + ] ): raise ValueError("No staking slots available") @@ -578,7 +665,10 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to chain_data.on_chain_state = OnChainState.PRE_REGISTRATION service.store() - if self._get_on_chain_state(chain_config=chain_config) == OnChainState.PRE_REGISTRATION: + if ( + self._get_on_chain_state(service=service, chain_id=chain_id) + == OnChainState.PRE_REGISTRATION + ): cost_of_bond = staking_params["min_staking_deposit"] if user_params.use_staking: token_utility = staking_params["service_registry_token_utility"] @@ -628,7 +718,10 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to chain_data.on_chain_state = OnChainState.ACTIVE_REGISTRATION service.store() - if self._get_on_chain_state(chain_config=chain_config) == OnChainState.ACTIVE_REGISTRATION: + if ( + self._get_on_chain_state(service=service, chain_id=chain_id) + == OnChainState.ACTIVE_REGISTRATION + ): cost_of_bond = user_params.cost_of_bond if user_params.use_staking: token_utility = staking_params["service_registry_token_utility"] @@ -682,7 +775,10 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to chain_data.on_chain_state = OnChainState.FINISHED_REGISTRATION service.store() - if self._get_on_chain_state(chain_config=chain_config) == OnChainState.FINISHED_REGISTRATION: + if ( + self._get_on_chain_state(service=service, chain_id=chain_id) + == OnChainState.FINISHED_REGISTRATION + ): self.logger.info("Deploying service") reuse_multisig = True @@ -695,7 +791,7 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to messages = sftxb.get_deploy_data_from_safe( service_id=chain_data.token, reuse_multisig=reuse_multisig, - master_safe=sftxb.wallet.safe, + master_safe=sftxb.wallet.safe, # type: ignore # TODO fix mypy ) tx = sftxb.new_tx() for message in messages: @@ -713,34 +809,48 @@ def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,to service.store() self.stake_service_on_chain_from_safe(hash=hash, chain_id=chain_id) - def terminate_service_on_chain(self, hash: str) -> None: + def terminate_service_on_chain( + self, hash: str, chain_id: t.Optional[str] = None + ) -> None: """ Terminate service on-chain :param hash: Service hash """ + # TODO This method has not been thoroughly reviewed. Deprecated usage in favour of Safe version. + + self.logger.info("terminate_service_on_chain") service = self.load_or_create(hash=hash) - ocm = self.get_on_chain_manager(service=service) - info = ocm.info(token_id=service.chain_data.token) - service.chain_data.on_chain_state = OnChainState(info["service_state"]) - if service.chain_data.on_chain_state != OnChainState.DEPLOYED: + if chain_id is None: + chain_id = service.home_chain_id + + chain_config = service.chain_configs[chain_id] + ledger_config = chain_config.ledger_config + chain_data = chain_config.chain_data + ocm = self.get_on_chain_manager(ledger_config=ledger_config) + info = ocm.info(token_id=chain_data.token) + chain_data.on_chain_state = OnChainState(info["service_state"]) + + if chain_data.on_chain_state != OnChainState.DEPLOYED: self.logger.info("Cannot terminate service") return self.logger.info("Terminating service") ocm.terminate( - service_id=service.chain_data.token, + service_id=chain_data.token, token=( - OLAS[service.ledger_config.chain] - if service.chain_data.user_params.use_staking + OLAS[ledger_config.chain] + if chain_data.user_params.use_staking else None ), ) - service.chain_data.on_chain_state = OnChainState.TERMINATED_BONDED + chain_data.on_chain_state = OnChainState.TERMINATED_BONDED service.store() - def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: + def _terminate_service_on_chain_from_safe( # pylint: disable=too-many-locals + self, hash: str, chain_id: str + ) -> None: """ Terminate service on-chain @@ -763,7 +873,9 @@ def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> Non chain_data.on_chain_state = OnChainState(info["service_state"]) # Determine if the service is staked in a known staking program - current_staking_program = self._get_current_staking_program(chain_data, ledger_config, sftxb) + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, sftxb + ) is_staked = current_staking_program is not None can_unstake = False @@ -780,9 +892,11 @@ def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> Non # Unstake the service if applies if is_staked and can_unstake: - self.unstake_service_on_chain_from_safe(hash=hash, chain_id=chain_id, staking_program_id=current_staking_program) + self.unstake_service_on_chain_from_safe( + hash=hash, chain_id=chain_id, staking_program_id=current_staking_program + ) - if self._get_on_chain_state(chain_config) in ( + if self._get_on_chain_state(service=service, chain_id=chain_id) in ( OnChainState.ACTIVE_REGISTRATION, OnChainState.FINISHED_REGISTRATION, OnChainState.DEPLOYED, @@ -794,7 +908,10 @@ def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> Non ) ).settle() - if self._get_on_chain_state(chain_config) == OnChainState.TERMINATED_BONDED: + if ( + self._get_on_chain_state(service=service, chain_id=chain_id) + == OnChainState.TERMINATED_BONDED + ): self.logger.info("Unbonding service") sftxb.new_tx().add( sftxb.get_unbond_data( @@ -808,17 +925,34 @@ def _terminate_service_on_chain_from_safe(self, hash: str, chain_id: str) -> Non counter_instances = Counter(s.lower() for s in instances) if counter_current_safe_owners == counter_instances: + self.logger.info("Service funded for safe swap") + self.fund_service( + hash=hash, + rpc=ledger_config.rpc, + agent_topup=chain_data.user_params.fund_requirements.agent, + agent_fund_threshold=chain_data.user_params.fund_requirements.agent, + safe_topup=0, + safe_fund_treshold=0, + ) + self.logger.info("Swapping Safe owners") sftxb.swap( # noqa: E800 service_id=chain_data.token, # noqa: E800 multisig=chain_data.multisig, # TODO this can be read from the registry owner_key=str( - self.keys_manager.get(key=current_safe_owners[0]).private_key # TODO allow multiple owners + self.keys_manager.get( + key=current_safe_owners[0] + ).private_key # TODO allow multiple owners ), # noqa: E800 - new_owner_address=wallet.safe if wallet.safe else wallet.crypto.address # TODO it should always be safe address + new_owner_address=wallet.safe + if wallet.safe + else wallet.crypto.address, # TODO it should always be safe address ) # noqa: E800 - def _get_current_staking_program(self, chain_data, ledger_config, sftxb) -> t.Optional[str]: + @staticmethod + def _get_current_staking_program( + chain_data: OnChainData, ledger_config: LedgerConfig, sftxb: EthSafeTxBuilder + ) -> t.Optional[str]: if chain_data.token == NON_EXISTENT_TOKEN: return None @@ -832,34 +966,45 @@ def _get_current_staking_program(self, chain_data, ledger_config, sftxb) -> t.Op current_staking_program = staking_program return current_staking_program - def unbond_service_on_chain(self, hash: str) -> None: + def unbond_service_on_chain( + self, hash: str, chain_id: t.Optional[str] = None + ) -> None: """ Unbond service on-chain :param hash: Service hash """ + # TODO This method has not been thoroughly reviewed. Deprecated usage in favour of Safe version. + service = self.load_or_create(hash=hash) - ocm = self.get_on_chain_manager(service=service) - info = ocm.info(token_id=service.chain_data.token) - service.chain_data.on_chain_state = OnChainState(info["service_state"]) - if service.chain_data.on_chain_state != OnChainState.TERMINATED_BONDED: + if chain_id is None: + chain_id = service.home_chain_id + + chain_config = service.chain_configs[chain_id] + ledger_config = chain_config.ledger_config + chain_data = chain_config.chain_data + ocm = self.get_on_chain_manager(ledger_config=ledger_config) + info = ocm.info(token_id=chain_data.token) + chain_data.on_chain_state = OnChainState(info["service_state"]) + + if chain_data.on_chain_state != OnChainState.TERMINATED_BONDED: self.logger.info("Cannot unbond service") return self.logger.info("Unbonding service") ocm.unbond( - service_id=service.chain_data.token, + service_id=chain_data.token, token=( - OLAS[service.ledger_config.chain] - if service.chain_data.user_params.use_staking + OLAS[ledger_config.chain] + if chain_data.user_params.use_staking else None ), ) - service.chain_data.on_chain_state = OnChainState.UNBONDED + chain_data.on_chain_state = OnChainState.UNBONDED service.store() - def stake_service_on_chain(self, hash: str, chain_id: int, staking_program_id: str) -> None: + def stake_service_on_chain(self, hash: str) -> None: """ Stake service on-chain @@ -867,7 +1012,9 @@ def stake_service_on_chain(self, hash: str, chain_id: int, staking_program_id: s """ raise NotImplementedError - def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: + def stake_service_on_chain_from_safe( # pylint: disable=too-many-statements,too-many-locals + self, hash: str, chain_id: str + ) -> None: """ Stake service on-chain @@ -888,40 +1035,76 @@ def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc # Determine if the service is staked in a known staking program - current_staking_program = self._get_current_staking_program(chain_data, ledger_config, sftxb) + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, sftxb + ) is_staked = current_staking_program is not None - current_staking_contract = STAKING[ledger_config.chain][current_staking_program] if is_staked else None + current_staking_contract = ( + STAKING[ledger_config.chain][current_staking_program] + if current_staking_program is not None + else None + ) # perform the unstaking flow if necessary if is_staked: - can_unstake = sftxb.can_unstake(chain_config.chain_data.token, current_staking_contract) + can_unstake = sftxb.can_unstake( + chain_config.chain_data.token, current_staking_contract # type: ignore # TODO fix mypy + ) if not chain_config.chain_data.user_params.use_staking and can_unstake: - self.logger.info(f"Use staking is set to false, but service {chain_config.chain_data.token} is staked and can be unstaked. Unstaking...") - self.unstake_service_on_chain_from_safe(hash=hash, chain_id=chain_id, staking_program_id=current_staking_program) + self.logger.info( + f"Use staking is set to false, but service {chain_config.chain_data.token} is staked and can be unstaked. Unstaking..." + ) + self.unstake_service_on_chain_from_safe( + hash=hash, + chain_id=chain_id, + staking_program_id=current_staking_program, + ) info = sftxb.info(token_id=chain_config.chain_data.token) chain_config.chain_data.on_chain_state = OnChainState(info["service_state"]) staking_state = sftxb.staking_status( service_id=chain_data.token, - staking_contract=current_staking_contract, + staking_contract=current_staking_contract, # type: ignore # TODO fix mypy ) if staking_state == StakingState.EVICTED and can_unstake: - self.logger.info(f"Service {chain_config.chain_data.token} has been evicted and can be unstaked. Unstaking...") - self.unstake_service_on_chain_from_safe(hash=hash, chain_id=chain_id, staking_program_id=current_staking_program) + self.logger.info( + f"Service {chain_config.chain_data.token} has been evicted and can be unstaked. Unstaking..." + ) + self.unstake_service_on_chain_from_safe( + hash=hash, + chain_id=chain_id, + staking_program_id=current_staking_program, + ) - if staking_state == StakingState.STAKED and can_unstake and not sftxb.staking_rewards_available(current_staking_contract): + if ( + staking_state == StakingState.STAKED + and can_unstake + and not sftxb.staking_rewards_available(current_staking_contract) # type: ignore # TODO fix mypy + ): self.logger.info( f"There are no rewards available, service {chain_config.chain_data.token} " f"is already staked and can be unstaked. Unstaking..." ) - self.unstake_service_on_chain_from_safe(hash=hash, chain_id=chain_id, staking_program_id=current_staking_program) + self.unstake_service_on_chain_from_safe( + hash=hash, + chain_id=chain_id, + staking_program_id=current_staking_program, + ) - if staking_state == StakingState.STAKED and current_staking_program != target_staking_contract and can_unstake: + if ( + staking_state == StakingState.STAKED + and current_staking_program != target_staking_contract + and can_unstake + ): self.logger.info( f"{chain_config.chain_data.token} is staked in a different staking program. Unstaking..." ) - self.unstake_service_on_chain_from_safe(hash=hash, chain_id=chain_id, staking_program_id=current_staking_program) + self.unstake_service_on_chain_from_safe( + hash=hash, + chain_id=chain_id, + staking_program_id=current_staking_program, + ) staking_state = sftxb.staking_status( service_id=chain_config.chain_data.token, @@ -929,12 +1112,18 @@ def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: ) self.logger.info("Checking conditions to stake.") - staking_rewards_available = sftxb.staking_rewards_available(target_staking_contract) + staking_rewards_available = sftxb.staking_rewards_available( + target_staking_contract + ) staking_slots_available = sftxb.staking_slots_available(target_staking_contract) - on_chain_state = self._get_on_chain_state(chain_config=chain_config) - current_staking_program = self._get_current_staking_program(chain_data, ledger_config, sftxb) - - self.logger.info(f"use_staking={chain_config.chain_data.user_params.use_staking}") + on_chain_state = self._get_on_chain_state(service=service, chain_id=chain_id) + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, sftxb + ) + + self.logger.info( + f"use_staking={chain_config.chain_data.user_params.use_staking}" + ) self.logger.info(f"{staking_state=}") self.logger.info(f"{staking_rewards_available=}") self.logger.info(f"{staking_slots_available=}") @@ -943,19 +1132,17 @@ def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: self.logger.info(f"{target_staking_program=}") if ( - chain_config.chain_data.user_params.use_staking - and staking_state == StakingState.UNSTAKED - and staking_rewards_available - and staking_slots_available - and on_chain_state == OnChainState.DEPLOYED + chain_config.chain_data.user_params.use_staking + and staking_state == StakingState.UNSTAKED + and staking_rewards_available + and staking_slots_available + and on_chain_state == OnChainState.DEPLOYED ): self.logger.info(f"Approving staking: {chain_config.chain_data.token}") sftxb.new_tx().add( sftxb.get_staking_approval_data( service_id=chain_config.chain_data.token, - service_registry=CONTRACTS[ledger_config.chain][ - "service_registry" - ], + service_registry=CONTRACTS[ledger_config.chain]["service_registry"], staking_contract=target_staking_contract, ) ).settle() @@ -970,44 +1157,57 @@ def stake_service_on_chain_from_safe(self, hash: str, chain_id: str) -> None: chain_config.chain_data.staked = True service.store() - current_staking_program = self._get_current_staking_program(chain_data, ledger_config, sftxb) + current_staking_program = self._get_current_staking_program( + chain_data, ledger_config, sftxb + ) self.logger.info(f"{target_staking_program=}") self.logger.info(f"{current_staking_program=}") - def unstake_service_on_chain(self, hash: str) -> None: + def unstake_service_on_chain( + self, hash: str, chain_id: t.Optional[str] = None + ) -> None: """ Unbond service on-chain :param hash: Service hash """ + # TODO This method has not been thoroughly reviewed. Deprecated usage in favour of Safe version. + service = self.load_or_create(hash=hash) - if not service.chain_data.user_params.use_staking: + + if chain_id is None: + chain_id = service.home_chain_id + + chain_config = service.chain_configs[chain_id] + ledger_config = chain_config.ledger_config + chain_data = chain_config.chain_data + ocm = self.get_on_chain_manager(ledger_config=ledger_config) + if not chain_data.user_params.use_staking: self.logger.info("Cannot unstake service, `use_staking` is set to false") return - ocm = self.get_on_chain_manager(service=service) state = ocm.staking_status( - service_id=service.chain_data.token, - staking_contract=STAKING[service.ledger_config.chain], - ) - self.logger.info( - f"Staking status for service {service.chain_data.token}: {state}" + service_id=chain_data.token, + staking_contract=STAKING[ledger_config.chain], # type: ignore # TODO fix mypy ) + self.logger.info(f"Staking status for service {chain_data.token}: {state}") if state not in {StakingState.STAKED, StakingState.EVICTED}: self.logger.info("Cannot unstake service, it's not staked") - service.chain_data.staked = False + chain_data.staked = False service.store() return - self.logger.info(f"Unstaking service: {service.chain_data.token}") + self.logger.info(f"Unstaking service: {chain_data.token}") ocm.unstake( - service_id=service.chain_data.token, - staking_contract=STAKING[service.ledger_config.chain], + service_id=chain_data.token, + staking_contract=STAKING[ledger_config.chain], # type: ignore # TODO fix mypy ) - service.chain_data.staked = False + chain_data.staked = False service.store() - def unstake_service_on_chain_from_safe(self, hash: str, chain_id: str, staking_program_id: str) -> None: + def unstake_service_on_chain_from_safe( + self, hash: str, chain_id: str, staking_program_id: t.Optional[str] = None + ) -> None: """ Unbond service on-chain @@ -1020,6 +1220,12 @@ def unstake_service_on_chain_from_safe(self, hash: str, chain_id: str, staking_p ledger_config = chain_config.ledger_config chain_data = chain_config.chain_data + if staking_program_id is None: + self.logger.info( + "Cannot unstake service, `staking_program_id` is set to None" + ) + return + if not chain_data.user_params.use_staking: self.logger.info("Cannot unstake service, `use_staking` is set to false") return @@ -1029,9 +1235,7 @@ def unstake_service_on_chain_from_safe(self, hash: str, chain_id: str, staking_p service_id=chain_data.token, staking_contract=STAKING[ledger_config.chain][staking_program_id], ) - self.logger.info( - f"Staking status for service {chain_data.token}: {state}" - ) + self.logger.info(f"Staking status for service {chain_data.token}: {state}") if state not in {StakingState.STAKED, StakingState.EVICTED}: self.logger.info("Cannot unstake service, it's not staked") chain_data.staked = False @@ -1048,7 +1252,7 @@ def unstake_service_on_chain_from_safe(self, hash: str, chain_id: str, staking_p chain_data.staked = False service.store() - def fund_service( # pylint: disable=too-many-arguments + def fund_service( # pylint: disable=too-many-arguments,too-many-locals self, hash: str, rpc: t.Optional[str] = None, @@ -1065,10 +1269,11 @@ def fund_service( # pylint: disable=too-many-arguments ledger_config = chain_config.ledger_config chain_data = chain_config.chain_data wallet = self.wallet_manager.load(ledger_config.type) - ledger_api = wallet.ledger_api(chain_type=ledger_config.chain, rpc=rpc if rpc else ledger_config.rpc) + ledger_api = wallet.ledger_api( + chain_type=ledger_config.chain, rpc=rpc if rpc else ledger_config.rpc + ) agent_fund_threshold = ( - agent_fund_threshold - or chain_data.user_params.fund_requirements.agent + agent_fund_threshold or chain_data.user_params.fund_requirements.agent ) for key in service.keys: @@ -1078,8 +1283,7 @@ def fund_service( # pylint: disable=too-many-arguments if agent_balance < agent_fund_threshold: self.logger.info("Funding agents") to_transfer = ( - agent_topup - or chain_data.user_params.fund_requirements.agent + agent_topup or chain_data.user_params.fund_requirements.agent ) self.logger.info(f"Transferring {to_transfer} units to {key.address}") wallet.transfer( @@ -1097,9 +1301,7 @@ def fund_service( # pylint: disable=too-many-arguments self.logger.info(f"Required balance: {safe_fund_treshold}") if safe_balance < safe_fund_treshold: self.logger.info("Funding safe") - to_transfer = ( - safe_topup or chain_data.user_params.fund_requirements.safe - ) + to_transfer = safe_topup or chain_data.user_params.fund_requirements.safe self.logger.info( f"Transferring {to_transfer} units to {chain_data.multisig}" ) @@ -1177,25 +1379,28 @@ def update_service( """Update a service.""" self.logger.info("-----Entering update local service-----") - old_service = self.load_or_create( - hash=old_hash - ) + old_service = self.load_or_create(hash=old_hash) new_service = self.load_or_create( - hash=new_hash, - service_template=service_template + hash=new_hash, service_template=service_template ) new_service.keys = old_service.keys - # new_Service.home_chain_id = old_service.home_chain_id - # TODO - Ensure this works as expected - New service must copy all chain_data from old service, - # but if service_template is not None, it must copy the user_params - # passed in the service_template and copy the remaining attributes from old_service. + # TODO Ensure this is as intended. + new_service.home_chain_id = old_service.home_chain_id + # new_service must copy all chain_data from old_service. + # Additionally, if service_template is not None, it must overwrite + # the user_params on all chain_data by the values passed through the + # service_template. new_service.chain_configs = {} for chain_id, config in old_service.chain_configs.items(): - new_service.chain_configs[chain_id] = config + new_service.chain_configs[chain_id] = config if service_template: - new_service.chain_configs[chain_id].chain_data.user_params = OnChainUserParams.from_json(service_template["configurations"][chain_id]) + new_service.chain_configs[ + chain_id + ].chain_data.user_params = OnChainUserParams.from_json( + service_template["configurations"][chain_id] # type: ignore # TODO fix mypy + ) new_service.store() diff --git a/operate/services/protocol.py b/operate/services/protocol.py index 120355c94..5b122bb7e 100644 --- a/operate/services/protocol.py +++ b/operate/services/protocol.py @@ -242,8 +242,9 @@ def service_info(self, staking_contract: str, service_id: int) -> dict: staking_contract, service_id, ).get("data") - + def agent_ids(self, staking_contract: str) -> t.List[int]: + """Get the agent IDs for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -251,6 +252,7 @@ def agent_ids(self, staking_contract: str) -> t.List[int]: return instance.functions.getAgentIds().call() def service_registry(self, staking_contract: str) -> str: + """Get the service registry address for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -258,6 +260,7 @@ def service_registry(self, staking_contract: str) -> str: return instance.functions.serviceRegistry().call() def staking_token(self, staking_contract: str) -> str: + """Get the staking token address for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -265,6 +268,7 @@ def staking_token(self, staking_contract: str) -> str: return instance.functions.stakingToken().call() def service_registry_token_utility(self, staking_contract: str) -> str: + """Get the service registry token utility address for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -272,6 +276,7 @@ def service_registry_token_utility(self, staking_contract: str) -> str: return instance.functions.serviceRegistryTokenUtility().call() def min_staking_deposit(self, staking_contract: str) -> str: + """Get the minimum staking deposit for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -279,6 +284,7 @@ def min_staking_deposit(self, staking_contract: str) -> str: return instance.functions.minStakingDeposit().call() def activity_checker(self, staking_contract: str) -> str: + """Get the activity checker address for the specified staking contract""" instance = self.staking_ctr.get_instance( ledger_api=self.ledger_api, contract_address=staking_contract, @@ -529,14 +535,6 @@ def ledger_api(self) -> LedgerApi: ) return ledger_api - def owner_of(self, token_id: int) -> str: - """Get owner of a service.""" - self._patch() - ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( - chain_type=self.chain_type - ) - - def info(self, token_id: int) -> t.Dict: """Get service info.""" self._patch() @@ -574,7 +572,6 @@ def info(self, token_id: int) -> t.Dict: instances=instances, ) - def get_service_safe_owners(self, service_id: int) -> t.List[str]: """Get list of owners.""" ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects( @@ -599,13 +596,8 @@ def get_service_safe_owners(self, service_id: int) -> t.List[str]: contract_address=multisig_address, ).get("owners", []) - def swap( # pylint: disable=too-many-arguments,too-many-locals - self, - service_id: int, - multisig: str, - owner_key: str, - new_owner_address: str + self, service_id: int, multisig: str, owner_key: str, new_owner_address: str ) -> None: """Swap safe owner.""" logging.info(f"Swapping safe for service {service_id} [{multisig}]...") @@ -634,9 +626,7 @@ def swap( # pylint: disable=too-many-arguments,too-many-locals ledger_api=manager.ledger_api, contract_address=multisig, old_owner=manager.ledger_api.api.to_checksum_address(owner_to_swap), - new_owner=manager.ledger_api.api.to_checksum_address( - new_owner_address - ), + new_owner=manager.ledger_api.api.to_checksum_address(new_owner_address), ).get("data") multisend_txs.append( { @@ -720,6 +710,55 @@ def staking_rewards_available(self, staking_contract: str) -> bool: ) return available_rewards > 0 + def staking_status(self, service_id: int, staking_contract: str) -> StakingState: + """Stake the service""" + self._patch() + return StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ).status( + service_id=service_id, + staking_contract=staking_contract, + ) + + def get_staking_params(self, staking_contract: str) -> t.Dict: + """Get agent IDs for the staking contract""" + self._patch() + staking_manager = StakingManager( + key=self.wallet.key_path, + password=self.wallet.password, + chain_type=self.chain_type, + ) + agent_ids = staking_manager.agent_ids( + staking_contract=staking_contract, + ) + service_registry = staking_manager.service_registry( + staking_contract=staking_contract, + ) + staking_token = staking_manager.staking_token( + staking_contract=staking_contract, + ) + service_registry_token_utility = staking_manager.service_registry_token_utility( + staking_contract=staking_contract, + ) + min_staking_deposit = staking_manager.min_staking_deposit( + staking_contract=staking_contract, + ) + activity_checker = staking_manager.activity_checker( + staking_contract=staking_contract, + ) + + return dict( + agent_ids=agent_ids, + service_registry=service_registry, + staking_token=staking_token, + service_registry_token_utility=service_registry_token_utility, + min_staking_deposit=min_staking_deposit, + activity_checker=activity_checker, + ) + + class OnChainManager(_ChainUtil): """On chain service management.""" @@ -974,7 +1013,7 @@ def get_mint_tx_data( # pylint: disable=too-many-arguments ) .load_metadata() .verify_nft(nft=nft) - #.verify_service_dependencies(agent_id=agent_id) # TODO add this check once subgraph production indexes agent 25 + .verify_service_dependencies(agent_id=agent_id) .publish_metadata() ) instance = registry_contracts.service_manager.get_instance( @@ -1003,9 +1042,9 @@ def get_mint_tx_data( # pylint: disable=too-many-arguments [agent_id], [[number_of_slots, cost_of_bond]], threshold, - update_token + update_token, ], - ) + ) return { "to": self.contracts["service_manager"], @@ -1139,7 +1178,11 @@ def get_deploy_data_from_safe( ) approve_hash_message = None if reuse_multisig: - _deployment_payload, approve_hash_message, error = get_reuse_multisig_from_safe_payload( + ( + _deployment_payload, + approve_hash_message, + error, + ) = get_reuse_multisig_from_safe_payload( ledger_api=self.ledger_api, chain_type=self.chain_type, service_id=service_id, @@ -1289,54 +1332,6 @@ def staking_slots_available(self, staking_contract: str) -> bool: staking_contract=staking_contract, ) - def staking_status(self, service_id: int, staking_contract: str) -> StakingState: - """Stake the service""" - self._patch() - return StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ).status( - service_id=service_id, - staking_contract=staking_contract, - ) - - def get_staking_params(self, staking_contract: str) -> t.Dict: - """Get agent IDs for the staking contract""" - self._patch() - staking_manager = StakingManager( - key=self.wallet.key_path, - password=self.wallet.password, - chain_type=self.chain_type, - ) - agent_ids = staking_manager.agent_ids( - staking_contract=staking_contract, - ) - service_registry = staking_manager.service_registry( - staking_contract=staking_contract, - ) - staking_token = staking_manager.staking_token( - staking_contract=staking_contract, - ) - service_registry_token_utility = staking_manager.service_registry_token_utility( - staking_contract=staking_contract, - ) - min_staking_deposit = staking_manager.min_staking_deposit( - staking_contract=staking_contract, - ) - activity_checker = staking_manager.activity_checker( - staking_contract=staking_contract, - ) - - return dict( - agent_ids=agent_ids, - service_registry=service_registry, - staking_token=staking_token, - service_registry_token_utility=service_registry_token_utility, - min_staking_deposit=min_staking_deposit, - activity_checker=activity_checker - ) - def can_unstake(self, service_id: int, staking_contract: str) -> bool: """Can unstake the service?""" try: @@ -1358,26 +1353,25 @@ def get_swap_data(self, service_id: int, multisig: str, owner_key: str) -> t.Dic raise NotImplementedError() - def get_packed_signature_for_approved_hash(owners: t.Tuple[str]) -> bytes: - """Get the packed signatures.""" - sorted_owners = sorted(owners, key=str.lower) - signatures = b'' - for owner in sorted_owners: - # Convert address to bytes and ensure it is 32 bytes long (left-padded with zeros) - r_bytes = to_bytes(hexstr=owner[2:].rjust(64, '0')) + """Get the packed signatures.""" + sorted_owners = sorted(owners, key=str.lower) + signatures = b"" + for owner in sorted_owners: + # Convert address to bytes and ensure it is 32 bytes long (left-padded with zeros) + r_bytes = to_bytes(hexstr=owner[2:].rjust(64, "0")) - # `s` as 32 zero bytes - s_bytes = b'\x00' * 32 + # `s` as 32 zero bytes + s_bytes = b"\x00" * 32 - # `v` as a single byte - v_bytes = to_bytes(1) + # `v` as a single byte + v_bytes = to_bytes(1) - # Concatenate r, s, and v to form the packed signature - packed_signature = r_bytes + s_bytes + v_bytes - signatures += packed_signature + # Concatenate r, s, and v to form the packed signature + packed_signature = r_bytes + s_bytes + v_bytes + signatures += packed_signature - return signatures + return signatures def get_reuse_multisig_from_safe_payload( # pylint: disable=too-many-locals @@ -1477,7 +1471,7 @@ def get_reuse_multisig_from_safe_payload( # pylint: disable=too-many-locals contract_address=multisend_address, txs=txs, ) - signature_bytes = get_packed_signature_for_approved_hash(owners=(master_safe, )) + signature_bytes = get_packed_signature_for_approved_hash(owners=(master_safe,)) safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash( ledger_api=ledger_api, @@ -1517,4 +1511,3 @@ def get_reuse_multisig_from_safe_payload( # pylint: disable=too-many-locals ) payload = multisig_address + safe_exec_data[2:] return payload, approve_hash_message, None - diff --git a/operate/services/service.py b/operate/services/service.py index ea6c563ca..7e115f5ff 100644 --- a/operate/services/service.py +++ b/operate/services/service.py @@ -82,12 +82,13 @@ ) +# pylint: disable=no-member,redefined-builtin,too-many-instance-attributes + SAFE_CONTRACT_ADDRESS = "safe_contract_address" ALL_PARTICIPANTS = "all_participants" CONSENSUS_THRESHOLD = "consensus_threshold" DELETE_PREFIX = "delete_" - -# pylint: disable=no-member,redefined-builtin,too-many-instance-attributes +SERVICE_CONFIG_VERSION = 2 DUMMY_MULTISIG = "0xm" NON_EXISTENT_TOKEN = -1 @@ -238,7 +239,7 @@ def __init__(self, path: Path) -> None: self.path = path self.config = load_service_config(service_path=path) - def ledger_configs(self) -> "LedgerConfigs": + def ledger_configs(self) -> LedgerConfigs: """Get ledger configs.""" ledger_configs = {} for override in self.config.overrides: @@ -416,7 +417,10 @@ def _build_docker( builder.deplopyment_type = DockerComposeGenerator.deployment_type builder.try_update_abci_connection_params() - home_chain_data = service.chain_configs[service.home_chain_id] + home_chain_data = service.chain_configs[service.home_chain_id].chain_data + home_chain_ledger_config = service.chain_configs[ + service.home_chain_id + ].ledger_config builder.try_update_runtime_params( multisig_address=home_chain_data.multisig, agent_instances=home_chain_data.instances, @@ -425,8 +429,8 @@ def _build_docker( ) # TODO: Support for multiledger builder.try_update_ledger_params( - chain=LedgerType(service.ledger_config.type).name.lower(), - address=service.ledger_config.rpc, + chain=LedgerType(home_chain_ledger_config.type).name.lower(), + address=home_chain_ledger_config.rpc, ) # build deployment @@ -656,15 +660,19 @@ class Service(LocalResource): @classmethod def migrate_format(cls, path: Path) -> None: """Migrate the JSON file format if needed.""" - file_path = path / Service._file if Service._file is not None and path.name != Service._file else path - - with open(file_path, 'r', encoding='utf-8') as file: + file_path = ( + path / Service._file + if Service._file is not None and path.name != Service._file + else path + ) + + with open(file_path, "r", encoding="utf-8") as file: data = json.load(file) - - if 'version' in data: + + if "version" in data: # Data is already in the new format return - + # Migrate from old format to new format new_data = { "version": 2, @@ -676,30 +684,42 @@ def migrate_format(cls, path: Path) -> None: "ledger_config": { "rpc": data.get("ledger_config", {}).get("rpc"), "type": data.get("ledger_config", {}).get("type"), - "chain": data.get("ledger_config", {}).get("chain") + "chain": data.get("ledger_config", {}).get("chain"), }, "chain_data": { "instances": data.get("chain_data", {}).get("instances", []), "token": data.get("chain_data", {}).get("token"), "multisig": data.get("chain_data", {}).get("multisig"), "staked": data.get("chain_data", {}).get("staked", False), - "on_chain_state": data.get("chain_data", {}).get("on_chain_state", 3), + "on_chain_state": data.get("chain_data", {}).get( + "on_chain_state", 3 + ), "user_params": { "staking_program_id": "pearl_alpha", - "nft": data.get("chain_data", {}).get("user_params", {}).get("nft"), - "threshold": data.get("chain_data", {}).get("user_params", {}).get("threshold"), - "use_staking": data.get("chain_data", {}).get("user_params", {}).get("use_staking"), - "cost_of_bond": data.get("chain_data", {}).get("user_params", {}).get("cost_of_bond"), - "fund_requirements": data.get("chain_data", {}).get("user_params", {}).get("fund_requirements", {}) - } - } + "nft": data.get("chain_data", {}) + .get("user_params", {}) + .get("nft"), + "threshold": data.get("chain_data", {}) + .get("user_params", {}) + .get("threshold"), + "use_staking": data.get("chain_data", {}) + .get("user_params", {}) + .get("use_staking"), + "cost_of_bond": data.get("chain_data", {}) + .get("user_params", {}) + .get("cost_of_bond"), + "fund_requirements": data.get("chain_data", {}) + .get("user_params", {}) + .get("fund_requirements", {}), + }, + }, } }, "service_path": data.get("service_path", ""), - "name": data.get("name", "") + "name": data.get("name", ""), } - - with open(file_path, 'w', encoding='utf-8') as file: + + with open(file_path, "w", encoding="utf-8") as file: json.dump(new_data, file, indent=2) @classmethod @@ -725,7 +745,7 @@ def deployment(self) -> Deployment: return t.cast(Deployment, self._deployment) @staticmethod - def new( + def new( # pylint: disable=too-many-locals hash: str, keys: Keys, service_template: ServiceTemplate, @@ -756,7 +776,7 @@ def new( multisig=DUMMY_MULTISIG, staked=False, on_chain_state=OnChainState.NON_EXISTENT, - user_params=OnChainUserParams.from_json(config), + user_params=OnChainUserParams.from_json(config), # type: ignore ) chain_configs[chain] = ChainConfig( @@ -765,7 +785,7 @@ def new( ) service = Service( - version=2, # TODO implement in appropriate place + version=SERVICE_CONFIG_VERSION, name=service_yaml["author"] + "/" + service_yaml["name"], hash=service_template["hash"], keys=keys, @@ -777,11 +797,16 @@ def new( service.store() return service - def update_user_params_from_template(self, service_template: ServiceTemplate): + def update_user_params_from_template( + self, service_template: ServiceTemplate + ) -> None: """Update user params from template.""" for chain, config in service_template["configurations"].items(): - for chain, config in service_template["configurations"].items(): - self.chain_configs[chain].chain_data.user_params = OnChainUserParams.from_json(config) + self.chain_configs[ + chain + ].chain_data.user_params = OnChainUserParams.from_json( + config # type: ignore + ) self.store() diff --git a/package.json b/package.json index b50e8fe5f..f940d09a7 100644 --- a/package.json +++ b/package.json @@ -58,5 +58,5 @@ "download-binaries": "sh download_binaries.sh", "build:pearl": "sh build_pearl.sh" }, - "version": "0.1.0-rc117" -} + "version": "0.1.0-rc120" +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3c4bce6cd..a02e08312 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "olas-operate-middleware" -version = "0.1.0-rc117" +version = "0.1.0-rc120" description = "" authors = ["David Vilela ", "Viraj Patel "] readme = "README.md" diff --git a/tox.ini b/tox.ini index b7de2caf0..88426bd93 100644 --- a/tox.ini +++ b/tox.ini @@ -215,3 +215,6 @@ ignore_missing_imports = True [mypy-psutil.*] ignore_missing_imports = True + +[mypy-eth_utils.*] +ignore_missing_imports = True