-
Notifications
You must be signed in to change notification settings - Fork 1
Governance additions (vote via legacy delegation, vote via liquid staking) #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
d6d07b3
Prepare scripts for governance call (work in progress).
andreibancioiu 996c21b
Micro-refactor.
andreibancioiu 5714f85
Sketch functions for voting via legacy delegation.
andreibancioiu ac6ceb3
Sketch script for voting via legacy delegation.
andreibancioiu 66c4483
Improve voting (on-chain). A bit more robust.
andreibancioiu f5b3901
Fix voting for direct staking.
andreibancioiu 4973bb5
Improve vote via legacy delegation.
andreibancioiu 9678f67
Display previous votes etc.
andreibancioiu 86378bc
Refactor, adjust getting previous votes.
andreibancioiu 65c75b1
More robust flow.
andreibancioiu 44c7c45
Get on-chain delegated votes, apply some checks when voting via legac…
andreibancioiu 6f5feac
Simple report on governance (voting).
andreibancioiu c34aaab
Adjust readme.
andreibancioiu 2ec932e
Rename files, cleanup etc.
andreibancioiu c3f68ba
Fix gas limit.
andreibancioiu 9942b53
Add proofs for governance, for liquid staking (delegating votes).
andreibancioiu 0d7bb39
Vote via liquid staking (work in progress).
andreibancioiu 6b528f2
Adjust report etc. Refactoring.
andreibancioiu b907494
Fix function name.
andreibancioiu 8e8d364
Update proofs for governance.
andreibancioiu cb556aa
Fix logic around getting past votes.
andreibancioiu 420e6f3
Fix decoding.
andreibancioiu f3c391d
Fix getting timestamp etc.
andreibancioiu a4b2a58
Fix after self-review (refactoring).
andreibancioiu 165442d
Update governance proofs (Hatom).
andreibancioiu f169fd2
Fix after review.
andreibancioiu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16,832 changes: 16,832 additions & 0 deletions
16,832
...ance_proofs/mainnet/erd1qqqqqqqqqqqqqpgq2khda0rx207gvlqg92dq5rh0z03a8dqf78ssu0qlcc/1.json
Large diffs are not rendered by default.
Oops, something went wrong.
2,472 changes: 2,472 additions & 0 deletions
2,472
...ance_proofs/mainnet/erd1qqqqqqqqqqqqqpgqdnpmeseu3j5t7grds9dfj8ttt70pev66ah0sydkq9x/1.json
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| requests>=2.32.0,<3.0.0 | ||
| ledgercomm[hid] | ||
| rich==13.3.4 | ||
| multiversx-sdk[ledger]==2.1.0 | ||
| multiversx-sdk[ledger]==2.3.2 | ||
| pyotp==2.9.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| import base64 | ||
| import time | ||
| from datetime import datetime, timedelta, timezone | ||
| from multiprocessing.dummy import Pool | ||
| from typing import Any, Callable, Optional | ||
|
|
||
|
|
@@ -8,7 +10,8 @@ | |
| NetworkProviderConfig, NetworkProviderError, | ||
| ProxyNetworkProvider, Token, TokenTransfer, | ||
| Transaction, TransactionOnNetwork, VoteType) | ||
| from multiversx_sdk.abi import BigUIntValue, BytesValue, U64Value | ||
| from multiversx_sdk.abi import (AddressValue, BigUIntValue, BytesValue, | ||
| StringValue, U64Value) | ||
| from rich import print | ||
|
|
||
| from wizard import ux | ||
|
|
@@ -21,7 +24,8 @@ | |
| COSIGNER_SIGN_TRANSACTIONS_RETRY_DELAY_IN_SECONDS, | ||
| DEFAULT_CHUNK_SIZE_OF_SEND_TRANSACTIONS, MAX_NUM_CUSTOM_TOKENS_TO_FETCH, | ||
| MAX_NUM_TRANSACTIONS_TO_FETCH_OF_TYPE_CLAIM_REWARDS, | ||
| MAX_NUM_TRANSACTIONS_TO_FETCH_OF_TYPE_REWARDS, METACHAIN_ID, | ||
| MAX_NUM_TRANSACTIONS_TO_FETCH_OF_TYPE_REWARDS, | ||
| MAX_NUM_TRANSACTIONS_TO_FETCH_OF_TYPE_VOTE, METACHAIN_ID, | ||
| NETWORK_PROVIDER_NUM_RETRIES, NETWORK_PROVIDER_TIMEOUT_SECONDS, | ||
| NETWORK_PROVIDERS_RETRY_DELAY_IN_SECONDS, | ||
| NUM_PARALLEL_GET_GUARDIAN_DATA_REQUESTS, NUM_PARALLEL_GET_NONCE_REQUESTS, | ||
|
|
@@ -30,6 +34,7 @@ | |
| TRANSACTION_AWAITING_POLLING_TIMEOUT_IN_MILLISECONDS) | ||
| from wizard.currencies import is_native_currency | ||
| from wizard.errors import KnownError, TransientError | ||
| from wizard.governance import OnChainVote | ||
| from wizard.guardians import (AuthApp, AuthRegistrationEntry, CosignerClient, | ||
| GuardianData) | ||
| from wizard.rewards import ClaimableRewards, ReceivedRewards, RewardsType | ||
|
|
@@ -44,7 +49,7 @@ def __init__( | |
| configuration: Configuration, | ||
| use_gas_estimator: Optional[bool] = None, | ||
| gas_limit_multiplier: Optional[float] = None | ||
| ) -> None: | ||
| ) -> None: | ||
| self.configuration = configuration | ||
|
|
||
| self.network_entrypoint = NetworkEntrypoint( | ||
|
|
@@ -119,12 +124,16 @@ def get_claimable_rewards_legacy(self, delegator: Address) -> int: | |
| return int(amount) | ||
|
|
||
| def recall_nonces(self, accounts_wrappers: list[AccountWrapper]): | ||
| print("Recalling nonces...") | ||
|
|
||
| def recall_nonce(wrapper: AccountWrapper): | ||
| wrapper.account.nonce = self.network_entrypoint.recall_account_nonce(wrapper.account.address) | ||
|
|
||
| Pool(NUM_PARALLEL_GET_NONCE_REQUESTS).map(recall_nonce, accounts_wrappers) | ||
|
|
||
| def recall_guardians(self, accounts: list[AccountWrapper]): | ||
| print("Recalling guardians...") | ||
|
|
||
| def recall_guardian(wrapper: AccountWrapper): | ||
| guardian_data = self.get_guardian_data(wrapper.account.address) | ||
| 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 | |
| guardian=sender.guardian | ||
| ) | ||
|
|
||
| def vote_on_governance(self, sender: AccountWrapper, proposal: int, choice: int, power: int, proof: bytes, gas_price: int) -> Transaction: | ||
| governance_contract = Address.new_from_bech32(self.configuration.governance_contract) | ||
| def get_direct_voting_power(self, voter: Address): | ||
| controller = self.network_entrypoint.create_governance_controller() | ||
| return controller.get_voting_power(voter) | ||
|
|
||
| def vote_directly(self, sender: AccountWrapper, proposal: int, vote: VoteType, gas_price: int) -> Transaction: | ||
| controller = self.network_entrypoint.create_governance_controller() | ||
|
|
||
| return controller.create_transaction_for_voting( | ||
| sender=sender.account, | ||
| nonce=sender.account.get_nonce_then_increment(), | ||
| proposal_nonce=proposal, | ||
| vote=vote, | ||
| gas_price=gas_price, | ||
| guardian=sender.guardian, | ||
| ) | ||
|
|
||
| def get_voting_power_via_legacy_delegation(self, voter: Address) -> int: | ||
| legacy_delegation_contract = Address.new_from_bech32(self.configuration.legacy_delegation_contract) | ||
|
|
||
| controller = self.network_entrypoint.create_smart_contract_controller() | ||
| [power_encoded] = controller.query( | ||
| contract=legacy_delegation_contract, | ||
| function="getVotingPower", | ||
| arguments=[AddressValue.new_from_address(voter)], | ||
| ) | ||
|
|
||
| power = BigUIntValue() | ||
| power.decode_top_level(power_encoded) | ||
| return power.value | ||
|
|
||
| def vote_via_legacy_delegation(self, sender: AccountWrapper, proposal: int, vote: VoteType, gas_price: int): | ||
| legacy_delegation_contract = Address.new_from_bech32(self.configuration.legacy_delegation_contract) | ||
|
|
||
| controller = self.network_entrypoint.create_smart_contract_controller() | ||
| transaction = controller.create_transaction_for_execute( | ||
| sender=sender.account, | ||
| nonce=sender.account.get_nonce_then_increment(), | ||
| contract=legacy_delegation_contract, | ||
| function="delegateVote", | ||
| arguments=[U64Value(proposal), StringValue(vote.value)], | ||
| # Gas estimator might not work, thus we hard-code a value here. | ||
| gas_limit=75_000_000, | ||
| gas_price=gas_price, | ||
| guardian=sender.guardian | ||
| ) | ||
|
|
||
| return transaction | ||
|
|
||
| def vote_via_liquid_staking(self, sender: AccountWrapper, contract: str, proposal: int, vote: VoteType, power: int, proof: bytes, gas_price: int) -> Transaction: | ||
| controller = self.network_entrypoint.create_smart_contract_controller() | ||
|
|
||
| transaction = controller.create_transaction_for_execute( | ||
| sender=sender.account, | ||
| nonce=sender.account.get_nonce_then_increment(), | ||
| contract=governance_contract, | ||
| gas_limit=50_000_000, | ||
| function="vote", | ||
| contract=Address.new_from_bech32(contract), | ||
| # Gas estimator might not work, thus we hard-code a value here. | ||
| gas_limit=100_000_000, | ||
| function="delegate_vote", | ||
| arguments=[ | ||
| U64Value(proposal), | ||
| U64Value(choice), | ||
| StringValue(vote.value), | ||
| BigUIntValue(power), | ||
| BytesValue(proof) | ||
| ], | ||
|
|
@@ -285,16 +342,59 @@ def vote_on_governance(self, sender: AccountWrapper, proposal: int, choice: int, | |
|
|
||
| return transaction | ||
|
|
||
| def vote_on_onchain_governance(self, sender: AccountWrapper, proposal: int, vote: VoteType, gas_price: int) -> Transaction: | ||
| controller = self.network_entrypoint.create_governance_controller() | ||
| return controller.create_transaction_for_voting( | ||
| sender=sender.account, | ||
| nonce=sender.account.get_nonce_then_increment(), | ||
| proposal_nonce=proposal, | ||
| vote=vote, | ||
| gas_price=gas_price, | ||
| guardian=sender.guardian, | ||
| ) | ||
| def get_direct_vote(self, voter: Address, proposal: int) -> Optional[OnChainVote]: | ||
| return self._get_past_vote(voter.to_bech32(), self.configuration.system_governance_contract, "vote", "vote", proposal) | ||
|
|
||
| def get_vote_via_legacy_delegation(self, voter: Address, proposal: int) -> Optional[OnChainVote]: | ||
| return self._get_past_vote(voter.to_bech32(), self.configuration.legacy_delegation_contract, "delegateVote", "delegateVote", proposal) | ||
|
|
||
| def get_vote_via_liquid_staking(self, voter: Address, contract: str, proposal: int) -> Optional[OnChainVote]: | ||
| return self._get_past_vote(voter.to_bech32(), contract, "delegate_vote", "delegateVote", proposal) | ||
|
|
||
| def _get_past_vote(self, voter: str, contract: str, function: str, event_identifier: str, proposal: int) -> Optional[OnChainVote]: | ||
| url = f"accounts/{voter}/transactions" | ||
| size = MAX_NUM_TRANSACTIONS_TO_FETCH_OF_TYPE_VOTE | ||
| reasonably_recent_timestamp = int((datetime.now(timezone.utc) - timedelta(days=30)).timestamp()) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Workaround (limitations): one month, at most 10 votes 🤞 |
||
|
|
||
| transactions = self._api_do_get(url, { | ||
| "status": "success", | ||
| "receiver": contract, | ||
| "function": function, | ||
| "withLogs": "true", | ||
| "withScResults": "true", | ||
| "size": size, | ||
| "after": reasonably_recent_timestamp | ||
| }) | ||
|
|
||
| if len(transactions) == size: | ||
| print(f"\tRetrieved {size} transactions. [red]There could be more![/red]") | ||
|
|
||
| for transaction in transactions: | ||
| timestamp = transaction.get("timestamp", 0) | ||
|
|
||
| all_events: list[Any] = [] | ||
| all_events.extend(transaction.get("logs", {}).get("events", [])) | ||
|
|
||
| for result in transaction.get("results"): | ||
| all_events.extend(result.get("logs", {}).get("events", [])) | ||
|
|
||
| for event in all_events: | ||
| if event.get("identifier") != event_identifier: | ||
| continue | ||
|
|
||
| topics = event.get("topics", []) | ||
|
|
||
| event_proposal_base64 = topics[0] | ||
| event_proposal_bytes = base64.b64decode(event_proposal_base64) | ||
| event_proposal = U64Value() | ||
| event_proposal.decode_top_level(event_proposal_bytes) | ||
| event_vote_type_base64 = topics[1] | ||
| event_vote_type = VoteType(base64.b64decode(event_vote_type_base64).decode()) | ||
|
|
||
| if event_proposal.value == proposal: | ||
| return OnChainVote(voter, proposal, contract, timestamp, event_vote_type) | ||
|
|
||
| return None | ||
|
|
||
| def get_guardian_data(self, address: Address): | ||
| response = self.proxy_network_provider.do_get_generic(f"address/{address.to_bech32()}/guardian-data") | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should move some of the functions below to
sdk-py? CC: @popentaThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will think about it, but I am not sure it's necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, it might be a good idea :)