1+ import base64
12import time
3+ from datetime import datetime , timedelta , timezone
24from multiprocessing .dummy import Pool
35from typing import Any , Callable , Optional
46
810 NetworkProviderConfig , NetworkProviderError ,
911 ProxyNetworkProvider , Token , TokenTransfer ,
1012 Transaction , TransactionOnNetwork , VoteType )
11- from multiversx_sdk .abi import BigUIntValue , BytesValue , U64Value
13+ from multiversx_sdk .abi import (AddressValue , BigUIntValue , BytesValue ,
14+ StringValue , U64Value )
1215from rich import print
1316
1417from wizard import ux
2124 COSIGNER_SIGN_TRANSACTIONS_RETRY_DELAY_IN_SECONDS ,
2225 DEFAULT_CHUNK_SIZE_OF_SEND_TRANSACTIONS , MAX_NUM_CUSTOM_TOKENS_TO_FETCH ,
2326 MAX_NUM_TRANSACTIONS_TO_FETCH_OF_TYPE_CLAIM_REWARDS ,
24- MAX_NUM_TRANSACTIONS_TO_FETCH_OF_TYPE_REWARDS , METACHAIN_ID ,
27+ MAX_NUM_TRANSACTIONS_TO_FETCH_OF_TYPE_REWARDS ,
28+ MAX_NUM_TRANSACTIONS_TO_FETCH_OF_TYPE_VOTE , METACHAIN_ID ,
2529 NETWORK_PROVIDER_NUM_RETRIES , NETWORK_PROVIDER_TIMEOUT_SECONDS ,
2630 NETWORK_PROVIDERS_RETRY_DELAY_IN_SECONDS ,
2731 NUM_PARALLEL_GET_GUARDIAN_DATA_REQUESTS , NUM_PARALLEL_GET_NONCE_REQUESTS ,
3034 TRANSACTION_AWAITING_POLLING_TIMEOUT_IN_MILLISECONDS )
3135from wizard .currencies import is_native_currency
3236from wizard .errors import KnownError , TransientError
37+ from wizard .governance import OnChainVote
3338from wizard .guardians import (AuthApp , AuthRegistrationEntry , CosignerClient ,
3439 GuardianData )
3540from wizard .rewards import ClaimableRewards , ReceivedRewards , RewardsType
@@ -44,7 +49,7 @@ def __init__(
4449 configuration : Configuration ,
4550 use_gas_estimator : Optional [bool ] = None ,
4651 gas_limit_multiplier : Optional [float ] = None
47- ) -> None :
52+ ) -> None :
4853 self .configuration = configuration
4954
5055 self .network_entrypoint = NetworkEntrypoint (
@@ -119,12 +124,16 @@ def get_claimable_rewards_legacy(self, delegator: Address) -> int:
119124 return int (amount )
120125
121126 def recall_nonces (self , accounts_wrappers : list [AccountWrapper ]):
127+ print ("Recalling nonces..." )
128+
122129 def recall_nonce (wrapper : AccountWrapper ):
123130 wrapper .account .nonce = self .network_entrypoint .recall_account_nonce (wrapper .account .address )
124131
125132 Pool (NUM_PARALLEL_GET_NONCE_REQUESTS ).map (recall_nonce , accounts_wrappers )
126133
127134 def recall_guardians (self , accounts : list [AccountWrapper ]):
135+ print ("Recalling guardians..." )
136+
128137 def recall_guardian (wrapper : AccountWrapper ):
129138 guardian_data = self .get_guardian_data (wrapper .account .address )
130139 wrapper .guardian = Address .new_from_bech32 (guardian_data .active_guardian ) if guardian_data .is_guarded else None
@@ -263,19 +272,67 @@ def transfer_funds(self, sender: AccountWrapper, receiver: Address, transfer: To
263272 guardian = sender .guardian
264273 )
265274
266- def vote_on_governance (self , sender : AccountWrapper , proposal : int , choice : int , power : int , proof : bytes , gas_price : int ) -> Transaction :
267- governance_contract = Address .new_from_bech32 (self .configuration .governance_contract )
275+ def get_direct_voting_power (self , voter : Address ):
276+ controller = self .network_entrypoint .create_governance_controller ()
277+ return controller .get_voting_power (voter )
278+
279+ def vote_directly (self , sender : AccountWrapper , proposal : int , vote : VoteType , gas_price : int ) -> Transaction :
280+ controller = self .network_entrypoint .create_governance_controller ()
281+
282+ return controller .create_transaction_for_voting (
283+ sender = sender .account ,
284+ nonce = sender .account .get_nonce_then_increment (),
285+ proposal_nonce = proposal ,
286+ vote = vote ,
287+ gas_price = gas_price ,
288+ guardian = sender .guardian ,
289+ )
290+
291+ def get_voting_power_via_legacy_delegation (self , voter : Address ) -> int :
292+ legacy_delegation_contract = Address .new_from_bech32 (self .configuration .legacy_delegation_contract )
293+
294+ controller = self .network_entrypoint .create_smart_contract_controller ()
295+ [power_encoded ] = controller .query (
296+ contract = legacy_delegation_contract ,
297+ function = "getVotingPower" ,
298+ arguments = [AddressValue .new_from_address (voter )],
299+ )
300+
301+ power = BigUIntValue ()
302+ power .decode_top_level (power_encoded )
303+ return power .value
304+
305+ def vote_via_legacy_delegation (self , sender : AccountWrapper , proposal : int , vote : VoteType , gas_price : int ):
306+ legacy_delegation_contract = Address .new_from_bech32 (self .configuration .legacy_delegation_contract )
307+
308+ controller = self .network_entrypoint .create_smart_contract_controller ()
309+ transaction = controller .create_transaction_for_execute (
310+ sender = sender .account ,
311+ nonce = sender .account .get_nonce_then_increment (),
312+ contract = legacy_delegation_contract ,
313+ function = "delegateVote" ,
314+ arguments = [U64Value (proposal ), StringValue (vote .value )],
315+ # Gas estimator might not work, thus we hard-code a value here.
316+ gas_limit = 75_000_000 ,
317+ gas_price = gas_price ,
318+ guardian = sender .guardian
319+ )
320+
321+ return transaction
268322
323+ def vote_via_liquid_staking (self , sender : AccountWrapper , contract : str , proposal : int , vote : VoteType , power : int , proof : bytes , gas_price : int ) -> Transaction :
269324 controller = self .network_entrypoint .create_smart_contract_controller ()
325+
270326 transaction = controller .create_transaction_for_execute (
271327 sender = sender .account ,
272328 nonce = sender .account .get_nonce_then_increment (),
273- contract = governance_contract ,
274- gas_limit = 50_000_000 ,
275- function = "vote" ,
329+ contract = Address .new_from_bech32 (contract ),
330+ # Gas estimator might not work, thus we hard-code a value here.
331+ gas_limit = 100_000_000 ,
332+ function = "delegate_vote" ,
276333 arguments = [
277334 U64Value (proposal ),
278- U64Value ( choice ),
335+ StringValue ( vote . value ),
279336 BigUIntValue (power ),
280337 BytesValue (proof )
281338 ],
@@ -285,16 +342,59 @@ def vote_on_governance(self, sender: AccountWrapper, proposal: int, choice: int,
285342
286343 return transaction
287344
288- def vote_on_onchain_governance (self , sender : AccountWrapper , proposal : int , vote : VoteType , gas_price : int ) -> Transaction :
289- controller = self .network_entrypoint .create_governance_controller ()
290- return controller .create_transaction_for_voting (
291- sender = sender .account ,
292- nonce = sender .account .get_nonce_then_increment (),
293- proposal_nonce = proposal ,
294- vote = vote ,
295- gas_price = gas_price ,
296- guardian = sender .guardian ,
297- )
345+ def get_direct_vote (self , voter : Address , proposal : int ) -> Optional [OnChainVote ]:
346+ return self ._get_past_vote (voter .to_bech32 (), self .configuration .system_governance_contract , "vote" , "vote" , proposal )
347+
348+ def get_vote_via_legacy_delegation (self , voter : Address , proposal : int ) -> Optional [OnChainVote ]:
349+ return self ._get_past_vote (voter .to_bech32 (), self .configuration .legacy_delegation_contract , "delegateVote" , "delegateVote" , proposal )
350+
351+ def get_vote_via_liquid_staking (self , voter : Address , contract : str , proposal : int ) -> Optional [OnChainVote ]:
352+ return self ._get_past_vote (voter .to_bech32 (), contract , "delegate_vote" , "delegateVote" , proposal )
353+
354+ def _get_past_vote (self , voter : str , contract : str , function : str , event_identifier : str , proposal : int ) -> Optional [OnChainVote ]:
355+ url = f"accounts/{ voter } /transactions"
356+ size = MAX_NUM_TRANSACTIONS_TO_FETCH_OF_TYPE_VOTE
357+ reasonably_recent_timestamp = int ((datetime .now (timezone .utc ) - timedelta (days = 30 )).timestamp ())
358+
359+ transactions = self ._api_do_get (url , {
360+ "status" : "success" ,
361+ "receiver" : contract ,
362+ "function" : function ,
363+ "withLogs" : "true" ,
364+ "withScResults" : "true" ,
365+ "size" : size ,
366+ "after" : reasonably_recent_timestamp
367+ })
368+
369+ if len (transactions ) == size :
370+ print (f"\t Retrieved { size } transactions. [red]There could be more![/red]" )
371+
372+ for transaction in transactions :
373+ timestamp = transaction .get ("timestamp" , 0 )
374+
375+ all_events : list [Any ] = []
376+ all_events .extend (transaction .get ("logs" , {}).get ("events" , []))
377+
378+ for result in transaction .get ("results" ):
379+ all_events .extend (result .get ("logs" , {}).get ("events" , []))
380+
381+ for event in all_events :
382+ if event .get ("identifier" ) != event_identifier :
383+ continue
384+
385+ topics = event .get ("topics" , [])
386+
387+ event_proposal_base64 = topics [0 ]
388+ event_proposal_bytes = base64 .b64decode (event_proposal_base64 )
389+ event_proposal = U64Value ()
390+ event_proposal .decode_top_level (event_proposal_bytes )
391+ event_vote_type_base64 = topics [1 ]
392+ event_vote_type = VoteType (base64 .b64decode (event_vote_type_base64 ).decode ())
393+
394+ if event_proposal .value == proposal :
395+ return OnChainVote (voter , proposal , contract , timestamp , event_vote_type )
396+
397+ return None
298398
299399 def get_guardian_data (self , address : Address ):
300400 response = self .proxy_network_provider .do_get_generic (f"address/{ address .to_bech32 ()} /guardian-data" )
0 commit comments