diff --git a/packages/packages.json b/packages/packages.json index 1f7ec53d..05880316 100644 --- a/packages/packages.json +++ b/packages/packages.json @@ -1,11 +1,12 @@ { "dev": { - "skill/valory/market_manager_abci/0.1.0": "bafybeihdw263vfkzvopfmsatnlrcky5ohcq6grjeurr6opcmm6kcqqfkba", - "skill/valory/decision_maker_abci/0.1.0": "bafybeigdlxygxlxtubwwbninvhcm7m44fe634kp6fjhsdsvsubbti44kzy", - "skill/valory/trader_abci/0.1.0": "bafybeiaf76cioo47imacxziiqnvzmzq5tculaxk3q2sa53mrpuvszur2ji", + "skill/valory/market_manager_abci/0.1.0": "bafybeiakiozzsmmqnpp2siwnyfc7ymgnjqga65ntyjlsqk3ntsqpfj2kaa", + "skill/valory/decision_maker_abci/0.1.0": "bafybeie6wgyx67kfpa2susaylnl3qoxl77aex5pvlxggvxjbfsqykkzuve", + "skill/valory/trader_abci/0.1.0": "bafybeibhd4nsc7pkpzkyzsgpvdadsxd2rlle5ap23fydrkgoztfpam4u64", "contract/valory/market_maker/0.1.0": "bafybeifihhoertnx3itmfzbuf4lazclzjuctxurayajguyn7ggsddolhte", - "agent/valory/trader/0.1.0": "bafybeief3ecqswbtah7iiweyw4afpnh7k3txn2jslhbf5ykkhvvpkhzrbu", - "service/valory/trader/0.1.0": "bafybeibkgt7tb4oy7ismczzzcxaw32v2ijefd4upr7z6ri4rharlgvethi" + "agent/valory/trader/0.1.0": "bafybeifrctknroktvl7imrsyqrflfdvdx5lklsxuua7akihhfrf6yt655i", + "service/valory/trader/0.1.0": "bafybeihkzdxep35mh7mg3vi3gufpwxa3aozqfd6bgmav6dnz6jktshn2ty", + "contract/valory/erc20/0.1.0": "bafybeico5tbp27ebo7a745ok7duafmgeacyuonq5unjrisaefj6mg3rq7a" }, "third_party": { "protocol/open_aea/signing/1.0.0": "bafybeibqlfmikg5hk4phzak6gqzhpkt6akckx7xppbp53mvwt6r73h7tk4", diff --git a/packages/valory/agents/trader/aea-config.yaml b/packages/valory/agents/trader/aea-config.yaml index 2f070245..84bba339 100644 --- a/packages/valory/agents/trader/aea-config.yaml +++ b/packages/valory/agents/trader/aea-config.yaml @@ -15,8 +15,12 @@ connections: - valory/ledger:0.19.0:bafybeicgfupeudtmvehbwziqfxiz6ztsxr5rxzvalzvsdsspzz73o5fzfi - valory/p2p_libp2p_client:0.1.0:bafybeidwcobzb7ut3efegoedad7jfckvt2n6prcmd4g7xnkm6hp6aafrva contracts: +- valory/gnosis_safe:0.1.0:bafybeif5fdwoxq5mscrurtuimadmtctyxxeeui45u4g6leqobzls7bsl3u +- valory/gnosis_safe_proxy_factory:0.1.0:bafybeiaa6fgwtykrti6i7sbt22raavpsbobsq2xgem4nkbcg744agnmkae - valory/service_registry:0.1.0:bafybeibdy55edqs3djptv77ljkmbf6m3zizhutmvwgj3hpsagvmzhr4jbm - valory/market_maker:0.1.0:bafybeifihhoertnx3itmfzbuf4lazclzjuctxurayajguyn7ggsddolhte +- valory/erc20:0.1.0:bafybeico5tbp27ebo7a745ok7duafmgeacyuonq5unjrisaefj6mg3rq7a +- valory/multisend:0.1.0:bafybeict2k3vf3c4fvzosaq5kku2ivtzsskbomrujmmoicut7eg52onnje protocols: - open_aea/signing:1.0.0:bafybeibqlfmikg5hk4phzak6gqzhpkt6akckx7xppbp53mvwt6r73h7tk4 - valory/abci:0.1.0:bafybeig3dj5jhsowlvg3t73kgobf6xn4nka7rkttakdb2gwsg5bp7rt7q4 @@ -33,9 +37,9 @@ skills: - valory/reset_pause_abci:0.1.0:bafybeibqz7y3i4aepuprhijwdydkcsbqjtpeea6gdzpp5fgc6abrvjz25a - valory/termination_abci:0.1.0:bafybeieb3gnvjxxsh73g67m7rivzknwb63xu4qeagpkv7f4mqz33ecikem - valory/transaction_settlement_abci:0.1.0:bafybeihdpac4ayfgop3ixflimlb3zzyeejlpqtljfptdak6tc7aq4u5fzi -- valory/market_manager_abci:0.1.0:bafybeihdw263vfkzvopfmsatnlrcky5ohcq6grjeurr6opcmm6kcqqfkba -- valory/decision_maker_abci:0.1.0:bafybeigdlxygxlxtubwwbninvhcm7m44fe634kp6fjhsdsvsubbti44kzy -- valory/trader_abci:0.1.0:bafybeiaf76cioo47imacxziiqnvzmzq5tculaxk3q2sa53mrpuvszur2ji +- valory/market_manager_abci:0.1.0:bafybeiakiozzsmmqnpp2siwnyfc7ymgnjqga65ntyjlsqk3ntsqpfj2kaa +- valory/decision_maker_abci:0.1.0:bafybeie6wgyx67kfpa2susaylnl3qoxl77aex5pvlxggvxjbfsqykkzuve +- valory/trader_abci:0.1.0:bafybeibhd4nsc7pkpzkyzsgpvdadsxd2rlle5ap23fydrkgoztfpam4u64 default_ledger: ethereum required_ledgers: - ethereum @@ -135,7 +139,7 @@ models: history_check_timeout: ${int:1205} reset_pause_duration: ${int:30} max_healthcheck: ${int:43200} - multisend_address: ${str:0x0000000000000000000000000000000000000000} + multisend_address: ${str:0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761} drand_public_key: ${str:868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31} service_registry_address: ${str:null} share_tm_config_on_startup: ${bool:false} diff --git a/packages/valory/contracts/erc20/README.md b/packages/valory/contracts/erc20/README.md new file mode 100644 index 00000000..8c0c7780 --- /dev/null +++ b/packages/valory/contracts/erc20/README.md @@ -0,0 +1 @@ +# ERC20 token contract diff --git a/packages/valory/contracts/erc20/__init__.py b/packages/valory/contracts/erc20/__init__.py new file mode 100644 index 00000000..13826083 --- /dev/null +++ b/packages/valory/contracts/erc20/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the support resources for an ERC20 token.""" diff --git a/packages/valory/contracts/erc20/build/ERC20.json b/packages/valory/contracts/erc20/build/ERC20.json new file mode 100644 index 00000000..da6252ca --- /dev/null +++ b/packages/valory/contracts/erc20/build/ERC20.json @@ -0,0 +1,288 @@ +{ + "_format": "", + "contractName": "", + "sourceName": "", + "abi": [ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "guy", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "src", + "type": "address" + }, + { + "name": "dst", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "wad", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "dst", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "deposit", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": true, + "name": "guy", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": true, + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + } + ], + "bytecode": "", + "deployedBytecode": "", + "linkReferences": {}, + "deployedLinkReferences": {} +} \ No newline at end of file diff --git a/packages/valory/contracts/erc20/contract.py b/packages/valory/contracts/erc20/contract.py new file mode 100644 index 00000000..137fb3b4 --- /dev/null +++ b/packages/valory/contracts/erc20/contract.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the class to connect to an ERC20 token contract.""" + +from typing import Dict + +from aea.common import JSONLike +from aea.configurations.base import PublicId +from aea.contracts.base import Contract +from aea.crypto.base import LedgerApi +from aea_ledger_ethereum import EthereumApi + +PUBLIC_ID = PublicId.from_str("valory/erc20:0.1.0") + + +class ERC20(Contract): + """The ERC20 contract.""" + + contract_id = PUBLIC_ID + + @classmethod + def check_balance( + cls, + ledger_api: EthereumApi, + contract_address: str, + account: str, + ) -> JSONLike: + """Check the balance of the given account.""" + contract_instance = cls.get_instance(ledger_api, contract_address) + balance_of = getattr(contract_instance.functions, "balanceOf") + balance = balance_of(account).call() + return dict(balance=balance) + + @classmethod + def build_approval_tx( + cls, + ledger_api: LedgerApi, + contract_address: str, + spender: str, + amount: int, + ) -> Dict[str, bytes]: + """Build an ERC20 approval.""" + contract_instance = cls.get_instance(ledger_api, contract_address) + checksumed_spender = ledger_api.api.toChecksumAddress(spender) + data = contract_instance.encodeABI("approve", args=(checksumed_spender, amount)) + return {"data": bytes.fromhex(data[2:])} diff --git a/packages/valory/contracts/erc20/contract.yaml b/packages/valory/contracts/erc20/contract.yaml new file mode 100644 index 00000000..be714f3c --- /dev/null +++ b/packages/valory/contracts/erc20/contract.yaml @@ -0,0 +1,31 @@ +name: erc20 +author: valory +version: 0.1.0 +type: contract +description: ERC20 token contract +license: Apache-2.0 +aea_version: '>=1.0.0, <2.0.0' +fingerprint: + README.md: bafybeifmfma6rglvpa22odtozyosnp5mwljum64utxip2wgmezuhnjjjyi + __init__.py: bafybeia2k2vmq7lmd3uchmm6jmzj7ebqkogk5aolpw4nwsawnkgqfobltm + build/ERC20.json: bafybeiemn5b5nszuss7xj6lmvmjuendltp6wz7ubihdvd7c6wqw4bohbpa + contract.py: bafybeigybnez6haka42c6hgh6odelf745vhlxtxd5hoe67spoqyfjzmtkq +fingerprint_ignore_patterns: [] +contracts: [] +class_name: ERC20 +contract_interface_paths: + ethereum: build/ERC20.json +dependencies: + ecdsa: + version: '>=0.15' + eth_typing: {} + hexbytes: {} + open-aea-ledger-ethereum: + version: ==1.35.0 + open-aea-test-autonomy: + version: ==0.10.7 + packaging: {} + py-eth-sig-utils: {} + requests: {} + web3: + version: ==5.31.4 diff --git a/packages/valory/services/trader/service.yaml b/packages/valory/services/trader/service.yaml index 10fd2c53..42cd18fd 100644 --- a/packages/valory/services/trader/service.yaml +++ b/packages/valory/services/trader/service.yaml @@ -7,7 +7,7 @@ license: Apache-2.0 fingerprint: README.md: bafybeigtuothskwyvrhfosps2bu6suauycolj67dpuxqvnicdrdu7yhtvq fingerprint_ignore_patterns: [] -agent: valory/trader:0.1.0:bafybeief3ecqswbtah7iiweyw4afpnh7k3txn2jslhbf5ykkhvvpkhzrbu +agent: valory/trader:0.1.0:bafybeifrctknroktvl7imrsyqrflfdvdx5lklsxuua7akihhfrf6yt655i number_of_agents: 4 deployment: {} --- diff --git a/packages/valory/skills/decision_maker_abci/behaviours/bet_placement.py b/packages/valory/skills/decision_maker_abci/behaviours/bet_placement.py index 140f8b8d..ce9182de 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/bet_placement.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/bet_placement.py @@ -19,18 +19,27 @@ """This module contains the behaviour for sampling a bet.""" +import dataclasses from datetime import datetime, timedelta -from typing import Any, Callable, Generator, Optional, cast +from typing import Any, Callable, Generator, List, Optional, cast -from packages.valory.contracts.gnosis_safe.contract import GnosisSafeContract +from hexbytes import HexBytes + +from packages.valory.contracts.erc20.contract import ERC20 +from packages.valory.contracts.gnosis_safe.contract import ( + GnosisSafeContract, + SafeOperation, +) from packages.valory.contracts.market_maker.contract import ( FixedProductMarketMakerContract, ) +from packages.valory.contracts.multisend.contract import MultiSendContract from packages.valory.protocols.contract_api import ContractApiMessage from packages.valory.skills.abstract_round_abci.behaviour_utils import TimeoutException from packages.valory.skills.decision_maker_abci.behaviours.base import ( DecisionMakerBaseBehaviour, ) +from packages.valory.skills.decision_maker_abci.models import MultisendBatch from packages.valory.skills.decision_maker_abci.payloads import BetPlacementPayload from packages.valory.skills.decision_maker_abci.states.bet_placement import ( BetPlacementRound, @@ -41,6 +50,9 @@ from packages.valory.skills.transaction_settlement_abci.rounds import TX_HASH_LENGTH +WaitableConditionType = Generator[None, None, bool] + + # setting the safe gas to 0 means that all available gas will be used # which is what we want in most cases # more info here: https://safe-docs.dev.gnosisdev.com/safe/docs/contracts_tx_execution/ @@ -57,10 +69,17 @@ class BetPlacementBehaviour(DecisionMakerBaseBehaviour): def __init__(self, **kwargs: Any) -> None: """Initialize the bet placement behaviour.""" super().__init__(**kwargs) + self.balance = 0 self.buy_amount = 0 - self.buy_data = b"" + self.multisend_batches: List[MultisendBatch] = [] + self.multisend_data = b"" self.safe_tx_hash = "" + @property + def collateral_token(self) -> str: + """Get the contract address of the token that the market maker supports.""" + return self.synchronized_data.sampled_bet.collateralToken + @property def market_maker_contract_address(self) -> str: """Get the contract address of the market maker on which the service is going to place the bet.""" @@ -71,12 +90,74 @@ def investment_amount(self) -> int: """Get the investment amount of the bet.""" return self.params.get_bet_amount(self.synchronized_data.confidence) + @property + def sufficient_balance(self) -> int: + """Get whether the balance is sufficient for the investment amount of the bet.""" + return self.balance >= self.investment_amount + @property def outcome_index(self) -> int: """Get the index of the outcome that the service is going to place a bet on.""" return cast(int, self.synchronized_data.vote) - def _calc_buy_amount(self) -> Generator[None, None, bool]: + @property + def multi_send_txs(self) -> List[dict]: + """Get the multisend transactions as a list of dictionaries.""" + return [dataclasses.asdict(batch) for batch in self.multisend_batches] + + def _check_balance(self) -> WaitableConditionType: + """Check the safe's balance.""" + response_msg = yield from self.get_contract_api_response( + performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, # type: ignore + contract_address=self.collateral_token, + contract_id=str(ERC20.contract_id), + contract_callable="check_balance", + account=self.synchronized_data.safe_contract_address, + ) + if response_msg.performative != ContractApiMessage.Performative.RAW_TRANSACTION: + self.context.logger.error( + f"Could not calculate the balance of the safe: {response_msg}" + ) + return False + + balance = response_msg.raw_transaction.body.get("balance", None) + if balance is None: + self.context.logger.error( + f"Something went wrong while trying to get the balance of the safe: {response_msg}" + ) + return False + + self.balance = int(balance) + return True + + def _build_approval_tx(self) -> WaitableConditionType: + """Build an ERC20 approve transaction.""" + response_msg = yield from self.get_contract_api_response( + performative=ContractApiMessage.Performative.GET_STATE, # type: ignore + contract_address=self.collateral_token, + contract_id=str(ERC20.contract_id), + contract_callable="build_approval_tx", + spender=self.market_maker_contract_address, + amount=self.investment_amount, + ) + + if response_msg.performative != ContractApiMessage.Performative.STATE: + self.context.logger.info(f"Could not build approval tx: {response_msg}") + return False + + approval_data = response_msg.state.body.get("data") + if approval_data is None: + self.context.logger.info(f"Could not build approval tx: {response_msg}") + return False + + batch = MultisendBatch( + to=self.collateral_token, + data=HexBytes(approval_data), + ) + self.multisend_batches.append(batch) + return True + + def _calc_buy_amount(self) -> WaitableConditionType: """Calculate the buy amount of the conditional token.""" response_msg = yield from self.get_contract_api_response( performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, # type: ignore @@ -99,13 +180,13 @@ def _calc_buy_amount(self) -> Generator[None, None, bool]: ) return False - self.buy_amount = buy_amount + self.buy_amount = int(buy_amount) return True - def _build_buy_data(self) -> Generator[None, None, bool]: + def _build_buy_tx(self) -> WaitableConditionType: """Get the buy tx data encoded.""" response_msg = yield from self.get_contract_api_response( - performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, # type: ignore + performative=ContractApiMessage.Performative.GET_STATE, # type: ignore contract_address=self.market_maker_contract_address, contract_id=str(FixedProductMarketMakerContract.contract_id), contract_callable="get_buy_data", @@ -113,43 +194,81 @@ def _build_buy_data(self) -> Generator[None, None, bool]: outcome_index=self.outcome_index, min_outcome_tokens_to_buy=self.buy_amount, ) - if response_msg.performative != ContractApiMessage.Performative.RAW_TRANSACTION: + if response_msg.performative != ContractApiMessage.Performative.STATE: self.context.logger.error( f"Could not get the data for the buy transaction: {response_msg}" ) return False - buy_data = response_msg.raw_transaction.body.get("data", None) + buy_data = response_msg.state.body.get("data", None) if buy_data is None: self.context.logger.error( - "Something went wrong while trying to encode the buy data." + f"Something went wrong while trying to encode the buy data: {response_msg}" ) return False - self.buy_data = buy_data + batch = MultisendBatch( + to=self.market_maker_contract_address, + data=HexBytes(buy_data), + ) + self.multisend_batches.append(batch) return True - def _build_safe_tx_hash(self) -> Generator[None, None, bool]: - """Prepares and returns the safe tx hash.""" + def _build_multisend_data( + self, + ) -> WaitableConditionType: + """Get the multisend tx.""" response_msg = yield from self.get_contract_api_response( performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, # type: ignore + contract_address=self.params.multisend_address, + contract_id=str(MultiSendContract.contract_id), + contract_callable="get_tx_data", + multi_send_txs=self.multi_send_txs, + ) + expected_performative = ContractApiMessage.Performative.RAW_TRANSACTION + if response_msg.performative != expected_performative: + self.context.logger.error( + f"Couldn't compile the multisend tx. " + f"Expected response performative {expected_performative.value}, " # type: ignore + f"received {response_msg.performative.value}: {response_msg}" + ) + return False + + multisend_data_str = response_msg.raw_transaction.body.get("data", None) + if multisend_data_str is None: + self.context.logger.error( + f"Something went wrong while trying to prepare the multisend data: {response_msg}" + ) + return False + + # strip "0x" from the response + multisend_data_str = str(response_msg.raw_transaction.body["data"])[2:] + self.multisend_data = bytes.fromhex(multisend_data_str) + return True + + def _build_safe_tx_hash(self) -> WaitableConditionType: + """Prepares and returns the safe tx hash.""" + response_msg = yield from self.get_contract_api_response( + performative=ContractApiMessage.Performative.GET_STATE, # type: ignore contract_address=self.synchronized_data.safe_contract_address, contract_id=str(GnosisSafeContract.contract_id), contract_callable="get_raw_safe_transaction_hash", - to_address=self.market_maker_contract_address, + to_address=self.params.multisend_address, value=_ETHER_VALUE, - data=self.buy_data, + data=self.multisend_data, safe_tx_gas=_SAFE_GAS, + operation=SafeOperation.DELEGATE_CALL.value, ) - if response_msg.performative != ContractApiMessage.Performative.RAW_TRANSACTION: + if response_msg.performative != ContractApiMessage.Performative.STATE: self.context.logger.error( "Couldn't get safe tx hash. Expected response performative " - f"{ContractApiMessage.Performative.RAW_TRANSACTION.value}, received {response_msg}." # type: ignore + f"{ContractApiMessage.Performative.STATE.value}, " # type: ignore + f"received {response_msg.performative.value}: {response_msg}." ) return False - tx_hash = response_msg.raw_transaction.body.get("tx_hash", None) + tx_hash = response_msg.state.body.get("tx_hash", None) if tx_hash is None or len(tx_hash) != TX_HASH_LENGTH: self.context.logger.error( "Something went wrong while trying to get the buy transaction's hash. " @@ -163,7 +282,7 @@ def _build_safe_tx_hash(self) -> Generator[None, None, bool]: def wait_for_condition_with_sleep( self, - condition_gen: Callable[[], Generator[None, None, bool]], + condition_gen: Callable[[], WaitableConditionType], timeout: Optional[float] = None, ) -> Generator[None, None, None]: """Wait for a condition to happen and sleep in-between checks. @@ -195,8 +314,10 @@ def wait_for_condition_with_sleep( def _prepare_safe_tx(self) -> Generator[None, None, str]: """Prepare the safe transaction for placing a bet and return the hex for the tx settlement skill.""" for step in ( + self._build_approval_tx, self._calc_buy_amount, - self._build_buy_data, + self._build_buy_tx, + self._build_multisend_data, self._build_safe_tx_hash, ): yield from self.wait_for_condition_with_sleep(step) @@ -205,14 +326,18 @@ def _prepare_safe_tx(self) -> Generator[None, None, str]: self.safe_tx_hash, _ETHER_VALUE, _SAFE_GAS, - self.market_maker_contract_address, - self.buy_data, + self.params.multisend_address, + self.multisend_data, + SafeOperation.DELEGATE_CALL.value, ) def async_act(self) -> Generator: """Do the action.""" with self.context.benchmark_tool.measure(self.behaviour_id).local(): - betting_tx_hex = yield from self._prepare_safe_tx() + yield from self.wait_for_condition_with_sleep(self._check_balance) + betting_tx_hex = None + if self.sufficient_balance: + betting_tx_hex = yield from self._prepare_safe_tx() payload = BetPlacementPayload(self.context.agent_address, betting_tx_hex) yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/fsm_specification.yaml b/packages/valory/skills/decision_maker_abci/fsm_specification.yaml index 6eb39841..5751f1f3 100644 --- a/packages/valory/skills/decision_maker_abci/fsm_specification.yaml +++ b/packages/valory/skills/decision_maker_abci/fsm_specification.yaml @@ -1,6 +1,7 @@ alphabet_in: - DONE - FETCH_ERROR +- INSUFFICIENT_BALANCE - MECH_RESPONSE_ERROR - NONE - NON_BINARY @@ -13,6 +14,7 @@ final_states: - FinishedDecisionMakerRound - FinishedWithoutDecisionRound - ImpossibleRound +- RefillRequiredRound label: DecisionMakerAbciApp start_states: - BlacklistingRound @@ -24,10 +26,11 @@ states: - FinishedDecisionMakerRound - FinishedWithoutDecisionRound - ImpossibleRound +- RefillRequiredRound - SamplingRound transition_func: (BetPlacementRound, DONE): FinishedDecisionMakerRound - (BetPlacementRound, NONE): ImpossibleRound + (BetPlacementRound, INSUFFICIENT_BALANCE): RefillRequiredRound (BetPlacementRound, NO_MAJORITY): BetPlacementRound (BetPlacementRound, ROUND_TIMEOUT): BetPlacementRound (BlacklistingRound, DONE): FinishedWithoutDecisionRound diff --git a/packages/valory/skills/decision_maker_abci/models.py b/packages/valory/skills/decision_maker_abci/models.py index 82245593..57738f5a 100644 --- a/packages/valory/skills/decision_maker_abci/models.py +++ b/packages/valory/skills/decision_maker_abci/models.py @@ -19,8 +19,13 @@ """This module contains the models for the skill.""" +from dataclasses import dataclass from typing import Any, Dict +from aea.exceptions import enforce +from hexbytes import HexBytes + +from packages.valory.contracts.multisend.contract import MultiSendOperation from packages.valory.skills.abstract_round_abci.models import ( BenchmarkTool as BaseBenchmarkTool, ) @@ -59,9 +64,22 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.blacklisting_duration: int = self._ensure( "blacklisting_duration", kwargs, int ) + multisend_address = kwargs.get("multisend_address", None) + enforce(multisend_address is not None, "Multisend address not specified!") + self.multisend_address = multisend_address super().__init__(*args, **kwargs) def get_bet_amount(self, confidence: float) -> int: """Get the bet amount given a prediction's confidence.""" threshold = round(confidence, 1) return self.bet_amount_per_threshold[threshold] + + +@dataclass +class MultisendBatch: + """A structure representing a single transaction of a multisend.""" + + to: str + data: HexBytes + value: int = 0 + operation: MultiSendOperation = MultiSendOperation.CALL diff --git a/packages/valory/skills/decision_maker_abci/payloads.py b/packages/valory/skills/decision_maker_abci/payloads.py index 37c7cf2c..2432df38 100644 --- a/packages/valory/skills/decision_maker_abci/payloads.py +++ b/packages/valory/skills/decision_maker_abci/payloads.py @@ -47,4 +47,4 @@ class SamplingPayload(UpdateBetsPayload): class BetPlacementPayload(BaseTxPayload): """Represents a transaction payload for placing a bet.""" - tx_hash: str + tx_hash: Optional[str] diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py index 91fb116b..de27f4a4 100644 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -44,6 +44,7 @@ FinishedDecisionMakerRound, FinishedWithoutDecisionRound, ImpossibleRound, + RefillRequiredRound, ) from packages.valory.skills.decision_maker_abci.states.sampling import SamplingRound from packages.valory.skills.market_manager_abci.rounds import ( @@ -61,33 +62,34 @@ class DecisionMakerAbciApp(AbciApp[Event]): Transition states: 0. SamplingRound - done: 1. - - none: 6. + - none: 7. - no majority: 0. - round timeout: 0. 1. DecisionMakerRound - done: 3. - mech response error: 2. - no majority: 1. - - non binary: 6. + - non binary: 7. - tie: 2. - unprofitable: 2. - round timeout: 1. 2. BlacklistingRound - done: 5. - - none: 6. + - none: 7. - no majority: 2. - round timeout: 2. - - fetch error: 6. + - fetch error: 7. 3. BetPlacementRound - done: 4. - - none: 6. + - insufficient balance: 6. - no majority: 3. - round timeout: 3. 4. FinishedDecisionMakerRound 5. FinishedWithoutDecisionRound - 6. ImpossibleRound + 6. RefillRequiredRound + 7. ImpossibleRound - Final states: {FinishedDecisionMakerRound, FinishedWithoutDecisionRound, ImpossibleRound} + Final states: {FinishedDecisionMakerRound, FinishedWithoutDecisionRound, ImpossibleRound, RefillRequiredRound} Timeouts: round timeout: 30.0 @@ -121,17 +123,19 @@ class DecisionMakerAbciApp(AbciApp[Event]): }, BetPlacementRound: { Event.DONE: FinishedDecisionMakerRound, - Event.NONE: ImpossibleRound, # degenerate round on purpose, should never have reached here + Event.INSUFFICIENT_BALANCE: RefillRequiredRound, # degenerate round on purpose, owner must refill the safe Event.NO_MAJORITY: BetPlacementRound, Event.ROUND_TIMEOUT: BetPlacementRound, }, FinishedDecisionMakerRound: {}, FinishedWithoutDecisionRound: {}, + RefillRequiredRound: {}, ImpossibleRound: {}, } final_states: Set[AppState] = { FinishedDecisionMakerRound, FinishedWithoutDecisionRound, + RefillRequiredRound, ImpossibleRound, } event_to_timeout: Dict[Event, float] = { @@ -149,5 +153,6 @@ class DecisionMakerAbciApp(AbciApp[Event]): get_name(SynchronizedData.most_voted_tx_hash), }, FinishedWithoutDecisionRound: {get_name(SynchronizedData.sampled_bet_index)}, + RefillRequiredRound: set(), ImpossibleRound: set(), } diff --git a/packages/valory/skills/decision_maker_abci/skill.yaml b/packages/valory/skills/decision_maker_abci/skill.yaml index ca352a4f..140cbde6 100644 --- a/packages/valory/skills/decision_maker_abci/skill.yaml +++ b/packages/valory/skills/decision_maker_abci/skill.yaml @@ -10,35 +10,37 @@ fingerprint: __init__.py: bafybeih563ujnigeci2ldzh7hakbau6a222vsed7leg3b7lq32vcn3nm4a behaviours/__init__.py: bafybeih6ddz2ocvm6x6ytvlbcz6oi4snb5ee5xh5h65nq4w2qf7fd7zfky behaviours/base.py: bafybeiftzkqcn2abil4ljzoppcl52zrztplymwqbnydm6ippn7kzhgjexy - behaviours/bet_placement.py: bafybeigs7rzfms4axsdrqayo6vjomrxfu4vb2xu7yemizqehqimmdejx4e + behaviours/bet_placement.py: bafybeifsninom3ith4amavgn5ou2rqx5vvjpr2f7ezdqz7lrmjltsfbv2i behaviours/blacklisting.py: bafybeicvespraci44y2dtddy4wi7cdhjuyk6crjs7ztnssm2rcrovha3hm behaviours/decision_maker.py: bafybeia7uxg4z2orig42t7k2s6bi2236jhlbgfwz2airwbpsuujbr3l6by behaviours/round_behaviour.py: bafybeieoumnukyl3zojpud5ojmjder5lkncxwsxnhtfvwrbvs52bl4gj7a behaviours/sampling.py: bafybeiejyrqf2evjtct35eq3vwjw3f3ymeank2utsa62u5zytbj7y4l7tm dialogues.py: bafybeigpwuzku3we7axmxeamg7vn656maww6emuztau5pg3ebsoquyfdqm - fsm_specification.yaml: bafybeiggebf4simuob5koqhtwnigt6yvnhe5fvr5px7gzci6kcx7tn6elm + fsm_specification.yaml: bafybeid4zoekzgda5txsihu3dunkwfu752to34p2fhrnbzlhc7rvqm6rky handlers.py: bafybeihj33szgrcxnpd73s4nvluyxwwsvhjum2cuq3ilhhe6vfola3k7vy - models.py: bafybeiggylwxzknh4duj6cid3qoodm5wg7ip6ui5hxptvjgkpuaqcuewbq - payloads.py: bafybeibq2my4qz2z7frtevwpafstmltrl3xh3a355unge5q6cli3uneuja - rounds.py: bafybeia6xznked22dam43rol2otqbqjt73yic33rnrdlz52xat6gotwblm + models.py: bafybeicacv5b5i3bw6fve5ilkidjg2e6jaljcmmgnkjmw25fhz52u3l22q + payloads.py: bafybeieywq56f4sg7qz44ztchksmx344mgopvc7apf4jpnrau2kuosaaai + rounds.py: bafybeigrouhbsvpmoo7k4ye2ebqrpxlv3gimcorzn3utyau5637p3fs2nu states/__init__.py: bafybeid23llnyp6j257dluxmrnztugo5llsrog7kua53hllyktz4dqhqoy - states/base.py: bafybeiflaaefherbp2jvmevonqmarpctuzcchi4uv25ghn6mjll4cnjgwi - states/bet_placement.py: bafybeiafuxdellvnnocttnelnyi75sqrlnkobh6wr57mqysyrsyzirkaya + states/base.py: bafybeifk44bz77zbz65q6vppfsjbxmjqvfeespd5jf6ms333cqrgli2rba + states/bet_placement.py: bafybeig5remjdlsuiyla6gismcahnqqq7ooashzcz2m3lalntf37hfj3be states/blacklisting.py: bafybeiao747i4z7owk6dmwfuzdijag55m3ryj3nowfoggvczpgk3koza44 states/decision_maker.py: bafybeiaqehtdzivhswlurqzxx2bxqjnrmeo6bfyvr2zn55ng3qpwbokauy - states/final_states.py: bafybeicruy5dbfzxtx5nvihcpxr5pfbgxxwbosy7x7d5wbqudtooxjsoqi + states/final_states.py: bafybeifhr36wfvzzum44qqknrolglii5fyspg7f5cmq4dxc6oukd7hniji states/sampling.py: bafybeidnvdogjlthjfe7jpaiuezm3xydrbxxukyoss4gx6t5fdin52rsta - tasks.py: bafybeidgfoukqknzwxc3aeu5ifhpygo5nwl6t37ezy362itx4wjslto5be + tasks.py: bafybeid47ybp2opuz2xb7bgxcwztk6rhwluubf7mnxiytqopwx4adprtve fingerprint_ignore_patterns: [] connections: [] contracts: - valory/gnosis_safe:0.1.0:bafybeif5fdwoxq5mscrurtuimadmtctyxxeeui45u4g6leqobzls7bsl3u - valory/market_maker:0.1.0:bafybeifihhoertnx3itmfzbuf4lazclzjuctxurayajguyn7ggsddolhte +- valory/erc20:0.1.0:bafybeico5tbp27ebo7a745ok7duafmgeacyuonq5unjrisaefj6mg3rq7a +- valory/multisend:0.1.0:bafybeict2k3vf3c4fvzosaq5kku2ivtzsskbomrujmmoicut7eg52onnje protocols: - valory/contract_api:1.0.0:bafybeidv6wxpjyb2sdyibnmmum45et4zcla6tl63bnol6ztyoqvpl4spmy skills: - valory/abstract_round_abci:0.1.0:bafybeiac62ennpw54gns2quk4g3yoaili2mb72nj6c52czobz5dcwj4mwi -- valory/market_manager_abci:0.1.0:bafybeihdw263vfkzvopfmsatnlrcky5ohcq6grjeurr6opcmm6kcqqfkba +- valory/market_manager_abci:0.1.0:bafybeiakiozzsmmqnpp2siwnyfc7ymgnjqga65ntyjlsqk3ntsqpfj2kaa - valory/transaction_settlement_abci:0.1.0:bafybeihdpac4ayfgop3ixflimlb3zzyeejlpqtljfptdak6tc7aq4u5fzi behaviours: main: @@ -167,4 +169,6 @@ models: dependencies: mech-client: version: ==0.2.2 + hexbytes: + version: ==0.3.1 is_abstract: true diff --git a/packages/valory/skills/decision_maker_abci/states/base.py b/packages/valory/skills/decision_maker_abci/states/base.py index 9c1c3d1e..3a1f8e46 100644 --- a/packages/valory/skills/decision_maker_abci/states/base.py +++ b/packages/valory/skills/decision_maker_abci/states/base.py @@ -41,6 +41,7 @@ class Event(Enum): NON_BINARY = "non_binary" TIE = "tie" UNPROFITABLE = "unprofitable" + INSUFFICIENT_BALANCE = "insufficient_balance" ROUND_TIMEOUT = "round_timeout" NO_MAJORITY = "no_majority" diff --git a/packages/valory/skills/decision_maker_abci/states/bet_placement.py b/packages/valory/skills/decision_maker_abci/states/bet_placement.py index 36f6a298..d875c04a 100644 --- a/packages/valory/skills/decision_maker_abci/states/bet_placement.py +++ b/packages/valory/skills/decision_maker_abci/states/bet_placement.py @@ -36,7 +36,7 @@ class BetPlacementRound(CollectSameUntilThresholdRound): payload_class = BetPlacementPayload synchronized_data_class = SynchronizedData done_event = Event.DONE - none_event = Event.NONE + none_event = Event.INSUFFICIENT_BALANCE no_majority_event = Event.NO_MAJORITY selection_key = get_name(SynchronizedData.most_voted_tx_hash) collection_key = get_name(SynchronizedData.participant_to_bet_placement) diff --git a/packages/valory/skills/decision_maker_abci/states/final_states.py b/packages/valory/skills/decision_maker_abci/states/final_states.py index 829acec0..ffc09c3b 100644 --- a/packages/valory/skills/decision_maker_abci/states/final_states.py +++ b/packages/valory/skills/decision_maker_abci/states/final_states.py @@ -30,5 +30,9 @@ class FinishedWithoutDecisionRound(DegenerateRound): """A round representing that decision-making has finished without deciding on a bet.""" +class RefillRequiredRound(DegenerateRound): + """A round representing that a refill is required for placing a bet.""" + + class ImpossibleRound(DegenerateRound): """A round representing that decision-making is impossible with the given parametrization.""" diff --git a/packages/valory/skills/decision_maker_abci/tasks.py b/packages/valory/skills/decision_maker_abci/tasks.py index 61428a47..381d57e3 100644 --- a/packages/valory/skills/decision_maker_abci/tasks.py +++ b/packages/valory/skills/decision_maker_abci/tasks.py @@ -19,6 +19,7 @@ """Contains the background tasks of the decision maker skill.""" +import json from dataclasses import dataclass from typing import Any, Optional @@ -60,6 +61,13 @@ class MechInteractionResponse: prediction: Optional[PredictionResponse] = None error: str = "Unknown" + @classmethod + def incorrect_format(cls, res: Any) -> "MechInteractionResponse": + """Return an incorrect format response.""" + response = cls() + response.error = f"The response's format was unexpected: {res}" + return response + class MechInteractionTask(Task): """Perform an interaction with a mech.""" @@ -73,9 +81,10 @@ def execute( res = interact(*args, **kwargs) try: - prediction = PredictionResponse(**res) - except (ValueError, TypeError): - error_msg = f"The response's format was unexpected: {res}" - return MechInteractionResponse(error=error_msg) + prediction_result = res["result"] + deserialized_prediction = json.loads(prediction_result) + prediction = PredictionResponse(**deserialized_prediction) + except (json.JSONDecodeError, KeyError, ValueError, TypeError): + return MechInteractionResponse.incorrect_format(res) else: return MechInteractionResponse(prediction) diff --git a/packages/valory/skills/market_manager_abci/bets.py b/packages/valory/skills/market_manager_abci/bets.py index 4906cbb1..b42be41b 100644 --- a/packages/valory/skills/market_manager_abci/bets.py +++ b/packages/valory/skills/market_manager_abci/bets.py @@ -45,6 +45,7 @@ class Bet: id: str market: str title: str + collateralToken: str creator: str fee: int openingTimestamp: int diff --git a/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py b/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py index b7386962..4fa55c95 100644 --- a/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py +++ b/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py @@ -38,6 +38,7 @@ ){ id title + collateralToken creator fee openingTimestamp diff --git a/packages/valory/skills/market_manager_abci/skill.yaml b/packages/valory/skills/market_manager_abci/skill.yaml index 5831f94e..dfb3c879 100644 --- a/packages/valory/skills/market_manager_abci/skill.yaml +++ b/packages/valory/skills/market_manager_abci/skill.yaml @@ -9,12 +9,12 @@ fingerprint: README.md: bafybeie6miwn67uin3bphukmf7qgiifh4xtm42i5v3nuyqxzxtehxsqvcq __init__.py: bafybeigrtedqzlq5mtql2ssjsdriw76ml3666m4e2c3fay6vmyzofl6v6e behaviours.py: bafybeidzzyssdo6gsqtsvr6xbh5chszkev6fnnheuyhgmqsbl2ae6lhs5a - bets.py: bafybeifycdeosqn32wyids6rymjhwdjjebbavi5vcn2a5hrt3ho3aa3bfy + bets.py: bafybeict4pqqepslz5j7jcdwopyjpkosw355jhgohrvtunppm6axb2k6ba dialogues.py: bafybeiebofyykseqp3fmif36cqmmyf3k7d2zbocpl6t6wnlpv4szghrxbm fsm_specification.yaml: bafybeic5cvwfbiu5pywyp3h5s2elvu7jqdrcwayay7o3v3ow47vu2jw53q graph_tooling/__init__.py: bafybeigzo7nhbzafyq3fuhrlewksjvmzttiuk4vonrggtjtph4rw4ncpk4 graph_tooling/queries/__init__.py: bafybeihbybnl53i7k57ql5ujt5ru5n2eg324jfndh4lcnm4fk52mwbkjda - graph_tooling/queries/omen.py: bafybeia2rhkpj4mu6ttcqze2i4eqdslwgew2iui2egqen5ym7ko4ybgwma + graph_tooling/queries/omen.py: bafybeiduznpc6n4kqqoaeev5qwthf7yxpx7tyarlzeiedhpchwkqqqpau4 graph_tooling/requests.py: bafybeifkwmfjhxbmfexolos3umq3k4gsrn5ajhdgq4iht7ne3yge4llfjq handlers.py: bafybeihot2i2yvfkz2gcowvt66wdu6tkjbmv7hsmc4jzt4reqeaiuphbtu models.py: bafybeidhhhp2obt36iatytckybywdm55pzmefpyccakfw3ufyz732ydigy diff --git a/packages/valory/skills/trader_abci/fsm_specification.yaml b/packages/valory/skills/trader_abci/fsm_specification.yaml index c7f1d4a8..c75f3589 100644 --- a/packages/valory/skills/trader_abci/fsm_specification.yaml +++ b/packages/valory/skills/trader_abci/fsm_specification.yaml @@ -7,6 +7,7 @@ alphabet_in: - FINALIZATION_FAILED - FINALIZE_TIMEOUT - INCORRECT_SERIALIZATION +- INSUFFICIENT_BALANCE - INSUFFICIENT_FUNDS - MECH_RESPONSE_ERROR - NEGATIVE @@ -23,6 +24,7 @@ alphabet_in: default_start_state: RegistrationStartupRound final_states: - ImpossibleRound +- RefillRequiredRound label: TraderAbciApp start_states: - RegistrationRound @@ -37,6 +39,7 @@ states: - FinalizationRound - ImpossibleRound - RandomnessTransactionSubmissionRound +- RefillRequiredRound - RegistrationRound - RegistrationStartupRound - ResetAndPauseRound @@ -50,7 +53,7 @@ states: - ValidateTransactionRound transition_func: (BetPlacementRound, DONE): RandomnessTransactionSubmissionRound - (BetPlacementRound, NONE): ImpossibleRound + (BetPlacementRound, INSUFFICIENT_BALANCE): RefillRequiredRound (BetPlacementRound, NO_MAJORITY): BetPlacementRound (BetPlacementRound, ROUND_TIMEOUT): BetPlacementRound (BlacklistingRound, DONE): ResetAndPauseRound diff --git a/packages/valory/skills/trader_abci/skill.yaml b/packages/valory/skills/trader_abci/skill.yaml index 751b80fc..85b1722a 100644 --- a/packages/valory/skills/trader_abci/skill.yaml +++ b/packages/valory/skills/trader_abci/skill.yaml @@ -11,7 +11,7 @@ fingerprint: behaviours.py: bafybeihxxsqctxptcch2pl2u4egpbrwukpggr7abrkcukzf6ant57wsqrq composition.py: bafybeiankmqnq3pns5f2zeclzoij2skpy35hly5evzqpjsh6hfe34ahwza dialogues.py: bafybeiebofyykseqp3fmif36cqmmyf3k7d2zbocpl6t6wnlpv4szghrxbm - fsm_specification.yaml: bafybeiewykne3nhzw7er25z3ywoi7wpdshkos66yzrstzilm5ywksswbu4 + fsm_specification.yaml: bafybeicrv3hkih4ii7jsgsm2w24xvsn3kd6vry4lptlaw7ui7wxqd4ulv4 handlers.py: bafybeicamc6vmozij5dwvkxmbxjazsgf3sacojhstbjtq7vfggszxugvey models.py: bafybeigro3coqmff2xd7hbnuxq7qi4pcirpr6v526zw6fstko3dsgce63u fingerprint_ignore_patterns: [] @@ -24,8 +24,8 @@ skills: - valory/reset_pause_abci:0.1.0:bafybeibqz7y3i4aepuprhijwdydkcsbqjtpeea6gdzpp5fgc6abrvjz25a - valory/transaction_settlement_abci:0.1.0:bafybeihdpac4ayfgop3ixflimlb3zzyeejlpqtljfptdak6tc7aq4u5fzi - valory/termination_abci:0.1.0:bafybeieb3gnvjxxsh73g67m7rivzknwb63xu4qeagpkv7f4mqz33ecikem -- valory/market_manager_abci:0.1.0:bafybeihdw263vfkzvopfmsatnlrcky5ohcq6grjeurr6opcmm6kcqqfkba -- valory/decision_maker_abci:0.1.0:bafybeigdlxygxlxtubwwbninvhcm7m44fe634kp6fjhsdsvsubbti44kzy +- valory/market_manager_abci:0.1.0:bafybeiakiozzsmmqnpp2siwnyfc7ymgnjqga65ntyjlsqk3ntsqpfj2kaa +- valory/decision_maker_abci:0.1.0:bafybeie6wgyx67kfpa2susaylnl3qoxl77aex5pvlxggvxjbfsqykkzuve behaviours: main: args: {} diff --git a/pyproject.toml b/pyproject.toml index 4084c90c..5eb5f973 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ certifi = "*" multidict = "*" ecdsa = ">=0.15" eth_typing = "*" -hexbytes = "*" +hexbytes = "==0.3.1" packaging = "*" py-ecc = "==5.2.0" pytz = "==2022.2.1" diff --git a/tox.ini b/tox.ini index 590f1c58..0670355c 100644 --- a/tox.ini +++ b/tox.ini @@ -39,7 +39,7 @@ deps = multidict ecdsa>=0.15 eth_typing - hexbytes + hexbytes==0.3.1 packaging py-ecc==5.2.0 pytz==2022.2.1 @@ -353,6 +353,9 @@ ignore_missing_imports = True [mypy-openai.*] ignore_missing_imports = True +[mypy-hexbytes.*] +ignore_missing_imports = True + [darglint] docstring_style=sphinx strictness=short