From 5d466c4446fde86ba20d567d3ef660d14b17748f Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Wed, 4 Dec 2024 10:12:22 +0100 Subject: [PATCH 1/5] Send data along with xDai (#566) --- .../tools/web3_utils.py | 3 ++ pyproject.toml | 2 +- .../markets/omen/test_local_chain.py | 36 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/prediction_market_agent_tooling/tools/web3_utils.py b/prediction_market_agent_tooling/tools/web3_utils.py index d61129b9..d98c7df5 100644 --- a/prediction_market_agent_tooling/tools/web3_utils.py +++ b/prediction_market_agent_tooling/tools/web3_utils.py @@ -297,12 +297,15 @@ def send_xdai_to( from_private_key: PrivateKey, to_address: ChecksumAddress, value: Wei, + data_text: Optional[str] = None, tx_params: Optional[TxParams] = None, timeout: int = 180, ) -> TxReceipt: from_address = private_key_to_public_key(from_private_key) tx_params_new: TxParams = {"value": value, "to": to_address} + if data_text is not None: + tx_params_new["data"] = Web3.to_bytes(text=data_text) if tx_params: tx_params_new.update(tx_params) tx_params_new = _prepare_tx_params(web3, from_address, tx_params=tx_params_new) diff --git a/pyproject.toml b/pyproject.toml index 7c725887..fc8209d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "prediction-market-agent-tooling" -version = "0.57.3" +version = "0.57.4" description = "Tools to benchmark, deploy and monitor prediction market agents." authors = ["Gnosis"] readme = "README.md" diff --git a/tests_integration_with_local_chain/markets/omen/test_local_chain.py b/tests_integration_with_local_chain/markets/omen/test_local_chain.py index 2df6dcfd..16c0c4bf 100644 --- a/tests_integration_with_local_chain/markets/omen/test_local_chain.py +++ b/tests_integration_with_local_chain/markets/omen/test_local_chain.py @@ -1,5 +1,7 @@ import time +import zlib +import pytest from ape_test import TestAccount from eth_account import Account from numpy import isclose @@ -127,3 +129,37 @@ def test_now_datetime(local_web3: Web3, test_keys: APIKeys) -> None: assert ( actual_difference <= allowed_difference ), f"chain_datetime and utc_datetime differ by more than {allowed_difference} seconds: {chain_datetime=} {utc_datetime=} {actual_difference=}" + + +@pytest.mark.parametrize( + "message, value_xdai", + [ + ("Hello there!", xDai(10)), + (zlib.compress(b"Hello there!"), xDai(10)), + ("Hello there!", xDai(0)), + ("", xDai(0)), + ], +) +def test_send_xdai_with_data( + message: str, value_xdai: xDai, local_web3: Web3, accounts: list[TestAccount] +) -> None: + value = xdai_to_wei(value_xdai) + message = "Hello there!" + from_account = accounts[0] + to_account = accounts[1] + + tx_receipt = send_xdai_to( + web3=local_web3, + from_private_key=private_key_type(from_account.private_key), + to_address=to_account.address, + value=value, + data_text=message, + ) + + # Check that we can get the original message + transaction = local_web3.eth.get_transaction(tx_receipt["transactionHash"]) + transaction_message = local_web3.to_text(transaction["input"]) + assert transaction_message == message + + # Check that the value is correct + assert transaction["value"] == value From 05869ef630075ebd0531c2c2b1e7dde17a640570 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Wed, 4 Dec 2024 15:30:37 +0100 Subject: [PATCH 2/5] Allow to accept bytes in send_xdai_to, add chainId to tx params (#568) --- prediction_market_agent_tooling/tools/web3_utils.py | 11 +++++++++-- pyproject.toml | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/prediction_market_agent_tooling/tools/web3_utils.py b/prediction_market_agent_tooling/tools/web3_utils.py index d98c7df5..cf895c51 100644 --- a/prediction_market_agent_tooling/tools/web3_utils.py +++ b/prediction_market_agent_tooling/tools/web3_utils.py @@ -153,6 +153,9 @@ def _prepare_tx_params( from_checksummed = Web3.to_checksum_address(tx_params_new["from"]) tx_params_new["nonce"] = web3.eth.get_transaction_count(from_checksummed) + if not tx_params_new.get("chainId"): + tx_params_new["chainId"] = web3.eth.chain_id + if access_list is not None: tx_params_new["accessList"] = access_list @@ -297,7 +300,7 @@ def send_xdai_to( from_private_key: PrivateKey, to_address: ChecksumAddress, value: Wei, - data_text: Optional[str] = None, + data_text: Optional[str | bytes] = None, tx_params: Optional[TxParams] = None, timeout: int = 180, ) -> TxReceipt: @@ -305,7 +308,11 @@ def send_xdai_to( tx_params_new: TxParams = {"value": value, "to": to_address} if data_text is not None: - tx_params_new["data"] = Web3.to_bytes(text=data_text) + tx_params_new["data"] = ( + Web3.to_bytes(text=data_text) + if not isinstance(data_text, bytes) + else data_text + ) if tx_params: tx_params_new.update(tx_params) tx_params_new = _prepare_tx_params(web3, from_address, tx_params=tx_params_new) diff --git a/pyproject.toml b/pyproject.toml index fc8209d8..af8ef235 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "prediction-market-agent-tooling" -version = "0.57.4" +version = "0.57.5" description = "Tools to benchmark, deploy and monitor prediction market agents." authors = ["Gnosis"] readme = "README.md" From beacb485c415a60ef153c875e4e3345b212014dd Mon Sep 17 00:00:00 2001 From: Gabriel Fior Date: Wed, 4 Dec 2024 15:50:26 -0300 Subject: [PATCH 3/5] DB Manager <> db_cache fix attempt (#567) * Reduced pool size | passed connection instead of engine * Added missing commit * Removed comment for deploying --- prediction_market_agent_tooling/tools/db/db_manager.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prediction_market_agent_tooling/tools/db/db_manager.py b/prediction_market_agent_tooling/tools/db/db_manager.py index f794f0a3..3ef8f1fb 100644 --- a/prediction_market_agent_tooling/tools/db/db_manager.py +++ b/prediction_market_agent_tooling/tools/db/db_manager.py @@ -33,7 +33,7 @@ def __init__(self, api_keys: APIKeys | None = None) -> None: sqlalchemy_db_url.get_secret_value(), json_serializer=json_serializer, json_deserializer=json_deserializer, - pool_size=20, + pool_size=10, pool_recycle=3600, echo=True, ) @@ -52,7 +52,6 @@ def get_connection(self) -> Generator[Connection, None, None]: def create_tables( self, sqlmodel_tables: Sequence[type[SQLModel]] | None = None ) -> None: - # Determine tables to create if sqlmodel_tables is not None: tables_to_create = [] for sqlmodel_table in sqlmodel_tables: @@ -68,7 +67,9 @@ def create_tables( tables_to_create = None # Create tables in the database - SQLModel.metadata.create_all(self._engine, tables=tables_to_create) + with self.get_connection() as connection: + SQLModel.metadata.create_all(connection, tables=tables_to_create) + connection.commit() # Update cache to mark tables as initialized if tables_to_create: From c6e10fa192c594079a3979bb4e3ad4a11596fdb6 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Thu, 5 Dec 2024 14:30:48 +0100 Subject: [PATCH 4/5] Try to fix amount of connecitons (#570) --- .../tools/db/db_manager.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/prediction_market_agent_tooling/tools/db/db_manager.py b/prediction_market_agent_tooling/tools/db/db_manager.py index 3ef8f1fb..b4a52f89 100644 --- a/prediction_market_agent_tooling/tools/db/db_manager.py +++ b/prediction_market_agent_tooling/tools/db/db_manager.py @@ -28,16 +28,17 @@ def __new__(cls, api_keys: APIKeys | None = None) -> "DBManager": return cls._instances[url_hash] def __init__(self, api_keys: APIKeys | None = None) -> None: + if hasattr(self, "_initialized"): + return sqlalchemy_db_url = (api_keys or APIKeys()).sqlalchemy_db_url self._engine = create_engine( sqlalchemy_db_url.get_secret_value(), json_serializer=json_serializer, json_deserializer=json_deserializer, - pool_size=10, - pool_recycle=3600, - echo=True, + pool_size=2, ) self.cache_table_initialized: dict[str, bool] = {} + self._initialized = True @contextmanager def get_session(self) -> Generator[Session, None, None]: @@ -52,6 +53,7 @@ def get_connection(self) -> Generator[Connection, None, None]: def create_tables( self, sqlmodel_tables: Sequence[type[SQLModel]] | None = None ) -> None: + # Determine tables to create if sqlmodel_tables is not None: tables_to_create = [] for sqlmodel_table in sqlmodel_tables: @@ -67,9 +69,10 @@ def create_tables( tables_to_create = None # Create tables in the database - with self.get_connection() as connection: - SQLModel.metadata.create_all(connection, tables=tables_to_create) - connection.commit() + if tables_to_create is None or len(tables_to_create) > 0: + with self.get_connection() as connection: + SQLModel.metadata.create_all(connection, tables=tables_to_create) + connection.commit() # Update cache to mark tables as initialized if tables_to_create: From a742ced195358e7f01e2ca405d0eba5b74e53cd5 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Fri, 6 Dec 2024 13:48:00 +0100 Subject: [PATCH 5/5] Add NFT contract (#569) --- .../abis/ownable_erc721.abi.json | 336 ++++++++++++++++++ .../tools/contract.py | 69 ++++ pyproject.toml | 2 +- 3 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 prediction_market_agent_tooling/abis/ownable_erc721.abi.json diff --git a/prediction_market_agent_tooling/abis/ownable_erc721.abi.json b/prediction_market_agent_tooling/abis/ownable_erc721.abi.json new file mode 100644 index 00000000..eb8b8491 --- /dev/null +++ b/prediction_market_agent_tooling/abis/ownable_erc721.abi.json @@ -0,0 +1,336 @@ +[ + { + "inputs": [ + { "internalType": "uint256", "name": "maxSupply", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "ERC721IncorrectOwner", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "operator", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "ERC721InsufficientApproval", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "approver", "type": "address" } + ], + "name": "ERC721InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "operator", "type": "address" } + ], + "name": "ERC721InvalidOperator", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "ERC721InvalidOwner", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "receiver", "type": "address" } + ], + "name": "ERC721InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" } + ], + "name": "ERC721InvalidSender", + "type": "error" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "ERC721NonexistentToken", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_SUPPLY", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "getApproved", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "operator", "type": "address" } + ], + "name": "isApprovedForAll", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "ownerOf", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "to", "type": "address" }], + "name": "safeMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "operator", "type": "address" }, + { "internalType": "bool", "name": "approved", "type": "bool" } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } + ], + "name": "supportsInterface", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "tokenURI", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/prediction_market_agent_tooling/tools/contract.py b/prediction_market_agent_tooling/tools/contract.py index b04ed271..ccf508f5 100644 --- a/prediction_market_agent_tooling/tools/contract.py +++ b/prediction_market_agent_tooling/tools/contract.py @@ -382,6 +382,67 @@ def get_in_shares(self, amount: Wei, web3: Web3 | None = None) -> Wei: return self.convertToShares(amount, web3=web3) +class ContractOwnableERC721BaseClass(ContractBaseClass): + abi: ABI = abi_field_validator( + os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "../abis/ownable_erc721.abi.json", + ) + ) + + def safeMint( + self, + api_keys: APIKeys, + to_address: ChecksumAddress, + tx_params: t.Optional[TxParams] = None, + web3: Web3 | None = None, + ) -> TxReceipt: + return self.send( + api_keys=api_keys, + function_name="safeMint", + function_params=[to_address], + tx_params=tx_params, + web3=web3, + ) + + def balanceOf(self, owner: ChecksumAddress, web3: Web3 | None = None) -> int: + balance: int = self.call("balanceOf", [owner], web3=web3) + return balance + + def ownerOf(self, tokenId: int, web3: Web3 | None = None) -> ChecksumAddress: + owner = Web3.to_checksum_address(self.call("ownerOf", [tokenId], web3=web3)) + return owner + + def name(self, web3: Web3 | None = None) -> str: + name: str = self.call("name", web3=web3) + return name + + def symbol(self, web3: Web3 | None = None) -> str: + symbol: str = self.call("symbol", web3=web3) + return symbol + + def tokenURI(self, tokenId: int, web3: Web3 | None = None) -> str: + uri: str = self.call("tokenURI", [tokenId], web3=web3) + return uri + + def safeTransferFrom( + self, + api_keys: APIKeys, + from_address: ChecksumAddress, + to_address: ChecksumAddress, + tokenId: int, + tx_params: t.Optional[TxParams] = None, + web3: Web3 | None = None, + ) -> TxReceipt: + return self.send( + api_keys=api_keys, + function_name="safeTransferFrom", + function_params=[from_address, to_address, tokenId], + tx_params=tx_params, + web3=web3, + ) + + class ContractOnGnosisChain(ContractBaseClass): """ Contract base class with Gnosis Chain configuration. @@ -403,6 +464,14 @@ class ContractERC20OnGnosisChain(ContractERC20BaseClass, ContractOnGnosisChain): """ +class ContractOwnableERC721OnGnosisChain( + ContractOwnableERC721BaseClass, ContractOnGnosisChain +): + """ + Ownable ERC-721 standard base class with Gnosis Chain configuration. + """ + + class ContractDepositableWrapperERC20OnGnosisChain( ContractDepositableWrapperERC20BaseClass, ContractERC20OnGnosisChain ): diff --git a/pyproject.toml b/pyproject.toml index af8ef235..22f7ec95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "prediction-market-agent-tooling" -version = "0.57.5" +version = "0.57.6" description = "Tools to benchmark, deploy and monitor prediction market agents." authors = ["Gnosis"] readme = "README.md"