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/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 9e842151..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,3 +1,5 @@ +import time + from ape_test import TestAccount from eth_account import Account from numpy import isclose @@ -10,11 +12,14 @@ 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, xdai_to_wei, ) +from tests.utils import mint_new_block def test_connect_local_chain(local_web3: Web3) -> None: @@ -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 + mint_new_block(test_keys, local_web3) + allowed_difference = 15 # 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 = 5 # 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 + mint_new_block(test_keys, local_web3) + allowed_difference = 15 # seconds + chain_datetime = DebuggingContract().get_now(local_web3) + utc_datetime = utcnow() + 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=}" 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