prediction-market-agent-tooling 0.65.5__py3-none-any.whl → 0.69.17.dev1149__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.
- prediction_market_agent_tooling/abis/agentresultmapping.abi.json +192 -0
- prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
- prediction_market_agent_tooling/abis/processor.abi.json +16 -0
- prediction_market_agent_tooling/abis/swapr_quoter.abi.json +221 -0
- prediction_market_agent_tooling/abis/swapr_router.abi.json +634 -0
- prediction_market_agent_tooling/benchmark/benchmark.py +1 -1
- prediction_market_agent_tooling/benchmark/utils.py +13 -0
- prediction_market_agent_tooling/chains.py +1 -0
- prediction_market_agent_tooling/config.py +61 -2
- prediction_market_agent_tooling/data_download/langfuse_data_downloader.py +405 -0
- prediction_market_agent_tooling/deploy/agent.py +199 -67
- prediction_market_agent_tooling/deploy/agent_example.py +1 -1
- prediction_market_agent_tooling/deploy/betting_strategy.py +412 -68
- prediction_market_agent_tooling/deploy/constants.py +6 -0
- prediction_market_agent_tooling/gtypes.py +11 -1
- prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +19 -20
- prediction_market_agent_tooling/loggers.py +9 -1
- prediction_market_agent_tooling/logprobs_parser.py +2 -1
- prediction_market_agent_tooling/markets/agent_market.py +106 -18
- prediction_market_agent_tooling/markets/blockchain_utils.py +37 -19
- prediction_market_agent_tooling/markets/data_models.py +120 -7
- prediction_market_agent_tooling/markets/manifold/data_models.py +5 -3
- prediction_market_agent_tooling/markets/manifold/manifold.py +21 -2
- prediction_market_agent_tooling/markets/manifold/utils.py +8 -2
- prediction_market_agent_tooling/markets/market_type.py +74 -0
- prediction_market_agent_tooling/markets/markets.py +7 -99
- prediction_market_agent_tooling/markets/metaculus/data_models.py +3 -3
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +5 -8
- prediction_market_agent_tooling/markets/omen/cow_contracts.py +5 -1
- prediction_market_agent_tooling/markets/omen/data_models.py +63 -32
- prediction_market_agent_tooling/markets/omen/omen.py +112 -23
- prediction_market_agent_tooling/markets/omen/omen_constants.py +8 -0
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +18 -203
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +33 -13
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +23 -18
- prediction_market_agent_tooling/markets/polymarket/api.py +123 -100
- prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
- prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
- prediction_market_agent_tooling/markets/polymarket/data_models.py +95 -19
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +373 -29
- prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
- prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +91 -0
- prediction_market_agent_tooling/markets/polymarket/utils.py +1 -22
- prediction_market_agent_tooling/markets/seer/data_models.py +111 -17
- prediction_market_agent_tooling/markets/seer/exceptions.py +2 -0
- prediction_market_agent_tooling/markets/seer/price_manager.py +165 -50
- prediction_market_agent_tooling/markets/seer/seer.py +393 -106
- prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +115 -5
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +297 -66
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +43 -8
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +80 -0
- prediction_market_agent_tooling/tools/_generic_value.py +8 -2
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +271 -8
- prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -1
- prediction_market_agent_tooling/tools/caches/db_cache.py +219 -117
- prediction_market_agent_tooling/tools/caches/serializers.py +11 -2
- prediction_market_agent_tooling/tools/contract.py +480 -38
- prediction_market_agent_tooling/tools/contract_utils.py +61 -0
- prediction_market_agent_tooling/tools/cow/cow_order.py +218 -45
- prediction_market_agent_tooling/tools/cow/models.py +122 -0
- prediction_market_agent_tooling/tools/cow/semaphore.py +104 -0
- prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
- prediction_market_agent_tooling/tools/db/db_manager.py +59 -0
- prediction_market_agent_tooling/tools/hexbytes_custom.py +4 -1
- prediction_market_agent_tooling/tools/httpx_cached_client.py +15 -6
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +21 -8
- prediction_market_agent_tooling/tools/openai_utils.py +31 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_client.py +86 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_models.py +26 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_search.py +73 -0
- prediction_market_agent_tooling/tools/rephrase.py +71 -0
- prediction_market_agent_tooling/tools/singleton.py +11 -6
- prediction_market_agent_tooling/tools/streamlit_utils.py +188 -0
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +64 -0
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
- prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
- prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
- prediction_market_agent_tooling/tools/utils.py +61 -3
- prediction_market_agent_tooling/tools/web3_utils.py +63 -9
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/METADATA +13 -9
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/RECORD +86 -64
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/WHEEL +1 -1
- prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +0 -171
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -420
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/entry_points.txt +0 -0
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info/licenses}/LICENSE +0 -0
|
@@ -5,19 +5,27 @@ import typing as t
|
|
|
5
5
|
from contextlib import contextmanager
|
|
6
6
|
|
|
7
7
|
import eth_abi
|
|
8
|
+
import tenacity
|
|
8
9
|
from eth_abi.exceptions import DecodingError
|
|
9
10
|
from pydantic import BaseModel, field_validator
|
|
10
11
|
from web3 import Web3
|
|
11
|
-
from web3.constants import CHECKSUM_ADDRESSS_ZERO
|
|
12
|
+
from web3.constants import CHECKSUM_ADDRESSS_ZERO, HASH_ZERO
|
|
12
13
|
from web3.contract.contract import Contract as Web3Contract
|
|
14
|
+
from web3.exceptions import ContractCustomError, ContractLogicError
|
|
15
|
+
from web3.types import BlockIdentifier
|
|
13
16
|
|
|
17
|
+
from prediction_market_agent_tooling.chains import POLYGON_CHAIN_ID
|
|
14
18
|
from prediction_market_agent_tooling.config import APIKeys, RPCConfig
|
|
15
19
|
from prediction_market_agent_tooling.gtypes import (
|
|
16
20
|
ABI,
|
|
17
21
|
ChainID,
|
|
18
22
|
ChecksumAddress,
|
|
19
23
|
CollateralToken,
|
|
24
|
+
HexAddress,
|
|
25
|
+
HexBytes,
|
|
26
|
+
HexStr,
|
|
20
27
|
Nonce,
|
|
28
|
+
OutcomeWei,
|
|
21
29
|
TxParams,
|
|
22
30
|
TxReceipt,
|
|
23
31
|
Wei,
|
|
@@ -90,6 +98,7 @@ class ContractBaseClass(BaseModel):
|
|
|
90
98
|
function_name: str,
|
|
91
99
|
function_params: t.Optional[list[t.Any] | dict[str, t.Any]] = None,
|
|
92
100
|
web3: Web3 | None = None,
|
|
101
|
+
block_identifier: BlockIdentifier | None = None,
|
|
93
102
|
) -> t.Any:
|
|
94
103
|
"""
|
|
95
104
|
Used for reading from the contract.
|
|
@@ -101,6 +110,7 @@ class ContractBaseClass(BaseModel):
|
|
|
101
110
|
contract_abi=self.abi,
|
|
102
111
|
function_name=function_name,
|
|
103
112
|
function_params=function_params,
|
|
113
|
+
block_identifier=block_identifier,
|
|
104
114
|
)
|
|
105
115
|
|
|
106
116
|
def send(
|
|
@@ -111,6 +121,7 @@ class ContractBaseClass(BaseModel):
|
|
|
111
121
|
tx_params: t.Optional[TxParams] = None,
|
|
112
122
|
timeout: int = 180,
|
|
113
123
|
web3: Web3 | None = None,
|
|
124
|
+
default_gas: int | None = None,
|
|
114
125
|
) -> TxReceipt:
|
|
115
126
|
"""
|
|
116
127
|
Used for changing a state (writing) to the contract.
|
|
@@ -127,6 +138,7 @@ class ContractBaseClass(BaseModel):
|
|
|
127
138
|
function_params=function_params,
|
|
128
139
|
tx_params=tx_params,
|
|
129
140
|
timeout=timeout,
|
|
141
|
+
default_gas=default_gas,
|
|
130
142
|
)
|
|
131
143
|
return send_function_on_contract_tx(
|
|
132
144
|
web3=web3 or self.get_web3(),
|
|
@@ -137,6 +149,7 @@ class ContractBaseClass(BaseModel):
|
|
|
137
149
|
function_params=function_params,
|
|
138
150
|
tx_params=tx_params,
|
|
139
151
|
timeout=timeout,
|
|
152
|
+
default_gas=default_gas,
|
|
140
153
|
)
|
|
141
154
|
|
|
142
155
|
def send_with_value(
|
|
@@ -186,6 +199,22 @@ class ContractProxyBaseClass(ContractBaseClass):
|
|
|
186
199
|
return Web3.to_checksum_address(address)
|
|
187
200
|
|
|
188
201
|
|
|
202
|
+
class ContractProcessorBaseClass(ContractBaseClass):
|
|
203
|
+
"""
|
|
204
|
+
Contract base class for processor contracts.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
abi: ABI = abi_field_validator(
|
|
208
|
+
os.path.join(
|
|
209
|
+
os.path.dirname(os.path.realpath(__file__)), "../abis/processor.abi.json"
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def processor(self, web3: Web3 | None = None) -> ChecksumAddress:
|
|
214
|
+
address = self.call("processor", web3=web3)
|
|
215
|
+
return Web3.to_checksum_address(address)
|
|
216
|
+
|
|
217
|
+
|
|
189
218
|
class ContractERC20BaseClass(ContractBaseClass):
|
|
190
219
|
"""
|
|
191
220
|
Contract base class extended by ERC-20 standard methods.
|
|
@@ -197,10 +226,18 @@ class ContractERC20BaseClass(ContractBaseClass):
|
|
|
197
226
|
)
|
|
198
227
|
)
|
|
199
228
|
|
|
229
|
+
def name(self, web3: Web3 | None = None) -> str:
|
|
230
|
+
name: str = self.call("name", web3=web3)
|
|
231
|
+
return name
|
|
232
|
+
|
|
200
233
|
def symbol(self, web3: Web3 | None = None) -> str:
|
|
201
234
|
symbol: str = self.call("symbol", web3=web3)
|
|
202
235
|
return symbol
|
|
203
236
|
|
|
237
|
+
def decimals(self, web3: Web3 | None = None) -> int:
|
|
238
|
+
decimals = int(self.call("decimals", web3=web3))
|
|
239
|
+
return decimals
|
|
240
|
+
|
|
204
241
|
def symbol_cached(self, web3: Web3 | None = None) -> str:
|
|
205
242
|
web3 = web3 or self.get_web3()
|
|
206
243
|
cache_key = create_contract_method_cache_key(self.symbol, web3)
|
|
@@ -256,8 +293,17 @@ class ContractERC20BaseClass(ContractBaseClass):
|
|
|
256
293
|
web3=web3,
|
|
257
294
|
)
|
|
258
295
|
|
|
259
|
-
def balanceOf(
|
|
260
|
-
|
|
296
|
+
def balanceOf(
|
|
297
|
+
self,
|
|
298
|
+
for_address: ChecksumAddress,
|
|
299
|
+
web3: Web3 | None = None,
|
|
300
|
+
block_identifier: BlockIdentifier | None = None,
|
|
301
|
+
) -> Wei:
|
|
302
|
+
balance = Wei(
|
|
303
|
+
self.call(
|
|
304
|
+
"balanceOf", [for_address], web3=web3, block_identifier=block_identifier
|
|
305
|
+
)
|
|
306
|
+
)
|
|
261
307
|
return balance
|
|
262
308
|
|
|
263
309
|
def balance_of_in_tokens(
|
|
@@ -413,6 +459,39 @@ class ContractERC4626BaseClass(ContractERC20BaseClass):
|
|
|
413
459
|
return self.convertToShares(amount, web3=web3)
|
|
414
460
|
|
|
415
461
|
|
|
462
|
+
class ContractWrapped1155BaseClass(ContractERC20BaseClass):
|
|
463
|
+
"""Wrapped 1155 contract from Seer (https://gnosisscan.io/address/0x2f9c49974ad8b9b31424d9dc812667b16310ca50#readContract)
|
|
464
|
+
Source code - https://github.com/seer-pm/demo/blob/main/contracts/src/interaction/1155-to-20/Wrapped1155Factory.sol#L224
|
|
465
|
+
This contract inherits from ERC20 and contains additional properties, such as multiToken (conditional Token contract implementation)
|
|
466
|
+
and tokenId (token identifier). Goal is to wrap individual tokens into a standalone ERC20 token.
|
|
467
|
+
"""
|
|
468
|
+
|
|
469
|
+
abi: ABI = abi_field_validator(
|
|
470
|
+
os.path.join(
|
|
471
|
+
os.path.dirname(os.path.realpath(__file__)), "../abis/erc1155.abi.json"
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
def factory(self, web3: Web3 | None = None) -> ChecksumAddress:
|
|
476
|
+
return Web3.to_checksum_address(self.call("factory", web3=web3))
|
|
477
|
+
|
|
478
|
+
def mint(
|
|
479
|
+
self,
|
|
480
|
+
api_keys: APIKeys,
|
|
481
|
+
to_address: ChecksumAddress,
|
|
482
|
+
amount: Wei,
|
|
483
|
+
tx_params: t.Optional[TxParams] = None,
|
|
484
|
+
web3: Web3 | None = None,
|
|
485
|
+
) -> TxReceipt:
|
|
486
|
+
return self.send(
|
|
487
|
+
api_keys=api_keys,
|
|
488
|
+
function_name="mint",
|
|
489
|
+
function_params=[to_address, amount],
|
|
490
|
+
tx_params=tx_params,
|
|
491
|
+
web3=web3,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
|
|
416
495
|
class OwnableContract(ContractBaseClass):
|
|
417
496
|
abi: ABI = abi_field_validator(
|
|
418
497
|
os.path.join(
|
|
@@ -502,9 +581,19 @@ class ContractOnGnosisChain(ContractBaseClass):
|
|
|
502
581
|
Contract base class with Gnosis Chain configuration.
|
|
503
582
|
"""
|
|
504
583
|
|
|
584
|
+
# This is defined like so because other chains (like Ethereum) rely on contracts that inherit
|
|
585
|
+
# from ContractOnGnosisChain. To be re-evaluated on https://github.com/gnosis/prediction-market-agent-tooling/issues/845
|
|
505
586
|
CHAIN_ID = RPCConfig().chain_id
|
|
506
587
|
|
|
507
588
|
|
|
589
|
+
class ContractOnPolygonChain(ContractBaseClass):
|
|
590
|
+
"""
|
|
591
|
+
Contract base class with Gnosis Chain configuration.
|
|
592
|
+
"""
|
|
593
|
+
|
|
594
|
+
CHAIN_ID = POLYGON_CHAIN_ID
|
|
595
|
+
|
|
596
|
+
|
|
508
597
|
class ContractProxyOnGnosisChain(ContractProxyBaseClass, ContractOnGnosisChain):
|
|
509
598
|
"""
|
|
510
599
|
Proxy contract base class with Gnosis Chain configuration.
|
|
@@ -533,6 +622,12 @@ class ContractDepositableWrapperERC20OnGnosisChain(
|
|
|
533
622
|
"""
|
|
534
623
|
|
|
535
624
|
|
|
625
|
+
class ContractWrapped1155OnGnosisChain(
|
|
626
|
+
ContractWrapped1155BaseClass, ContractERC20OnGnosisChain
|
|
627
|
+
):
|
|
628
|
+
pass
|
|
629
|
+
|
|
630
|
+
|
|
536
631
|
class ContractERC4626OnGnosisChain(
|
|
537
632
|
ContractERC4626BaseClass, ContractERC20OnGnosisChain
|
|
538
633
|
):
|
|
@@ -546,6 +641,254 @@ class ContractERC4626OnGnosisChain(
|
|
|
546
641
|
return to_gnosis_chain_contract(super().get_asset_token_contract(web3=web3))
|
|
547
642
|
|
|
548
643
|
|
|
644
|
+
class PayoutRedemptionEvent(BaseModel):
|
|
645
|
+
redeemer: HexAddress
|
|
646
|
+
collateralToken: HexAddress
|
|
647
|
+
parentCollectionId: HexBytes
|
|
648
|
+
conditionId: HexBytes
|
|
649
|
+
indexSets: list[int]
|
|
650
|
+
payout: Wei
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
class ConditionPreparationEvent(BaseModel):
|
|
654
|
+
conditionId: HexBytes
|
|
655
|
+
oracle: HexAddress
|
|
656
|
+
questionId: HexBytes
|
|
657
|
+
outcomeSlotCount: int
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
class ConditionalTokenContract(ContractBaseClass):
|
|
661
|
+
# Contract ABI taken from https://gnosisscan.io/address/0xCeAfDD6bc0bEF976fdCd1112955828E00543c0Ce#code.
|
|
662
|
+
abi: ABI = abi_field_validator(
|
|
663
|
+
os.path.join(
|
|
664
|
+
os.path.dirname(os.path.realpath(__file__)),
|
|
665
|
+
"../abis/omen_fpmm_conditionaltokens.abi.json",
|
|
666
|
+
)
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
def getConditionId(
|
|
670
|
+
self,
|
|
671
|
+
question_id: HexBytes,
|
|
672
|
+
oracle_address: ChecksumAddress,
|
|
673
|
+
outcomes_slot_count: int,
|
|
674
|
+
web3: Web3 | None = None,
|
|
675
|
+
) -> HexBytes:
|
|
676
|
+
id_ = HexBytes(
|
|
677
|
+
self.call(
|
|
678
|
+
"getConditionId",
|
|
679
|
+
[oracle_address, question_id, outcomes_slot_count],
|
|
680
|
+
web3=web3,
|
|
681
|
+
)
|
|
682
|
+
)
|
|
683
|
+
return id_
|
|
684
|
+
|
|
685
|
+
def balanceOf(
|
|
686
|
+
self, from_address: ChecksumAddress, position_id: int, web3: Web3 | None = None
|
|
687
|
+
) -> OutcomeWei:
|
|
688
|
+
balance = OutcomeWei(
|
|
689
|
+
self.call("balanceOf", [from_address, position_id], web3=web3)
|
|
690
|
+
)
|
|
691
|
+
return balance
|
|
692
|
+
|
|
693
|
+
def isApprovedForAll(
|
|
694
|
+
self,
|
|
695
|
+
owner: ChecksumAddress,
|
|
696
|
+
for_address: ChecksumAddress,
|
|
697
|
+
web3: Web3 | None = None,
|
|
698
|
+
) -> bool:
|
|
699
|
+
is_approved: bool = self.call(
|
|
700
|
+
"isApprovedForAll", [owner, for_address], web3=web3
|
|
701
|
+
)
|
|
702
|
+
return is_approved
|
|
703
|
+
|
|
704
|
+
def getCollectionId(
|
|
705
|
+
self,
|
|
706
|
+
parent_collection_id: HexBytes,
|
|
707
|
+
condition_id: HexBytes,
|
|
708
|
+
index_set: int,
|
|
709
|
+
web3: Web3 | None = None,
|
|
710
|
+
) -> HexBytes:
|
|
711
|
+
collection_id = HexBytes(
|
|
712
|
+
self.call(
|
|
713
|
+
"getCollectionId",
|
|
714
|
+
[parent_collection_id, condition_id, index_set],
|
|
715
|
+
web3=web3,
|
|
716
|
+
)
|
|
717
|
+
)
|
|
718
|
+
return collection_id
|
|
719
|
+
|
|
720
|
+
def getPositionId(
|
|
721
|
+
self,
|
|
722
|
+
collateral_token_address: ChecksumAddress,
|
|
723
|
+
collection_id: HexBytes,
|
|
724
|
+
web3: Web3 | None = None,
|
|
725
|
+
) -> int:
|
|
726
|
+
position_id: int = self.call(
|
|
727
|
+
"getPositionId",
|
|
728
|
+
[collateral_token_address, collection_id],
|
|
729
|
+
web3=web3,
|
|
730
|
+
)
|
|
731
|
+
return position_id
|
|
732
|
+
|
|
733
|
+
def mergePositions(
|
|
734
|
+
self,
|
|
735
|
+
api_keys: APIKeys,
|
|
736
|
+
collateral_token_address: ChecksumAddress,
|
|
737
|
+
conditionId: HexBytes,
|
|
738
|
+
index_sets: t.List[int],
|
|
739
|
+
amount: OutcomeWei,
|
|
740
|
+
parent_collection_id: HexStr = HASH_ZERO,
|
|
741
|
+
web3: Web3 | None = None,
|
|
742
|
+
) -> TxReceipt:
|
|
743
|
+
return self.send(
|
|
744
|
+
api_keys=api_keys,
|
|
745
|
+
function_name="mergePositions",
|
|
746
|
+
function_params=[
|
|
747
|
+
collateral_token_address,
|
|
748
|
+
parent_collection_id,
|
|
749
|
+
conditionId,
|
|
750
|
+
index_sets,
|
|
751
|
+
amount,
|
|
752
|
+
],
|
|
753
|
+
web3=web3,
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
def redeemPositions(
|
|
757
|
+
self,
|
|
758
|
+
api_keys: APIKeys,
|
|
759
|
+
collateral_token_address: HexAddress,
|
|
760
|
+
condition_id: HexBytes,
|
|
761
|
+
index_sets: t.List[int],
|
|
762
|
+
parent_collection_id: HexStr = HASH_ZERO,
|
|
763
|
+
web3: Web3 | None = None,
|
|
764
|
+
) -> PayoutRedemptionEvent:
|
|
765
|
+
receipt_tx = self.send(
|
|
766
|
+
api_keys=api_keys,
|
|
767
|
+
function_name="redeemPositions",
|
|
768
|
+
function_params=[
|
|
769
|
+
collateral_token_address,
|
|
770
|
+
parent_collection_id,
|
|
771
|
+
condition_id,
|
|
772
|
+
index_sets,
|
|
773
|
+
],
|
|
774
|
+
web3=web3,
|
|
775
|
+
)
|
|
776
|
+
redeem_event_logs = (
|
|
777
|
+
self.get_web3_contract(web3=web3)
|
|
778
|
+
.events.PayoutRedemption()
|
|
779
|
+
.process_receipt(receipt_tx)
|
|
780
|
+
)
|
|
781
|
+
logger.info(
|
|
782
|
+
f"Receipt tx: `{receipt_tx}` Redeem event logs: `{redeem_event_logs}`"
|
|
783
|
+
)
|
|
784
|
+
redeem_event = PayoutRedemptionEvent(**redeem_event_logs[0]["args"])
|
|
785
|
+
return redeem_event
|
|
786
|
+
|
|
787
|
+
def getOutcomeSlotCount(
|
|
788
|
+
self, condition_id: HexBytes, web3: Web3 | None = None
|
|
789
|
+
) -> int:
|
|
790
|
+
count: int = self.call("getOutcomeSlotCount", [condition_id], web3=web3)
|
|
791
|
+
return count
|
|
792
|
+
|
|
793
|
+
def does_condition_exists(
|
|
794
|
+
self, condition_id: HexBytes, web3: Web3 | None = None
|
|
795
|
+
) -> bool:
|
|
796
|
+
return self.getOutcomeSlotCount(condition_id, web3=web3) > 0
|
|
797
|
+
|
|
798
|
+
def is_condition_resolved(
|
|
799
|
+
self, condition_id: HexBytes, web3: Web3 | None = None
|
|
800
|
+
) -> bool:
|
|
801
|
+
# from ConditionalTokens.redeemPositions:
|
|
802
|
+
# uint den = payoutDenominator[conditionId]; require(den > 0, "result for condition not received yet");
|
|
803
|
+
payout_for_condition = self.payoutDenominator(condition_id, web3=web3)
|
|
804
|
+
return payout_for_condition > 0
|
|
805
|
+
|
|
806
|
+
def payoutDenominator(
|
|
807
|
+
self, condition_id: HexBytes, web3: Web3 | None = None
|
|
808
|
+
) -> int:
|
|
809
|
+
payoutForCondition: int = self.call(
|
|
810
|
+
"payoutDenominator", [condition_id], web3=web3
|
|
811
|
+
)
|
|
812
|
+
return payoutForCondition
|
|
813
|
+
|
|
814
|
+
def splitPosition(
|
|
815
|
+
self,
|
|
816
|
+
api_keys: APIKeys,
|
|
817
|
+
collateral_token: ChecksumAddress,
|
|
818
|
+
condition_id: HexBytes,
|
|
819
|
+
outcome_slot_count: int,
|
|
820
|
+
amount_wei: Wei,
|
|
821
|
+
parent_collateral_id: HexBytes = HexBytes(HASH_ZERO),
|
|
822
|
+
tx_params: t.Optional[TxParams] = None,
|
|
823
|
+
web3: Web3 | None = None,
|
|
824
|
+
) -> TxReceipt:
|
|
825
|
+
# We always split the full set of outcome tokens (for simplicity)
|
|
826
|
+
# partitions are given in bitmasks, i.e. outcomes are 1,2,4,8,etc.
|
|
827
|
+
partition = [2**i for i in range(outcome_slot_count)]
|
|
828
|
+
return self.send(
|
|
829
|
+
api_keys=api_keys,
|
|
830
|
+
function_name="splitPosition",
|
|
831
|
+
function_params=[
|
|
832
|
+
collateral_token,
|
|
833
|
+
parent_collateral_id,
|
|
834
|
+
condition_id,
|
|
835
|
+
partition,
|
|
836
|
+
amount_wei,
|
|
837
|
+
],
|
|
838
|
+
tx_params=tx_params,
|
|
839
|
+
web3=web3,
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
def setApprovalForAll(
|
|
843
|
+
self,
|
|
844
|
+
api_keys: APIKeys,
|
|
845
|
+
for_address: ChecksumAddress,
|
|
846
|
+
approve: bool,
|
|
847
|
+
tx_params: t.Optional[TxParams] = None,
|
|
848
|
+
web3: Web3 | None = None,
|
|
849
|
+
) -> TxReceipt:
|
|
850
|
+
return self.send(
|
|
851
|
+
api_keys=api_keys,
|
|
852
|
+
function_name="setApprovalForAll",
|
|
853
|
+
function_params=[
|
|
854
|
+
for_address,
|
|
855
|
+
approve,
|
|
856
|
+
],
|
|
857
|
+
tx_params=tx_params,
|
|
858
|
+
web3=web3,
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
def prepareCondition(
|
|
862
|
+
self,
|
|
863
|
+
api_keys: APIKeys,
|
|
864
|
+
oracle_address: ChecksumAddress,
|
|
865
|
+
question_id: HexBytes,
|
|
866
|
+
outcomes_slot_count: int,
|
|
867
|
+
tx_params: t.Optional[TxParams] = None,
|
|
868
|
+
web3: Web3 | None = None,
|
|
869
|
+
) -> ConditionPreparationEvent:
|
|
870
|
+
receipt_tx = self.send(
|
|
871
|
+
api_keys=api_keys,
|
|
872
|
+
function_name="prepareCondition",
|
|
873
|
+
function_params=[
|
|
874
|
+
oracle_address,
|
|
875
|
+
question_id,
|
|
876
|
+
outcomes_slot_count,
|
|
877
|
+
],
|
|
878
|
+
tx_params=tx_params,
|
|
879
|
+
web3=web3,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
event_logs = (
|
|
883
|
+
self.get_web3_contract(web3=web3)
|
|
884
|
+
.events.ConditionPreparation()
|
|
885
|
+
.process_receipt(receipt_tx)
|
|
886
|
+
)
|
|
887
|
+
cond_event = ConditionPreparationEvent(**event_logs[0]["args"])
|
|
888
|
+
|
|
889
|
+
return cond_event
|
|
890
|
+
|
|
891
|
+
|
|
549
892
|
class DebuggingContract(ContractOnGnosisChain):
|
|
550
893
|
# Contract ABI taken from https://gnosisscan.io/address/0x5Aa82E068aE6a6a1C26c42E5a59520a74Cdb8998#code.
|
|
551
894
|
abi: ABI = abi_field_validator(
|
|
@@ -594,64 +937,154 @@ def contract_implements_function(
|
|
|
594
937
|
look_for_proxy_contract: bool = True,
|
|
595
938
|
) -> bool:
|
|
596
939
|
function_signature = f"{function_name}({','.join(function_arg_types or [])})"
|
|
597
|
-
function_selector = web3.keccak(text=function_signature)[0:4].
|
|
940
|
+
function_selector = web3.keccak(text=function_signature)[0:4].to_0x_hex()[2:]
|
|
598
941
|
# 1. Check directly in bytecode
|
|
599
|
-
bytecode = web3.eth.get_code(contract_address).
|
|
942
|
+
bytecode = web3.eth.get_code(contract_address).to_0x_hex()
|
|
600
943
|
if function_selector in bytecode:
|
|
601
944
|
return True
|
|
602
|
-
contract_code = web3.eth.get_code(contract_address).
|
|
945
|
+
contract_code = web3.eth.get_code(contract_address).to_0x_hex()
|
|
603
946
|
implements = function_selector in contract_code
|
|
604
947
|
|
|
605
948
|
# If not found directly and we should check proxies
|
|
606
949
|
if not implements and look_for_proxy_contract:
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
contract_address, "implementation", web3, look_for_proxy_contract=False
|
|
610
|
-
):
|
|
611
|
-
# Get the implementation address and check the function there
|
|
612
|
-
implementation_address = ContractProxyOnGnosisChain(
|
|
613
|
-
address=contract_address
|
|
614
|
-
).implementation()
|
|
950
|
+
imp_addresses = uni_implementation_address(contract_address, web3)
|
|
951
|
+
for imp_address in imp_addresses:
|
|
615
952
|
implements = contract_implements_function(
|
|
616
|
-
|
|
953
|
+
imp_address,
|
|
617
954
|
function_name=function_name,
|
|
618
955
|
web3=web3,
|
|
619
956
|
function_arg_types=function_arg_types,
|
|
620
957
|
look_for_proxy_contract=False,
|
|
621
958
|
)
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
contract_address=contract_address,
|
|
626
|
-
function_name=function_name,
|
|
627
|
-
web3=web3,
|
|
628
|
-
function_arg_types=function_arg_types,
|
|
629
|
-
)
|
|
959
|
+
# If one of the implementations has the function, we can terminate early.
|
|
960
|
+
if implements:
|
|
961
|
+
break
|
|
630
962
|
|
|
631
963
|
return implements
|
|
632
964
|
|
|
633
965
|
|
|
634
|
-
def
|
|
635
|
-
contract_address: ChecksumAddress,
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
966
|
+
def uni_implementation_address(
|
|
967
|
+
contract_address: ChecksumAddress, web3: Web3
|
|
968
|
+
) -> list[ChecksumAddress]:
|
|
969
|
+
"""
|
|
970
|
+
There are multiple ways how proxies can be implemented.
|
|
971
|
+
This function enumerates them and returns the ones that succeed, or an empty list.
|
|
972
|
+
"""
|
|
973
|
+
# Currently, our implementation sometimes returns implementation multiple addresses for a single proxy contract.
|
|
974
|
+
# This should not happen and needs more investigation.
|
|
975
|
+
# Probably `minimal_proxy_address` is bugged and a function identifying the ERC-1967 proxy pattern (https://eips.ethereum.org/EIPS/eip-1967#logic-contract-address) should be implemented.
|
|
976
|
+
# TODO: Fix the above, and afterwards assert that only 1 imp address is returned from this function. Or prove that this could indeed happen (although we are very pretty sure it shouldn't).
|
|
977
|
+
addresses = [
|
|
978
|
+
implementation_proxy_address(contract_address, web3),
|
|
979
|
+
processor_proxy_address(contract_address, web3),
|
|
980
|
+
minimal_proxy_address(contract_address, web3),
|
|
981
|
+
seer_minimal_proxy_address(contract_address, web3),
|
|
982
|
+
eip_1967_proxy_address(contract_address, web3),
|
|
983
|
+
zeppelinos_unstructured_storage_proxy_address(contract_address, web3),
|
|
984
|
+
]
|
|
985
|
+
return [addr for addr in addresses if addr is not None]
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
def implementation_proxy_address(
|
|
989
|
+
contract_address: ChecksumAddress, web3: Web3
|
|
990
|
+
) -> ChecksumAddress | None:
|
|
991
|
+
if not contract_implements_function(
|
|
992
|
+
contract_address, "implementation", web3, look_for_proxy_contract=False
|
|
993
|
+
):
|
|
994
|
+
return None
|
|
995
|
+
try:
|
|
996
|
+
return ContractProxyOnGnosisChain(address=contract_address).implementation(web3)
|
|
997
|
+
except (ContractCustomError, ContractLogicError, tenacity.RetryError) as e:
|
|
998
|
+
if isinstance(e, tenacity.RetryError) and not isinstance(
|
|
999
|
+
e.last_attempt.exception(), (ContractCustomError, ContractLogicError)
|
|
1000
|
+
):
|
|
1001
|
+
raise
|
|
1002
|
+
|
|
1003
|
+
# For example https://gnosisscan.io/address/0x3221a28ed2b2e955da64d1d299956f277562c95c#code,
|
|
1004
|
+
# it has the implementation method, but it's only for admins.
|
|
1005
|
+
logger.warning(
|
|
1006
|
+
f"Failed to get implementation for {contract_address=} even though it has the method: {e}"
|
|
1007
|
+
)
|
|
1008
|
+
return None
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
def processor_proxy_address(
|
|
1012
|
+
contract_address: ChecksumAddress, web3: Web3
|
|
1013
|
+
) -> ChecksumAddress | None:
|
|
1014
|
+
if not contract_implements_function(
|
|
1015
|
+
contract_address, "processor", web3, look_for_proxy_contract=False
|
|
1016
|
+
):
|
|
1017
|
+
return None
|
|
1018
|
+
try:
|
|
1019
|
+
return ContractProcessorBaseClass(address=contract_address).processor(web3)
|
|
1020
|
+
except (ContractCustomError, ContractLogicError, tenacity.RetryError) as e:
|
|
1021
|
+
if isinstance(e, tenacity.RetryError) and not isinstance(
|
|
1022
|
+
e.last_attempt.exception(), (ContractCustomError, ContractLogicError)
|
|
1023
|
+
):
|
|
1024
|
+
raise
|
|
1025
|
+
|
|
1026
|
+
# For example https://gnosisscan.io/address/0x3221a28ed2b2e955da64d1d299956f277562c95c#code,
|
|
1027
|
+
# it has the processor method, but it's only for admins.
|
|
1028
|
+
logger.warning(
|
|
1029
|
+
f"Failed to get processor for {contract_address=} even though it has the method: {e}"
|
|
1030
|
+
)
|
|
1031
|
+
return None
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
def minimal_proxy_address(
|
|
1035
|
+
contract_address: ChecksumAddress, web3: Web3
|
|
1036
|
+
) -> ChecksumAddress | None:
|
|
640
1037
|
try:
|
|
641
1038
|
# Read storage slot 0 which should contain the implementation address in minimal proxies
|
|
642
1039
|
raw_slot_0 = web3.eth.get_storage_at(contract_address, 0)
|
|
643
1040
|
singleton_address = eth_abi.decode(["address"], raw_slot_0)[0]
|
|
644
|
-
|
|
645
|
-
return contract_implements_function(
|
|
646
|
-
Web3.to_checksum_address(singleton_address),
|
|
647
|
-
function_name=function_name,
|
|
648
|
-
web3=web3,
|
|
649
|
-
function_arg_types=function_arg_types,
|
|
650
|
-
look_for_proxy_contract=False,
|
|
651
|
-
)
|
|
1041
|
+
return Web3.to_checksum_address(singleton_address)
|
|
652
1042
|
except DecodingError:
|
|
653
1043
|
logger.info(f"Error decoding contract address for singleton")
|
|
654
|
-
return
|
|
1044
|
+
return None
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
def seer_minimal_proxy_address(
|
|
1048
|
+
contract_address: ChecksumAddress, web3: Web3
|
|
1049
|
+
) -> ChecksumAddress | None:
|
|
1050
|
+
try:
|
|
1051
|
+
# Read address between specific indices to find logic contract
|
|
1052
|
+
bytecode = web3.eth.get_code(contract_address)
|
|
1053
|
+
logic_contract_address = bytecode[11:31]
|
|
1054
|
+
if not Web3.is_address(logic_contract_address):
|
|
1055
|
+
return None
|
|
1056
|
+
return Web3.to_checksum_address(logic_contract_address)
|
|
1057
|
+
except DecodingError:
|
|
1058
|
+
logger.info("Error decoding contract address on seer minimal proxy")
|
|
1059
|
+
return None
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
def eip_1967_proxy_address(
|
|
1063
|
+
contract_address: ChecksumAddress, web3: Web3
|
|
1064
|
+
) -> ChecksumAddress | None:
|
|
1065
|
+
try:
|
|
1066
|
+
slot = HexBytes(Web3.keccak(text="eip1967.proxy.implementation")).as_int() - 1
|
|
1067
|
+
raw_slot = web3.eth.get_storage_at(contract_address, slot)
|
|
1068
|
+
address = eth_abi.decode(["address"], raw_slot)[0]
|
|
1069
|
+
return Web3.to_checksum_address(address)
|
|
1070
|
+
except DecodingError:
|
|
1071
|
+
logger.info("Error decoding contract address for eip 1967 proxy")
|
|
1072
|
+
return None
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
def zeppelinos_unstructured_storage_proxy_address(
|
|
1076
|
+
contract_address: ChecksumAddress, web3: Web3
|
|
1077
|
+
) -> ChecksumAddress | None:
|
|
1078
|
+
try:
|
|
1079
|
+
slot = HexBytes(
|
|
1080
|
+
Web3.keccak(text="org.zeppelinos.proxy.implementation")
|
|
1081
|
+
).as_int()
|
|
1082
|
+
raw_slot = web3.eth.get_storage_at(contract_address, slot)
|
|
1083
|
+
address = eth_abi.decode(["address"], raw_slot)[0]
|
|
1084
|
+
return Web3.to_checksum_address(address)
|
|
1085
|
+
except DecodingError:
|
|
1086
|
+
logger.info("Error decoding contract address for zeppelinos proxy")
|
|
1087
|
+
return None
|
|
655
1088
|
|
|
656
1089
|
|
|
657
1090
|
def init_collateral_token_contract(
|
|
@@ -673,6 +1106,13 @@ def init_collateral_token_contract(
|
|
|
673
1106
|
):
|
|
674
1107
|
return ContractDepositableWrapperERC20BaseClass(address=address)
|
|
675
1108
|
|
|
1109
|
+
elif contract_implements_function(
|
|
1110
|
+
address,
|
|
1111
|
+
"multiToken",
|
|
1112
|
+
web3=web3,
|
|
1113
|
+
):
|
|
1114
|
+
return ContractWrapped1155BaseClass(address=address)
|
|
1115
|
+
|
|
676
1116
|
elif contract_implements_function(
|
|
677
1117
|
address,
|
|
678
1118
|
"balanceOf",
|
|
@@ -694,6 +1134,8 @@ def to_gnosis_chain_contract(
|
|
|
694
1134
|
return ContractERC4626OnGnosisChain(address=contract.address)
|
|
695
1135
|
elif isinstance(contract, ContractDepositableWrapperERC20BaseClass):
|
|
696
1136
|
return ContractDepositableWrapperERC20OnGnosisChain(address=contract.address)
|
|
1137
|
+
elif isinstance(contract, ContractWrapped1155BaseClass):
|
|
1138
|
+
return ContractWrapped1155OnGnosisChain(address=contract.address)
|
|
697
1139
|
elif isinstance(contract, ContractERC20BaseClass):
|
|
698
1140
|
return ContractERC20OnGnosisChain(address=contract.address)
|
|
699
1141
|
else:
|