From c207cd48c6ab6f7eb87a96e9bdcac4d5646f5a9f Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Thu, 26 Sep 2024 12:41:25 +0200 Subject: [PATCH 1/9] Integrate Tenderly to local_web3 --- prediction_market_agent_tooling/config.py | 1 + .../conftest.py | 45 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/prediction_market_agent_tooling/config.py b/prediction_market_agent_tooling/config.py index 312acf22..c124625b 100644 --- a/prediction_market_agent_tooling/config.py +++ b/prediction_market_agent_tooling/config.py @@ -35,6 +35,7 @@ class APIKeys(BaseSettings): SAFE_ADDRESS: t.Optional[ChecksumAddress] = None OPENAI_API_KEY: t.Optional[SecretStr] = None GRAPH_API_KEY: t.Optional[SecretStr] = None + TENDERLY_FORK_RPC: t.Optional[str] = None GOOGLE_SEARCH_API_KEY: t.Optional[SecretStr] = None GOOGLE_SEARCH_ENGINE_ID: t.Optional[SecretStr] = None diff --git a/tests_integration_with_local_chain/conftest.py b/tests_integration_with_local_chain/conftest.py index 55fed91c..d281ff51 100644 --- a/tests_integration_with_local_chain/conftest.py +++ b/tests_integration_with_local_chain/conftest.py @@ -1,6 +1,7 @@ import typing as t import pytest +import requests from ape.managers import ChainManager from ape_test import TestAccount from dotenv import load_dotenv @@ -8,7 +9,14 @@ from web3 import Web3 from prediction_market_agent_tooling.config import APIKeys -from prediction_market_agent_tooling.gtypes import private_key_type +from prediction_market_agent_tooling.gtypes import ( + HexAddress, + int_to_hexbytes, + private_key_type, + xDai, + xdai_type, +) +from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei @pytest.fixture(autouse=True, scope="session") @@ -17,13 +25,26 @@ def load_env() -> None: @pytest.fixture(scope="session") -def local_web3(load_env: None, chain: ChainManager) -> t.Generator[Web3, None, None]: +def local_web3( + load_env: None, chain: ChainManager, accounts: list[TestAccount] +) -> t.Generator[Web3, None, None]: print("entering fixture local_web3") - with chain.network_manager.parse_network_choice( - "gnosis:mainnet_fork:foundry" - ) as provider: - w3 = Web3(Web3.HTTPProvider(provider.http_uri)) + + if (tenderly_fork_rpc := APIKeys().TENDERLY_FORK_RPC) is not None: + print("using tenderly rpc") + w3 = Web3(Web3.HTTPProvider(tenderly_fork_rpc)) + print("funding test accounts on tenderly") + fund_account_on_tenderly( + tenderly_fork_rpc, [a.address for a in accounts], xdai_type(1000) + ) yield w3 + else: + print("using foundry") + with chain.network_manager.parse_network_choice( + "gnosis:mainnet_fork:foundry" + ) as provider: + w3 = Web3(Web3.HTTPProvider(provider.http_uri)) + yield w3 print("exiting fixture local_web3") @@ -41,3 +62,15 @@ def test_keys(accounts: list[TestAccount]) -> APIKeys: return APIKeys( BET_FROM_PRIVATE_KEY=private_key_type(account.private_key), SAFE_ADDRESS=None ) + + +def fund_account_on_tenderly( + fork_rpc: str, addresses: list[HexAddress], balance: xDai +) -> None: + payload = { + "jsonrpc": "2.0", + "method": "tenderly_setBalance", + "params": [addresses, f"0x{xdai_to_wei(balance):X}"], + } + response = requests.post(fork_rpc, json=payload) + response.raise_for_status() From 6e352b0e01db037f4b959df07dcf0ded29edbe97 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Thu, 26 Sep 2024 12:43:04 +0200 Subject: [PATCH 2/9] lint --- tests_integration_with_local_chain/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests_integration_with_local_chain/conftest.py b/tests_integration_with_local_chain/conftest.py index d281ff51..1bb9d8ec 100644 --- a/tests_integration_with_local_chain/conftest.py +++ b/tests_integration_with_local_chain/conftest.py @@ -11,7 +11,6 @@ from prediction_market_agent_tooling.config import APIKeys from prediction_market_agent_tooling.gtypes import ( HexAddress, - int_to_hexbytes, private_key_type, xDai, xdai_type, From 1cdb9f7e497f3a37bb6dc07f0d82bca3dd525898 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Thu, 26 Sep 2024 12:58:40 +0200 Subject: [PATCH 3/9] Add a new contract used for debugging --- .../abis/debuggingcontract.abi.json | 29 +++++++++++ .../tools/contract.py | 50 ++++++++++++++++++- .../markets/omen/test_local_chain.py | 39 +++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 prediction_market_agent_tooling/abis/debuggingcontract.abi.json diff --git a/prediction_market_agent_tooling/abis/debuggingcontract.abi.json b/prediction_market_agent_tooling/abis/debuggingcontract.abi.json new file mode 100644 index 00000000..c5478b81 --- /dev/null +++ b/prediction_market_agent_tooling/abis/debuggingcontract.abi.json @@ -0,0 +1,29 @@ +[ + { + "constant": false, + "inputs": [], + "name": "inc", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "counter", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getNow", + "outputs": [{ "name": "", "type": "uint32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/prediction_market_agent_tooling/tools/contract.py b/prediction_market_agent_tooling/tools/contract.py index 761985cf..eb3b51ee 100644 --- a/prediction_market_agent_tooling/tools/contract.py +++ b/prediction_market_agent_tooling/tools/contract.py @@ -3,7 +3,9 @@ import time import typing as t from contextlib import contextmanager +from datetime import datetime +import pytz from pydantic import BaseModel, field_validator from web3 import Web3 @@ -21,7 +23,11 @@ GNOSIS_NETWORK_ID, GNOSIS_RPC_URL, ) -from prediction_market_agent_tooling.tools.utils import should_not_happen +from prediction_market_agent_tooling.tools.utils import ( + DatetimeWithTimezone, + add_utc_timezone_validator, + should_not_happen, +) from prediction_market_agent_tooling.tools.web3_utils import ( call_function_on_contract, send_function_on_contract_tx, @@ -423,6 +429,48 @@ def get_asset_token_contract( return to_gnosis_chain_contract(super().get_asset_token_contract(web3=web3)) +class DebuggingContract(ContractOnGnosisChain): + # Contract ABI taken from https://gnosisscan.io/address/0x5Aa82E068aE6a6a1C26c42E5a59520a74Cdb8998#code. + abi: ABI = abi_field_validator( + os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "../abis/debuggingcontract.abi.json", + ) + ) + address: ChecksumAddress = Web3.to_checksum_address( + "0x5Aa82E068aE6a6a1C26c42E5a59520a74Cdb8998" + ) + + def getNow( + self, + web3: Web3 | None = None, + ) -> int: + now: int = self.call( + function_name="getNow", + web3=web3, + ) + return now + + def get_now( + self, + web3: Web3 | None = None, + ) -> DatetimeWithTimezone: + return add_utc_timezone_validator( + datetime.fromtimestamp(self.getNow(web3), tz=pytz.UTC) + ) + + def inc( + self, + api_keys: APIKeys, + web3: Web3 | None = None, + ) -> TxReceipt: + return self.send( + api_keys=api_keys, + function_name="inc", + web3=web3, + ) + + def contract_implements_function( contract_address: ChecksumAddress, function_name: str, 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 9e842151..9c674c67 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,3 +1,6 @@ +import time +from datetime import timedelta + from ape_test import TestAccount from eth_account import Account from numpy import isclose @@ -10,6 +13,8 @@ is_minimum_required_balance, ) from prediction_market_agent_tooling.tools.balances import get_balances +from prediction_market_agent_tooling.tools.contract import DebuggingContract +from prediction_market_agent_tooling.tools.utils import utcnow from prediction_market_agent_tooling.tools.web3_utils import ( send_xdai_to, wei_to_xdai, @@ -93,3 +98,37 @@ def test_fresh_account_has_less_than_minimum_required_balance( fresh_account_adr = Account.create().address account_adr = Web3.to_checksum_address(fresh_account_adr) assert not is_minimum_required_balance(account_adr, xdai_type(0.5), local_web3) + + +def test_now(local_web3: Web3, test_keys: APIKeys) -> None: + # we need to mint a new block to update timestamp + DebuggingContract().inc(test_keys, local_web3) + allowed_difference = 10 # seconds + chain_timestamp = DebuggingContract().getNow(local_web3) + utc_timestamp = int(utcnow().timestamp()) + assert ( + abs(chain_timestamp - utc_timestamp) <= allowed_difference + ), f"chain_timestamp and utc_timestamp differ by more than {allowed_difference} seconds: {chain_timestamp=} {utc_timestamp=}" + + +def test_now_failed(local_web3: Web3, test_keys: APIKeys) -> None: + # Sleep a little to let the local chain go out of sync without updating the block + time.sleep(5) + allowed_difference = 10 # seconds + chain_timestamp = DebuggingContract().getNow(local_web3) + utc_timestamp = int(utcnow().timestamp()) + assert ( + abs(chain_timestamp - utc_timestamp) >= allowed_difference + ), f"without minting a new block, timestamps should differ by more than {allowed_difference} seconds: {chain_timestamp=} {utc_timestamp=}" + + +def test_now_datetime(local_web3: Web3, test_keys: APIKeys) -> None: + # we need to mint a new block to update timestamp + DebuggingContract().inc(test_keys, local_web3) + allowed_difference = 10 # seconds + chain_datetime = DebuggingContract().get_now(local_web3) + utc_datetime = utcnow() + actual_difference = abs(chain_datetime - utc_datetime) + assert actual_difference <= timedelta( + seconds=allowed_difference + ), f"chain_datetime and utc_datetime differ by more than {allowed_difference} seconds: {chain_datetime=} {utc_datetime=} {actual_difference=}" From 0312c5520924503d691c8437cea1c9a63621555b Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 30 Sep 2024 09:01:17 +0200 Subject: [PATCH 4/9] Use `mint_new_block` --- prediction_market_agent_tooling/tools/web3_utils.py | 10 ++++++++++ .../markets/omen/test_local_chain.py | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/prediction_market_agent_tooling/tools/web3_utils.py b/prediction_market_agent_tooling/tools/web3_utils.py index edbdd62e..2bf483cd 100644 --- a/prediction_market_agent_tooling/tools/web3_utils.py +++ b/prediction_market_agent_tooling/tools/web3_utils.py @@ -12,6 +12,7 @@ from web3.constants import HASH_ZERO from web3.types import AccessList, AccessListEntry, Nonce, TxParams, TxReceipt, Wei +from prediction_market_agent_tooling.config import APIKeys from prediction_market_agent_tooling.gtypes import ( ABI, ChecksumAddress, @@ -24,6 +25,7 @@ xdai_type, ) from prediction_market_agent_tooling.loggers import logger +from prediction_market_agent_tooling.tools.contract import DebuggingContract ONE_NONCE = Nonce(1) ONE_XDAI = xdai_type(1) @@ -335,3 +337,11 @@ def byte32_to_ipfscidv0(hex: HexBytes) -> IPFSCIDVersion0: """ completed_binary_str = b"\x12 " + hex return IPFSCIDVersion0(base58.b58encode(completed_binary_str).decode("utf-8")) + + +def mint_new_block(keys: APIKeys, web3: Web3) -> None: + """ + Mints a new block on the web3's blockchain. + Useful for tests that debends on chain's timestamp, this will update it. + """ + DebuggingContract().inc(keys, web3) 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 9c674c67..2ed13362 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 @@ -16,6 +16,7 @@ from prediction_market_agent_tooling.tools.contract import DebuggingContract from prediction_market_agent_tooling.tools.utils import utcnow from prediction_market_agent_tooling.tools.web3_utils import ( + mint_new_block, send_xdai_to, wei_to_xdai, xdai_to_wei, @@ -102,7 +103,7 @@ def test_fresh_account_has_less_than_minimum_required_balance( def test_now(local_web3: Web3, test_keys: APIKeys) -> None: # we need to mint a new block to update timestamp - DebuggingContract().inc(test_keys, local_web3) + mint_new_block(test_keys, local_web3) allowed_difference = 10 # seconds chain_timestamp = DebuggingContract().getNow(local_web3) utc_timestamp = int(utcnow().timestamp()) @@ -124,7 +125,7 @@ def test_now_failed(local_web3: Web3, test_keys: APIKeys) -> None: def test_now_datetime(local_web3: Web3, test_keys: APIKeys) -> None: # we need to mint a new block to update timestamp - DebuggingContract().inc(test_keys, local_web3) + mint_new_block(test_keys, local_web3) allowed_difference = 10 # seconds chain_datetime = DebuggingContract().get_now(local_web3) utc_datetime = utcnow() From 75d1f1ee8d5bff29661c7710273cabd1c5c6824d Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 30 Sep 2024 09:03:50 +0200 Subject: [PATCH 5/9] lower the difference --- .../markets/omen/test_local_chain.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 2ed13362..5acba8fe 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 @@ -104,7 +104,7 @@ def test_fresh_account_has_less_than_minimum_required_balance( def test_now(local_web3: Web3, test_keys: APIKeys) -> None: # we need to mint a new block to update timestamp mint_new_block(test_keys, local_web3) - allowed_difference = 10 # seconds + allowed_difference = 5 # seconds chain_timestamp = DebuggingContract().getNow(local_web3) utc_timestamp = int(utcnow().timestamp()) assert ( @@ -115,7 +115,7 @@ def test_now(local_web3: Web3, test_keys: APIKeys) -> None: def test_now_failed(local_web3: Web3, test_keys: APIKeys) -> None: # Sleep a little to let the local chain go out of sync without updating the block time.sleep(5) - allowed_difference = 10 # seconds + allowed_difference = 5 # seconds chain_timestamp = DebuggingContract().getNow(local_web3) utc_timestamp = int(utcnow().timestamp()) assert ( @@ -126,7 +126,7 @@ def test_now_failed(local_web3: Web3, test_keys: APIKeys) -> None: def test_now_datetime(local_web3: Web3, test_keys: APIKeys) -> None: # we need to mint a new block to update timestamp mint_new_block(test_keys, local_web3) - allowed_difference = 10 # seconds + allowed_difference = 5 # seconds chain_datetime = DebuggingContract().get_now(local_web3) utc_datetime = utcnow() actual_difference = abs(chain_datetime - utc_datetime) From e8694219197ee2c1f941a803f7a2dd8dbaaf3ca3 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 30 Sep 2024 09:13:44 +0200 Subject: [PATCH 6/9] fix circular import --- prediction_market_agent_tooling/tools/web3_utils.py | 10 ---------- tests/utils.py | 13 +++++++++++++ .../markets/omen/test_local_chain.py | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/prediction_market_agent_tooling/tools/web3_utils.py b/prediction_market_agent_tooling/tools/web3_utils.py index 2bf483cd..edbdd62e 100644 --- a/prediction_market_agent_tooling/tools/web3_utils.py +++ b/prediction_market_agent_tooling/tools/web3_utils.py @@ -12,7 +12,6 @@ from web3.constants import HASH_ZERO from web3.types import AccessList, AccessListEntry, Nonce, TxParams, TxReceipt, Wei -from prediction_market_agent_tooling.config import APIKeys from prediction_market_agent_tooling.gtypes import ( ABI, ChecksumAddress, @@ -25,7 +24,6 @@ xdai_type, ) from prediction_market_agent_tooling.loggers import logger -from prediction_market_agent_tooling.tools.contract import DebuggingContract ONE_NONCE = Nonce(1) ONE_XDAI = xdai_type(1) @@ -337,11 +335,3 @@ def byte32_to_ipfscidv0(hex: HexBytes) -> IPFSCIDVersion0: """ completed_binary_str = b"\x12 " + hex return IPFSCIDVersion0(base58.b58encode(completed_binary_str).decode("utf-8")) - - -def mint_new_block(keys: APIKeys, web3: Web3) -> None: - """ - Mints a new block on the web3's blockchain. - Useful for tests that debends on chain's timestamp, this will update it. - """ - DebuggingContract().inc(keys, web3) diff --git a/tests/utils.py b/tests/utils.py index f2cf1cdc..b7ed8589 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,3 +1,16 @@ import os +from web3 import Web3 + +from prediction_market_agent_tooling.config import APIKeys +from prediction_market_agent_tooling.tools.contract import DebuggingContract + RUN_PAID_TESTS = os.environ.get("RUN_PAID_TESTS", "0") == "1" + + +def mint_new_block(keys: APIKeys, web3: Web3) -> None: + """ + Mints a new block on the web3's blockchain. + Useful for tests that debends on chain's timestamp, this will update it. + """ + DebuggingContract().inc(keys, web3) 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 5acba8fe..f984a72f 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 @@ -16,11 +16,11 @@ from prediction_market_agent_tooling.tools.contract import DebuggingContract from prediction_market_agent_tooling.tools.utils import utcnow from prediction_market_agent_tooling.tools.web3_utils import ( - mint_new_block, send_xdai_to, wei_to_xdai, xdai_to_wei, ) +from tests.utils import mint_new_block def test_connect_local_chain(local_web3: Web3) -> None: From 4930a23cb176adab68e55f13e1010850b7eb290f Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 30 Sep 2024 09:21:57 +0200 Subject: [PATCH 7/9] fix another test --- tests_integration_with_local_chain/markets/omen/test_omen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests_integration_with_local_chain/markets/omen/test_omen.py b/tests_integration_with_local_chain/markets/omen/test_omen.py index b8719625..a7189959 100644 --- a/tests_integration_with_local_chain/markets/omen/test_omen.py +++ b/tests_integration_with_local_chain/markets/omen/test_omen.py @@ -382,7 +382,7 @@ def test_place_bet_with_autodeposit( # Check that we have xdai funds, but no wxdai funds initial_balances = get_balances(address=test_keys.bet_from_address, web3=local_web3) - assert initial_balances.wxdai == xdai_type(0) + assert np.isclose(initial_balances.wxdai, xdai_type(0)) assert initial_balances.xdai > xdai_type(0) # Try to place a bet with 90% of the xDai funds From ec70b3523eced5cbe080a3aab598a8be5ccdaf3a Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 30 Sep 2024 12:15:34 +0200 Subject: [PATCH 8/9] be less strict --- .../markets/omen/test_local_chain.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 f984a72f..bafeece2 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 @@ -104,7 +104,7 @@ def test_fresh_account_has_less_than_minimum_required_balance( def test_now(local_web3: Web3, test_keys: APIKeys) -> None: # we need to mint a new block to update timestamp mint_new_block(test_keys, local_web3) - allowed_difference = 5 # seconds + allowed_difference = 15 # seconds chain_timestamp = DebuggingContract().getNow(local_web3) utc_timestamp = int(utcnow().timestamp()) assert ( @@ -126,10 +126,10 @@ def test_now_failed(local_web3: Web3, test_keys: APIKeys) -> None: def test_now_datetime(local_web3: Web3, test_keys: APIKeys) -> None: # we need to mint a new block to update timestamp mint_new_block(test_keys, local_web3) - allowed_difference = 5 # seconds + allowed_difference = 15 # seconds chain_datetime = DebuggingContract().get_now(local_web3) utc_datetime = utcnow() - actual_difference = abs(chain_datetime - utc_datetime) - assert actual_difference <= timedelta( - seconds=allowed_difference + actual_difference = (utc_datetime - chain_datetime).total_seconds() + assert ( + actual_difference <= allowed_difference ), f"chain_datetime and utc_datetime differ by more than {allowed_difference} seconds: {chain_datetime=} {utc_datetime=} {actual_difference=}" From 51ac55f7bcb3b8ad085c5367413266355a15a911 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 30 Sep 2024 12:19:33 +0200 Subject: [PATCH 9/9] lint --- .../markets/omen/test_local_chain.py | 1 - 1 file changed, 1 deletion(-) 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 bafeece2..e6fd14e2 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,4 @@ import time -from datetime import timedelta from ape_test import TestAccount from eth_account import Account