iwa 0.0.14__py3-none-any.whl → 0.0.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iwa/core/contracts/contract.py +27 -7
- iwa/core/pricing.py +18 -7
- iwa/plugins/olas/service_manager/base.py +7 -3
- {iwa-0.0.14.dist-info → iwa-0.0.15.dist-info}/METADATA +1 -1
- {iwa-0.0.14.dist-info → iwa-0.0.15.dist-info}/RECORD +10 -10
- tests/test_contract.py +9 -1
- {iwa-0.0.14.dist-info → iwa-0.0.15.dist-info}/WHEEL +0 -0
- {iwa-0.0.14.dist-info → iwa-0.0.15.dist-info}/entry_points.txt +0 -0
- {iwa-0.0.14.dist-info → iwa-0.0.15.dist-info}/licenses/LICENSE +0 -0
- {iwa-0.0.14.dist-info → iwa-0.0.15.dist-info}/top_level.txt +0 -0
iwa/core/contracts/contract.py
CHANGED
|
@@ -19,7 +19,18 @@ logger = configure_logger()
|
|
|
19
19
|
ERROR_SELECTOR = "0x08c379a0" # Error(string)
|
|
20
20
|
PANIC_SELECTOR = "0x4e487b71" # Panic(uint256)
|
|
21
21
|
|
|
22
|
+
# Global cache for ABIs and error selectors to avoid redundant disk I/O and parsing
|
|
23
|
+
# Format: {abi_path: {"abi": [...], "selectors": {...}}}
|
|
24
|
+
_ABI_CACHE: Dict[str, Dict[str, Any]] = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def clear_abi_cache() -> None:
|
|
28
|
+
"""Clear the global ABI cache (mainly for testing)."""
|
|
29
|
+
global _ABI_CACHE
|
|
30
|
+
_ABI_CACHE = {}
|
|
31
|
+
|
|
22
32
|
# Panic codes (from Solidity)
|
|
33
|
+
# ... (rest of PANIC_CODES) ...
|
|
23
34
|
PANIC_CODES = {
|
|
24
35
|
0x00: "Generic compiler inserted panic",
|
|
25
36
|
0x01: "Assert failed",
|
|
@@ -46,16 +57,25 @@ class ContractInstance:
|
|
|
46
57
|
self.abi = None
|
|
47
58
|
self.chain_interface = ChainInterfaces().get(chain_name)
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
# Check global cache first
|
|
61
|
+
cache_key = str(self.abi_path)
|
|
62
|
+
if cache_key in _ABI_CACHE:
|
|
63
|
+
self.abi = _ABI_CACHE[cache_key]["abi"]
|
|
64
|
+
self.error_selectors = _ABI_CACHE[cache_key]["selectors"]
|
|
65
|
+
else:
|
|
66
|
+
with open(self.abi_path, "r", encoding="utf-8") as abi_file:
|
|
67
|
+
contract_abi = json.load(abi_file)
|
|
68
|
+
|
|
69
|
+
if isinstance(contract_abi, dict) and "abi" in contract_abi:
|
|
70
|
+
self.abi = contract_abi.get("abi")
|
|
71
|
+
else:
|
|
72
|
+
self.abi = contract_abi
|
|
51
73
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
self.abi = contract_abi
|
|
74
|
+
self.error_selectors = self.load_error_selectors()
|
|
75
|
+
# Store in global cache
|
|
76
|
+
_ABI_CACHE[cache_key] = {"abi": self.abi, "selectors": self.error_selectors}
|
|
56
77
|
|
|
57
78
|
self._contract_cache = None
|
|
58
|
-
self.error_selectors = self.load_error_selectors()
|
|
59
79
|
|
|
60
80
|
@property
|
|
61
81
|
def contract(self) -> Contract:
|
iwa/core/pricing.py
CHANGED
|
@@ -12,12 +12,14 @@ from iwa.core.secrets import secrets
|
|
|
12
12
|
# Global cache shared across all PriceService instances
|
|
13
13
|
_PRICE_CACHE: Dict[str, Dict] = {}
|
|
14
14
|
_CACHE_TTL = timedelta(minutes=30)
|
|
15
|
+
_NEGATIVE_CACHE_TTL = timedelta(minutes=5)
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class PriceService:
|
|
18
19
|
"""Service to fetch token prices from CoinGecko."""
|
|
19
20
|
|
|
20
21
|
BASE_URL = "https://api.coingecko.com/api/v3"
|
|
22
|
+
DEMO_URL = "https://demo-api.coingecko.com/api/v3"
|
|
21
23
|
|
|
22
24
|
def __init__(self):
|
|
23
25
|
"""Initialize PriceService."""
|
|
@@ -41,15 +43,16 @@ class PriceService:
|
|
|
41
43
|
"""
|
|
42
44
|
cache_key = f"{token_id}_{vs_currency}"
|
|
43
45
|
|
|
44
|
-
# Check global cache
|
|
46
|
+
# Check global cache (including negative cache)
|
|
45
47
|
if cache_key in _PRICE_CACHE:
|
|
46
48
|
entry = _PRICE_CACHE[cache_key]
|
|
47
|
-
|
|
49
|
+
ttl = _CACHE_TTL if entry["price"] is not None else _NEGATIVE_CACHE_TTL
|
|
50
|
+
if datetime.now() - entry["timestamp"] < ttl:
|
|
48
51
|
return entry["price"]
|
|
49
52
|
|
|
50
53
|
price = self._fetch_price_from_api(token_id, vs_currency)
|
|
51
|
-
if price is
|
|
52
|
-
|
|
54
|
+
# We always cache, even if price is None (negative caching)
|
|
55
|
+
_PRICE_CACHE[cache_key] = {"price": price, "timestamp": datetime.now()}
|
|
53
56
|
return price
|
|
54
57
|
|
|
55
58
|
def _fetch_price_from_api(self, token_id: str, vs_currency: str) -> Optional[float]:
|
|
@@ -57,7 +60,10 @@ class PriceService:
|
|
|
57
60
|
max_retries = 2
|
|
58
61
|
for attempt in range(max_retries + 1):
|
|
59
62
|
try:
|
|
60
|
-
|
|
63
|
+
# Use demo URL if API key is present, otherwise standard URL
|
|
64
|
+
# NOTE: Demo URL is significantly more reliable for demo keys
|
|
65
|
+
base_url = self.DEMO_URL if self.api_key else self.BASE_URL
|
|
66
|
+
url = f"{base_url}/simple/price"
|
|
61
67
|
params = {"ids": token_id, "vs_currencies": vs_currency}
|
|
62
68
|
headers = {}
|
|
63
69
|
if self.api_key:
|
|
@@ -69,6 +75,8 @@ class PriceService:
|
|
|
69
75
|
logger.warning("CoinGecko API key invalid (401). Retrying without key...")
|
|
70
76
|
self.api_key = None
|
|
71
77
|
headers.pop("x-cg-demo-api-key", None)
|
|
78
|
+
# Re-run with base URL
|
|
79
|
+
url = f"{self.BASE_URL}/simple/price"
|
|
72
80
|
response = requests.get(url, params=params, headers=headers, timeout=10)
|
|
73
81
|
|
|
74
82
|
if response.status_code == 429:
|
|
@@ -87,13 +95,16 @@ class PriceService:
|
|
|
87
95
|
if token_id in data and vs_currency in data[token_id]:
|
|
88
96
|
return float(data[token_id][vs_currency])
|
|
89
97
|
|
|
90
|
-
|
|
98
|
+
# If we got response but price not found, it's likely a wrong ID
|
|
99
|
+
logger.debug(
|
|
91
100
|
f"Price for {token_id} in {vs_currency} not found in response: {data}"
|
|
92
101
|
)
|
|
93
102
|
return None
|
|
94
103
|
|
|
95
104
|
except Exception as e:
|
|
96
|
-
|
|
105
|
+
# Only log error on last attempt to avoid spamming
|
|
106
|
+
if attempt == max_retries:
|
|
107
|
+
logger.error(f"Failed to fetch price for {token_id}: {e}")
|
|
97
108
|
if attempt < max_retries:
|
|
98
109
|
time.sleep(1)
|
|
99
110
|
continue
|
|
@@ -51,6 +51,10 @@ class ServiceManagerBase:
|
|
|
51
51
|
|
|
52
52
|
def _init_contracts(self, chain_name: str) -> None:
|
|
53
53
|
"""Initialize contracts for the given chain."""
|
|
54
|
+
# OPTIMIZATION: Skip if already initialized for this chain
|
|
55
|
+
if getattr(self, "chain_name", None) == chain_name.lower() and hasattr(self, "registry"):
|
|
56
|
+
return
|
|
57
|
+
|
|
54
58
|
chain_interface = ChainInterfaces().get(chain_name)
|
|
55
59
|
|
|
56
60
|
# Get protocol contracts from plugin-local constants
|
|
@@ -63,9 +67,9 @@ class ServiceManagerBase:
|
|
|
63
67
|
|
|
64
68
|
self.registry = ServiceRegistryContract(registry_address, chain_name=chain_name)
|
|
65
69
|
self.manager = ServiceManagerContract(manager_address, chain_name=chain_name)
|
|
66
|
-
logger.
|
|
67
|
-
logger.
|
|
68
|
-
logger.
|
|
70
|
+
logger.debug(f"[SM-INIT] ServiceManager initialized. Chain: {chain_name}")
|
|
71
|
+
logger.debug(f"[SM-INIT] Registry Address: {self.registry.address}")
|
|
72
|
+
logger.debug(f"[SM-INIT] Manager Address: {self.manager.address}")
|
|
69
73
|
self.chain_interface = chain_interface
|
|
70
74
|
self.chain_name = chain_name.lower()
|
|
71
75
|
|
|
@@ -10,7 +10,7 @@ iwa/core/mnemonic.py,sha256=LiG1VmpydQoHQ0pHUJ1OIlrWJry47VSMnOqPM_Yk-O8,12930
|
|
|
10
10
|
iwa/core/models.py,sha256=kBQ0cBe6uFmL2QfW7mjKiMFeZxhT-FRN-RyK3Ko0vE8,12849
|
|
11
11
|
iwa/core/monitor.py,sha256=OmhKVMkfhvtxig3wDUL6iGwBIClTx0YUqMncCao4SqI,7953
|
|
12
12
|
iwa/core/plugins.py,sha256=FLvOG4S397fKi0aTH1fWBEtexn4yvGv_QzGWqFrhSKE,1102
|
|
13
|
-
iwa/core/pricing.py,sha256=
|
|
13
|
+
iwa/core/pricing.py,sha256=U-vIjJwzh6O7MZTAFZx6mFFxaiLcwn6PnsZc4g0z4IQ,4293
|
|
14
14
|
iwa/core/secrets.py,sha256=U7DZKrwKuSOFV00Ij3ISrrO1cWn_t1GBW_0PyAqjcD4,2588
|
|
15
15
|
iwa/core/tables.py,sha256=y7Cg67PAGHYVMVyAjbo_CQ9t2iz7UXE-OTuUHRyFRTo,2021
|
|
16
16
|
iwa/core/test.py,sha256=gey0dql5eajo1itOhgkSrgfyGWue2eSfpr0xzX3vc38,643
|
|
@@ -25,7 +25,7 @@ iwa/core/chain/manager.py,sha256=cFEzh6pK5OyVhjhpeMAqhc9RnRDQR1DjIGiGKp-FXBI,115
|
|
|
25
25
|
iwa/core/chain/models.py,sha256=0OgBo08FZEQisOdd00YUMXSAV7BC0CcWpqJ2y-gs0cI,4863
|
|
26
26
|
iwa/core/chain/rate_limiter.py,sha256=gU7TmWdH9D_wbXKT1X7mIgoIUCWVuebgvRhxiyLGAmI,6613
|
|
27
27
|
iwa/core/contracts/__init__.py,sha256=P5GFY_pnuI02teqVY2U0t98bn1_SSPAbcAzRMpCdTi4,34
|
|
28
|
-
iwa/core/contracts/contract.py,sha256=
|
|
28
|
+
iwa/core/contracts/contract.py,sha256=N2I4-38O_6awqVrTEdJFh4XhsMbeu2JtFvkboUC4ZMg,12517
|
|
29
29
|
iwa/core/contracts/erc20.py,sha256=VqriOdUXej0ilTgpukpm1FUF_9sSrVMAPuEpIvyZ2SQ,2646
|
|
30
30
|
iwa/core/contracts/multisend.py,sha256=tSdBCWe7LSdBoKZ7z2QebmRFK4M2ln7H3kmJBeEb4Ho,2431
|
|
31
31
|
iwa/core/contracts/abis/erc20.json,sha256=vrdExMWcIogg_nO59j1Pmipmpa2Ulj3oCCdcdrrFVCE,16995
|
|
@@ -79,7 +79,7 @@ iwa/plugins/olas/contracts/abis/staking_token.json,sha256=cuUOmi1s4Z6VSIX0an_IxK
|
|
|
79
79
|
iwa/plugins/olas/scripts/test_full_mech_flow.py,sha256=id9IxC06FOKwexgwsG5nbsTB2rQLlIq5a5soMvK0IgY,9021
|
|
80
80
|
iwa/plugins/olas/scripts/test_simple_lifecycle.py,sha256=8T50tOZx3afeECSfCNAb0rAHNtYOsBaeXlMwKXElCk8,2099
|
|
81
81
|
iwa/plugins/olas/service_manager/__init__.py,sha256=GXiThMEY3nPgHUl1i-DLrF4h96z9jPxxI8Jepo2E1PM,1926
|
|
82
|
-
iwa/plugins/olas/service_manager/base.py,sha256=
|
|
82
|
+
iwa/plugins/olas/service_manager/base.py,sha256=CCTH7RiYtgyFwRszrMLxNf1rNM_6leWHuJJmse4m2wI,4854
|
|
83
83
|
iwa/plugins/olas/service_manager/drain.py,sha256=IS7YYKuQdkULcNdxfHVzjcq95pXKdpajolzLL78u4jc,12430
|
|
84
84
|
iwa/plugins/olas/service_manager/lifecycle.py,sha256=DIB6yrP0VPICu6558uQJuFp2sgrA66iVNTzZVUUowGw,47159
|
|
85
85
|
iwa/plugins/olas/service_manager/mech.py,sha256=72-tEap-aYd0gebcH6y_De1SNeL6OrXn7sWuv_Ok_-Y,12224
|
|
@@ -150,7 +150,7 @@ iwa/web/tests/test_web_endpoints.py,sha256=C264MH-CTyDW4GLUrTXBgLJKUk4-89pFAScBd
|
|
|
150
150
|
iwa/web/tests/test_web_olas.py,sha256=0CVSsrncOeJ3x0ECV7mVLQV_CXZRrOqGiVjgLIi6hZ8,16308
|
|
151
151
|
iwa/web/tests/test_web_swap.py,sha256=7A4gBJFL01kIXPtW1E1J17SCsVc_0DmUn-R8kKrnnVA,2974
|
|
152
152
|
iwa/web/tests/test_web_swap_coverage.py,sha256=zGNrzlhZ_vWDCvWmLcoUwFgqxnrp_ACbo49AtWBS_Kw,5584
|
|
153
|
-
iwa-0.0.
|
|
153
|
+
iwa-0.0.15.dist-info/licenses/LICENSE,sha256=eIubm_IlBHPYRQlLNZKbBNKhJUUP3JH0A2miZUhAVfI,1078
|
|
154
154
|
tests/legacy_cow.py,sha256=oOkZvIxL70ReEoD9oHQbOD5GpjIr6AGNHcOCgfPlerU,8389
|
|
155
155
|
tests/legacy_safe.py,sha256=AssM2g13E74dNGODu_H0Q0y412lgqsrYnEzI97nm_Ts,2972
|
|
156
156
|
tests/legacy_transaction_retry_logic.py,sha256=D9RqZ7DBu61Xr2djBAodU2p9UE939LL-DnQXswX5iQk,1497
|
|
@@ -163,7 +163,7 @@ tests/test_chain.py,sha256=z3Mo9mHNQZ0aXSlrHUcdtNgGqsNyOwWYQAmmKa_dqiM,17221
|
|
|
163
163
|
tests/test_chain_interface.py,sha256=Wu0q0sREtmYBp7YvWrBIrrSTtqeQj18oJp2VmMUEMec,8312
|
|
164
164
|
tests/test_chain_interface_coverage.py,sha256=fvrVvw8-DMwdsSFKQHUhpbfutrVRxnnTc-tjB7Bb-jo,3327
|
|
165
165
|
tests/test_cli.py,sha256=WW6EDeHLws5-BqFNOy11pH_D5lttuyspD5hrDCFpR0Q,3968
|
|
166
|
-
tests/test_contract.py,sha256=
|
|
166
|
+
tests/test_contract.py,sha256=tApHAxsfKGawYJWA9PhTNrOZUE0VVAq79ruIe3KxeWY,14412
|
|
167
167
|
tests/test_db.py,sha256=dmbrupj0qlUeiiycZ2mzMFjf7HrDa6tcqMPY8zpiKIk,5710
|
|
168
168
|
tests/test_drain_coverage.py,sha256=jtN5tIXzSTlS2IjwLS60azyMYsjFDlSTUa98JM1bMic,6786
|
|
169
169
|
tests/test_erc20.py,sha256=kNEw1afpm5EbXRNXkjpkBNZIy7Af1nqGlztKH5IWAwU,3074
|
|
@@ -202,8 +202,8 @@ tests/test_utils.py,sha256=vkP49rYNI8BRzLpWR3WnKdDr8upeZjZcs7Rx0pjbQMo,1292
|
|
|
202
202
|
tests/test_workers.py,sha256=MInwdkFY5LdmFB3o1odIaSD7AQZb3263hNafO1De5PE,2793
|
|
203
203
|
tools/create_and_stake_service.py,sha256=1xwy_bJQI1j9yIQ968Oc9Db_F6mk1659LuuZntTASDE,3742
|
|
204
204
|
tools/verify_drain.py,sha256=PkMjblyOOAuQge88FwfEzRtCYeEtJxXhPBmtQYCoQ-8,6743
|
|
205
|
-
iwa-0.0.
|
|
206
|
-
iwa-0.0.
|
|
207
|
-
iwa-0.0.
|
|
208
|
-
iwa-0.0.
|
|
209
|
-
iwa-0.0.
|
|
205
|
+
iwa-0.0.15.dist-info/METADATA,sha256=J9pChdhUaRH85yUje-9tNp2xCTdncgmxJHnMuV9kv7M,7295
|
|
206
|
+
iwa-0.0.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
207
|
+
iwa-0.0.15.dist-info/entry_points.txt,sha256=nwB6kscrfA7M00pYmL2j-sBH6eF6h2ga9IK1BZxdiyQ,241
|
|
208
|
+
iwa-0.0.15.dist-info/top_level.txt,sha256=kedS9cRUbm4JE2wYeabIXilhHjN8KCw0IGbqqqsw0Bs,16
|
|
209
|
+
iwa-0.0.15.dist-info/RECORD,,
|
tests/test_contract.py
CHANGED
|
@@ -4,7 +4,15 @@ from unittest.mock import MagicMock, mock_open, patch
|
|
|
4
4
|
import pytest
|
|
5
5
|
from web3.exceptions import ContractCustomError
|
|
6
6
|
|
|
7
|
-
from iwa.core.contracts.contract import ContractInstance
|
|
7
|
+
from iwa.core.contracts.contract import ContractInstance, clear_abi_cache
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(autouse=True)
|
|
11
|
+
def clean_abi_cache():
|
|
12
|
+
"""Clear global ABI cache before each test."""
|
|
13
|
+
clear_abi_cache()
|
|
14
|
+
yield
|
|
15
|
+
clear_abi_cache()
|
|
8
16
|
|
|
9
17
|
|
|
10
18
|
@pytest.fixture
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|