diff --git a/examples/deposit_wallet_execute.py b/examples/deposit_wallet_execute.py index 8304e77..44969b1 100644 --- a/examples/deposit_wallet_execute.py +++ b/examples/deposit_wallet_execute.py @@ -41,7 +41,7 @@ def main(): token = to_checksum_address(os.getenv("USDC_ADDRESS")) spender = to_checksum_address(os.getenv("SPENDER_ADDRESS")) nonce = os.getenv("DEPOSIT_WALLET_NONCE", "0") - deadline = os.getenv("DEPOSIT_WALLET_DEADLINE", str(int(time.time()) + 240)) + deadline = os.getenv("DEPOSIT_WALLET_DEADLINE", str(int(time.time()) + 600)) approve_data = encode_approve( spender, diff --git a/py_builder_relayer_client/client.py b/py_builder_relayer_client/client.py index 5f94ce5..112ad6c 100644 --- a/py_builder_relayer_client/client.py +++ b/py_builder_relayer_client/client.py @@ -13,7 +13,10 @@ is_safe_config_valid, is_deposit_wallet_config_valid, ) -from .constants.constants import ZERO_ADDRESS +from .constants.constants import ( + MIN_DEPOSIT_WALLET_DEADLINE_BUFFER_SECONDS, + ZERO_ADDRESS, +) from .gas import estimate_gas, DEFAULT_GAS_LIMIT from .http_helpers.helpers import get, post, POST from .builder.derive import derive, derive_proxy_wallet, derive_deposit_wallet @@ -323,6 +326,8 @@ def execute_deposit_wallet_batch( "Deposit wallet contracts are not configured for this chain" ) + self._validate_deposit_wallet_deadline(deadline) + from_address = self.signer.address() args = DepositWalletTransactionArgs( from_address=from_address, @@ -348,6 +353,21 @@ def execute_deposit_wallet_batch( self, ) + def _validate_deposit_wallet_deadline(self, deadline: str): + try: + deadline_ts = int(deadline) + except (TypeError, ValueError): + raise RelayerClientException( + "deposit wallet deadline must be a unix timestamp" + ) + + min_deadline = int(time.time()) + MIN_DEPOSIT_WALLET_DEADLINE_BUFFER_SECONDS + if deadline_ts < min_deadline: + raise RelayerClientException( + "deposit wallet deadline must be at least " + f"{MIN_DEPOSIT_WALLET_DEADLINE_BUFFER_SECONDS} seconds in the future" + ) + def poll_until_state( self, transaction_id: str, diff --git a/py_builder_relayer_client/constants/constants.py b/py_builder_relayer_client/constants/constants.py index 8261084..f50fd92 100644 --- a/py_builder_relayer_client/constants/constants.py +++ b/py_builder_relayer_client/constants/constants.py @@ -12,3 +12,4 @@ DEPOSIT_WALLET_DOMAIN_NAME = "DepositWallet" DEPOSIT_WALLET_DOMAIN_VERSION = "1" +MIN_DEPOSIT_WALLET_DEADLINE_BUFFER_SECONDS = 300 diff --git a/setup.py b/setup.py index 386d281..bafcf8a 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="py_builder_relayer_client", - version="0.0.2rc1", + version="0.0.2rc2", author="Polymarket Engineering", author_email="engineering@polymarket.com", maintainer="Polymarket Engineering", diff --git a/tests/test_client_deposit_wallet.py b/tests/test_client_deposit_wallet.py index 57d64df..86df050 100644 --- a/tests/test_client_deposit_wallet.py +++ b/tests/test_client_deposit_wallet.py @@ -2,6 +2,7 @@ from unittest.mock import Mock, patch from py_builder_relayer_client.client import RelayClient +from py_builder_relayer_client.exceptions import RelayerClientException from py_builder_relayer_client.http_helpers.helpers import POST from py_builder_relayer_client.models import DepositWalletCall, TransactionType from py_builder_relayer_client.endpoints import SUBMIT_TRANSACTION @@ -62,12 +63,13 @@ def test_deploy_deposit_wallet_posts_wallet_create(self): def test_execute_deposit_wallet_batch_posts_wallet_request(self): client = self._client() call = DepositWalletCall(target=TOKEN, value="0", data=APPROVE_CALLDATA) - resp = client.execute_deposit_wallet_batch( - calls=[call], - wallet_address=WALLET, - nonce="0", - deadline="1234567890", - ) + with patch("py_builder_relayer_client.client.time.time", return_value=1000): + resp = client.execute_deposit_wallet_batch( + calls=[call], + wallet_address=WALLET, + nonce="0", + deadline="1600", + ) method, path, body = client._post_request.call_args[0] self.assertEqual(POST, method) @@ -81,9 +83,54 @@ def test_execute_deposit_wallet_batch_posts_wallet_request(self): self.assertEqual( { "depositWallet": WALLET, - "deadline": "1234567890", + "deadline": "1600", "calls": [call.to_dict()], }, body["depositWalletParams"], ) self.assertEqual("test-txn", resp.transaction_id) + + def test_execute_deposit_wallet_batch_rejects_deadline_too_soon(self): + client = self._client() + call = DepositWalletCall(target=TOKEN, value="0", data=APPROVE_CALLDATA) + + with patch("py_builder_relayer_client.client.time.time", return_value=1000): + with self.assertRaises(RelayerClientException) as ctx: + client.execute_deposit_wallet_batch( + calls=[call], + wallet_address=WALLET, + nonce="0", + deadline="1299", + ) + + self.assertIn("at least 300 seconds", ctx.exception.msg) + client._post_request.assert_not_called() + + def test_execute_deposit_wallet_batch_accepts_min_deadline_boundary(self): + client = self._client() + call = DepositWalletCall(target=TOKEN, value="0", data=APPROVE_CALLDATA) + + with patch("py_builder_relayer_client.client.time.time", return_value=1000): + client.execute_deposit_wallet_batch( + calls=[call], + wallet_address=WALLET, + nonce="0", + deadline="1300", + ) + + self.assertTrue(client._post_request.called) + + def test_execute_deposit_wallet_batch_rejects_invalid_deadline(self): + client = self._client() + call = DepositWalletCall(target=TOKEN, value="0", data=APPROVE_CALLDATA) + + with self.assertRaises(RelayerClientException) as ctx: + client.execute_deposit_wallet_batch( + calls=[call], + wallet_address=WALLET, + nonce="0", + deadline="not-a-timestamp", + ) + + self.assertIn("unix timestamp", ctx.exception.msg) + client._post_request.assert_not_called()