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.
Files changed (88) hide show
  1. prediction_market_agent_tooling/abis/agentresultmapping.abi.json +192 -0
  2. prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
  3. prediction_market_agent_tooling/abis/processor.abi.json +16 -0
  4. prediction_market_agent_tooling/abis/swapr_quoter.abi.json +221 -0
  5. prediction_market_agent_tooling/abis/swapr_router.abi.json +634 -0
  6. prediction_market_agent_tooling/benchmark/benchmark.py +1 -1
  7. prediction_market_agent_tooling/benchmark/utils.py +13 -0
  8. prediction_market_agent_tooling/chains.py +1 -0
  9. prediction_market_agent_tooling/config.py +61 -2
  10. prediction_market_agent_tooling/data_download/langfuse_data_downloader.py +405 -0
  11. prediction_market_agent_tooling/deploy/agent.py +199 -67
  12. prediction_market_agent_tooling/deploy/agent_example.py +1 -1
  13. prediction_market_agent_tooling/deploy/betting_strategy.py +412 -68
  14. prediction_market_agent_tooling/deploy/constants.py +6 -0
  15. prediction_market_agent_tooling/gtypes.py +11 -1
  16. prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
  17. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +19 -20
  18. prediction_market_agent_tooling/loggers.py +9 -1
  19. prediction_market_agent_tooling/logprobs_parser.py +2 -1
  20. prediction_market_agent_tooling/markets/agent_market.py +106 -18
  21. prediction_market_agent_tooling/markets/blockchain_utils.py +37 -19
  22. prediction_market_agent_tooling/markets/data_models.py +120 -7
  23. prediction_market_agent_tooling/markets/manifold/data_models.py +5 -3
  24. prediction_market_agent_tooling/markets/manifold/manifold.py +21 -2
  25. prediction_market_agent_tooling/markets/manifold/utils.py +8 -2
  26. prediction_market_agent_tooling/markets/market_type.py +74 -0
  27. prediction_market_agent_tooling/markets/markets.py +7 -99
  28. prediction_market_agent_tooling/markets/metaculus/data_models.py +3 -3
  29. prediction_market_agent_tooling/markets/metaculus/metaculus.py +5 -8
  30. prediction_market_agent_tooling/markets/omen/cow_contracts.py +5 -1
  31. prediction_market_agent_tooling/markets/omen/data_models.py +63 -32
  32. prediction_market_agent_tooling/markets/omen/omen.py +112 -23
  33. prediction_market_agent_tooling/markets/omen/omen_constants.py +8 -0
  34. prediction_market_agent_tooling/markets/omen/omen_contracts.py +18 -203
  35. prediction_market_agent_tooling/markets/omen/omen_resolving.py +33 -13
  36. prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +23 -18
  37. prediction_market_agent_tooling/markets/polymarket/api.py +123 -100
  38. prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
  39. prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
  40. prediction_market_agent_tooling/markets/polymarket/data_models.py +95 -19
  41. prediction_market_agent_tooling/markets/polymarket/polymarket.py +373 -29
  42. prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
  43. prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +91 -0
  44. prediction_market_agent_tooling/markets/polymarket/utils.py +1 -22
  45. prediction_market_agent_tooling/markets/seer/data_models.py +111 -17
  46. prediction_market_agent_tooling/markets/seer/exceptions.py +2 -0
  47. prediction_market_agent_tooling/markets/seer/price_manager.py +165 -50
  48. prediction_market_agent_tooling/markets/seer/seer.py +393 -106
  49. prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
  50. prediction_market_agent_tooling/markets/seer/seer_contracts.py +115 -5
  51. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +297 -66
  52. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +43 -8
  53. prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +80 -0
  54. prediction_market_agent_tooling/tools/_generic_value.py +8 -2
  55. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +271 -8
  56. prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -1
  57. prediction_market_agent_tooling/tools/caches/db_cache.py +219 -117
  58. prediction_market_agent_tooling/tools/caches/serializers.py +11 -2
  59. prediction_market_agent_tooling/tools/contract.py +480 -38
  60. prediction_market_agent_tooling/tools/contract_utils.py +61 -0
  61. prediction_market_agent_tooling/tools/cow/cow_order.py +218 -45
  62. prediction_market_agent_tooling/tools/cow/models.py +122 -0
  63. prediction_market_agent_tooling/tools/cow/semaphore.py +104 -0
  64. prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
  65. prediction_market_agent_tooling/tools/db/db_manager.py +59 -0
  66. prediction_market_agent_tooling/tools/hexbytes_custom.py +4 -1
  67. prediction_market_agent_tooling/tools/httpx_cached_client.py +15 -6
  68. prediction_market_agent_tooling/tools/langfuse_client_utils.py +21 -8
  69. prediction_market_agent_tooling/tools/openai_utils.py +31 -0
  70. prediction_market_agent_tooling/tools/perplexity/perplexity_client.py +86 -0
  71. prediction_market_agent_tooling/tools/perplexity/perplexity_models.py +26 -0
  72. prediction_market_agent_tooling/tools/perplexity/perplexity_search.py +73 -0
  73. prediction_market_agent_tooling/tools/rephrase.py +71 -0
  74. prediction_market_agent_tooling/tools/singleton.py +11 -6
  75. prediction_market_agent_tooling/tools/streamlit_utils.py +188 -0
  76. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +64 -0
  77. prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
  78. prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
  79. prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
  80. prediction_market_agent_tooling/tools/utils.py +61 -3
  81. prediction_market_agent_tooling/tools/web3_utils.py +63 -9
  82. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/METADATA +13 -9
  83. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/RECORD +86 -64
  84. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/WHEEL +1 -1
  85. prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +0 -171
  86. prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -420
  87. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/entry_points.txt +0 -0
  88. {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(self, for_address: ChecksumAddress, web3: Web3 | None = None) -> Wei:
260
- balance = Wei(self.call("balanceOf", [for_address], web3=web3))
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].hex()[2:]
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).hex()
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).hex()
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
- # Case 1: Check if it's a standard proxy (has implementation() function)
608
- if contract_implements_function(
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
- implementation_address,
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
- else:
623
- # Case 2: Check if it's a minimal proxy contract
624
- implements = minimal_proxy_implements_function(
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 minimal_proxy_implements_function(
635
- contract_address: ChecksumAddress,
636
- function_name: str,
637
- web3: Web3,
638
- function_arg_types: list[str] | None = None,
639
- ) -> bool:
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
- # Recurse into singleton
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 False
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: