From 925f390d62ec715a8c0f890a07ea452001273fab Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 10:41:41 -0400 Subject: [PATCH 001/134] refactor: fix Safe API endpoints --- ape_safe/client.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 96cc363..1510b9b 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -12,19 +12,19 @@ SafeTxID = NewType("SafeTxID", bytes) TRANSACTION_SERVICE_URL = { - 1: "https://safe-transaction.mainnet.gnosis.io", - 4: "https://safe-transaction.rinkeby.gnosis.io", - 5: "https://safe-transaction.goerli.gnosis.io", + 1: "https://safe-transaction-mainnet.safe.global", + 5: "https://safe-transaction-goerli.safe.global", 10: "https://safe-transaction-optimism.safe.global", - 56: "https://safe-transaction.bsc.gnosis.io", - 100: "https://safe-transaction.xdai.gnosis.io", - 137: "https://safe-transaction.polygon.gnosis.io", + 56: "https://safe-transaction-bsc.safe.global", + 100: "https://safe-transaction-gnosis-chain.safe.global", + 137: "https://safe-transaction-polygon.safe.global", 250: "https://safe-txservice.fantom.network", - 246: "https://safe-transaction.ewc.gnosis.io", 288: "https://safe-transaction.mainnet.boba.network", + # NOTE: Not supported yet + # 8453: "https://safe-transaction-base.safe.global/", 42161: "https://safe-transaction-arbitrum.safe.global", 43114: "https://safe-transaction-avalanche.safe.global", - 73799: "https://safe-transaction.volta.gnosis.io", + 84531: "https://safe-transaction-base-testnet.safe.global/", } From 68115289863c828e1ae68728c4bb05e1c811f9fc Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:57:06 -0400 Subject: [PATCH 002/134] refactor: make SafeTxID a HexBytes subclass --- ape_safe/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 1510b9b..dbb23b9 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -9,7 +9,7 @@ from pydantic import BaseModel SafeTx = Union[SafeTxV1, SafeTxV2] -SafeTxID = NewType("SafeTxID", bytes) +SafeTxID = NewType("SafeTxID", HexBytes) TRANSACTION_SERVICE_URL = { 1: "https://safe-transaction-mainnet.safe.global", @@ -225,10 +225,10 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature if not response.ok: raise - def post_signature(self, safe_tx_id: SafeTxID, signature: MessageSignature): + def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): url = ( - f"{self.transaction_service_url}" - f"/api/v1/multisig-transactions/{safe_tx_id.hex()}/confirmations" + f"{self.transaction_service_url}/api" + f"/v1/multisig-transactions/{str(safe_tx_hash)}/confirmations" ) response = requests.post(url, json={"signature": signature.encode_vrs().hex()}) From 549dfbb932067e444866ed432c46e954a5ec7c51 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:57:31 -0400 Subject: [PATCH 003/134] feat: add API wrapper for fetching confirmations --- ape_safe/client.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ape_safe/client.py b/ape_safe/client.py index dbb23b9..222144d 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -212,6 +212,20 @@ def get_transactions( yield ExecutedTxData.parse_obj(txn) if isExecuted else UnexecutedTxData.parse_obj(txn) + def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: + url = ( + f"{self.transaction_service_url}/api" + f"/v1/multisig-transactions/{str(safe_tx_hash)}/confirmations" + ) + while url: + response = requests.get(url) + if not response.ok: + raise + + data = response.json() + yield from map(SafeTxConfirmation.parse_obj, data["results"]) + url = data["next"] + def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature]] = None): tx_data = UnexecutedTxData.from_safe_tx(safe_tx) if sigs: From 5fab05053978412e95649223fbcf4d7ceb2b1c4e Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:58:37 -0400 Subject: [PATCH 004/134] feat: add method for fetching pending confirmations to SafeAccount --- ape_safe/accounts.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 4281e31..f789ff5 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -14,7 +14,7 @@ from eth_utils import keccak, to_bytes, to_int from ethpm_types import ContractType -from .client import SafeClient, SafeTx +from .client import SafeClient, SafeTx, SafeTxConfirmation from .exceptions import NoLocalSigners, NotASigner, NotEnoughSignatures, handle_safe_logic_error @@ -171,6 +171,10 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) return self.safe_tx_def(**safe_tx) + def pending_transactions(self) -> Iterator[Tuple[SafeTx, List[SafeTxConfirmation]]]: + for unexec_tx_data in self.client.get_transactions(confirmed=False): + yield self.create_safe_tx(**unexec_tx_data.dict()), unexec_tx_data.confirmations + @property def local_signers(self) -> List[AccountAPI]: # NOTE: Is not ordered by signing order From e359dbceb24614185d83aedc083decc438fce8db Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:58:59 -0400 Subject: [PATCH 005/134] feat: add method for fetching confirmations as signature dict --- ape_safe/accounts.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index f789ff5..a617c3f 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -11,6 +11,7 @@ from ape.utils import ZERO_ADDRESS, cached_property from ape_ethereum.transactions import TransactionType from eip712.common import create_safe_tx_def +from eip712.messages import hash_eip712_message from eth_utils import keccak, to_bytes, to_int from ethpm_types import ContractType @@ -195,6 +196,15 @@ def get_signatures( if sig := signer.sign_message(safe_tx.signable_message): yield signer.address, sig + def get_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: + safe_tx_hash = hash_eip712_message(safe_tx) + return { + conf.owner: MessageSignature( + r=conf.signature[:32], s=conf.signature[32:64], v=conf.signature[64] + ) + for conf in self.client.get_confirmations(safe_tx_hash) + } + @handle_safe_logic_error() def create_execute_transaction( self, From 8af0812adb33f253773db863e45fab2b6421de0b Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:59:23 -0400 Subject: [PATCH 006/134] refactor: allow pending txn CLI to submit txns --- ape_safe/_cli.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index cbdd8b0..33e6955 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -98,22 +98,34 @@ def remove(cli_ctx, alias): cls=NetworkBoundCommand, short_help="See pending transactions for a locally-tracked Safe" ) @network_option() +@click.option("sign_with_local_signers", "--sign", is_flag=True) +@click.option("--execute", is_flag=False, flag_value=True, default=False) @existing_alias_argument(account_type=SafeAccount) -def pending(network, alias): +def pending(network, sign_with_local_signers, execute, alias): safe = accounts.load(alias) - local_signers = set(signer for signer in accounts if signer.address in safe.signers) - if local_signers: - click.echo("Local Signer(s) detected!") - sign_with_local_signers = click.confirm("Do you want to sign unconfirmed transactions") - else: - sign_with_local_signers = False + if isinstance(execute, str): + if execute in accounts.aliases: + submitter = accounts.load(execute) + else: + raise + + elif execute is True: + submitter = safe.local_signers[0] - for txn in safe.client.get_transactions( - starting_nonce=safe.next_nonce, - filter_by_missing_signers=local_signers if sign_with_local_signers else None, - ): - click.echo(f"Txn {txn.nonce}: ({len(txn.confirmations)}/{txn.confirmationsRequired})") + for safe_tx, confirmations in safe.pending_transactions(): + if sign_with_local_signers and len(confirmations) < safe.confirmations_required: + pass # TODO: sign `safe_tx` with local signers not in `confirmations` + + else: + click.echo(f"Txn {safe_tx.nonce}: ({len(confirmations)}/{safe.confirmations_required})") + + if execute is not False: + signatures = safe.get_confirmations(safe_tx) + if len(signatures) >= safe.confirmations_required and click.confirm( + f"Submit Txn {safe_tx.nonce}" + ): + submitter.call(safe.create_execute_transaction(safe_tx, signatures)) @cli.command(cls=NetworkBoundCommand, short_help="Reject one or more pending transactions") From 9c1ad3d0605a3bbe0ee2428666025a990579fba4 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:45:25 -0400 Subject: [PATCH 007/134] refactor: add proper exceptions for Safe API client errors --- ape_safe/client.py | 16 +++++++++------- ape_safe/exceptions.py | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 222144d..1c488ed 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -8,6 +8,8 @@ from eip712.common import SafeTxV1, SafeTxV2 from pydantic import BaseModel +from .exceptions import ClientResponseError, ClientUnsupportedChainError + SafeTx = Union[SafeTxV1, SafeTxV2] SafeTxID = NewType("SafeTxID", HexBytes) @@ -135,21 +137,21 @@ def __init__( elif chain_id: if chain_id not in TRANSACTION_SERVICE_URL: - raise # No endpoint for this chain + raise ClientUnsupportedChainError(chain_id) self.transaction_service_url = TRANSACTION_SERVICE_URL.get( # type: ignore[assignment] chain_id ) else: - raise # Must provide one of chain_id or override_url + raise ValueError("Must provide one of chain_id or override_url.") @property def safe_details(self) -> SafeDetails: url = f"{self.transaction_service_url}/api/v1/safes/{self.address}" response = requests.get(url) if not response.ok: - raise + raise ClientResponseError(url, response) return SafeDetails.parse_obj(response.json()) @@ -165,7 +167,7 @@ def _all_transactions(self) -> Iterator[dict]: while url: response = requests.get(url) if not response.ok: - raise + raise ClientResponseError(url, response) data = response.json() yield from data["results"] @@ -220,7 +222,7 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati while url: response = requests.get(url) if not response.ok: - raise + raise ClientResponseError(url, response) data = response.json() yield from map(SafeTxConfirmation.parse_obj, data["results"]) @@ -237,7 +239,7 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature response = requests.post(url, json=tx_data.dict()) if not response.ok: - raise + raise ClientResponseError(url, response) def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): url = ( @@ -247,4 +249,4 @@ def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): response = requests.post(url, json={"signature": signature.encode_vrs().hex()}) if not response.ok: - raise + raise ClientResponseError(url, response) diff --git a/ape_safe/exceptions.py b/ape_safe/exceptions.py index 5455fff..b97ab9b 100644 --- a/ape_safe/exceptions.py +++ b/ape_safe/exceptions.py @@ -3,6 +3,7 @@ from ape.exceptions import ApeException, ContractLogicError, SignatureError from ape.types import AddressType +from requests import Response # type: ignore[import] class ApeSafeException(ApeException): @@ -93,3 +94,18 @@ def __init__(self, amount: int): class UnsupportedChainError(MulticallException): def __init__(self): super().__init__("Multicall not supported on this chain.") + + +class SafeClientException(ApeSafeException): + pass + + +class ClientUnsupportedChainError(SafeClientException): + def __init__(self, chain_id: int): + super().__init__(f"Unsupported Chain ID '{chain_id}'.") + + +class ClientResponseError(SafeClientException): + def __init__(self, endpoint_url: str, response: Response): + error_str = response.json()["message"] + super().__init__(f"Exception when calling '{endpoint_url}':\n{error_str}") From 090611666a47b661889477da6c03bb5411c24d79 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:04:26 -0400 Subject: [PATCH 008/134] refactor: Use ABC for SafeClient to support Mock --- ape_safe/client.py | 118 +++++++++++++++++++++++++++++---------------- 1 file changed, 76 insertions(+), 42 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 1c488ed..5604e4a 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -1,3 +1,4 @@ +from abc import ABC, abstractmethod from datetime import datetime from enum import Enum from functools import reduce @@ -123,7 +124,74 @@ class ExecutedTxData(UnexecutedTxData): SafeApiTxData = Union[ExecutedTxData, UnexecutedTxData] -class SafeClient: +class BaseSafeClient(ABC): + @property + @abstractmethod + def safe_details(self) -> SafeDetails: + ... + + @abstractmethod + def get_next_nonce(self) -> int: + ... + + @abstractmethod + def _all_transactions(self) -> Iterator[SafeApiTxData]: + ... + + def get_transactions( + self, + confirmed: Optional[bool] = None, + starting_nonce: int = 0, + filter_by_ids: Optional[Set[SafeTxID]] = None, + filter_by_missing_signers: Optional[Set[AddressType]] = None, + ) -> Iterator[SafeApiTxData]: + """ + confirmed: Confirmed if True, not confirmed if False, both if None + """ + next_nonce = self.get_next_nonce() + + for txn in self._all_transactions(): + if txn.nonce < starting_nonce: + break # NOTE: order is largest nonce to smallest, so safe to break here + + isConfirmed = len(txn.confirmations) >= txn.confirmationsRequired + + if confirmed is not None: + if not confirmed and isinstance(txn, ExecutedTxData): + break # NOTE: Break at the first executed transaction + + elif confirmed and not isConfirmed: + continue # NOTE: Skip not confirmed transactions + + if txn.nonce < next_nonce and isinstance(txn, UnexecutedTxData): + continue # NOTE: Skip orphaned transactions + + if filter_by_ids and txn.safeTxHash not in filter_by_ids: + continue # NOTE: Skip transactions not in the filter + + if filter_by_missing_signers and filter_by_missing_signers.issubset( + set(conf.owner for conf in txn.confirmations) + ): + # NOTE: Skip if all signers from `filter_by_missing_signers` + # are in `txn.confirmations` + continue + + yield txn + + @abstractmethod + def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: + ... + + @abstractmethod + def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature]] = None): + ... + + @abstractmethod + def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): + ... + + +class SafeClient(BaseSafeClient): def __init__( self, address: AddressType, @@ -158,7 +226,7 @@ def safe_details(self) -> SafeDetails: def get_next_nonce(self) -> int: return self.safe_details.nonce - def _all_transactions(self) -> Iterator[dict]: + def _all_transactions(self) -> Iterator[SafeApiTxData]: """ confirmed: Confirmed if True, not confirmed if False, both if None """ @@ -170,49 +238,15 @@ def _all_transactions(self) -> Iterator[dict]: raise ClientResponseError(url, response) data = response.json() - yield from data["results"] - url = data["next"] - def get_transactions( - self, - confirmed: Optional[bool] = None, - starting_nonce: int = 0, - filter_by_ids: Optional[Set[SafeTxID]] = None, - filter_by_missing_signers: Optional[Set[AddressType]] = None, - ) -> Iterator[SafeApiTxData]: - """ - confirmed: Confirmed if True, not confirmed if False, both if None - """ - next_nonce = self.get_next_nonce() + for txn in data["results"]: + if "isExecuted" in txn and txn["isExecuted"]: + yield ExecutedTxData.parse_obj(txn) - for txn in self._all_transactions(): - if txn["nonce"] < starting_nonce: - break # NOTE: order is largest nonce to smallest, so safe to break here - - isConfirmed = len(txn["confirmations"]) >= txn["confirmationsRequired"] - isExecuted = "isExecuted" in txn and txn["isExecuted"] - - if confirmed is not None: - if not confirmed and isExecuted: - break # NOTE: Break at the first executed transaction - - elif confirmed and not isConfirmed: - continue # NOTE: Skip not confirmed transactions - - if txn["nonce"] < next_nonce and not isExecuted: - continue # NOTE: Skip orphaned transactions + else: + yield UnexecutedTxData.parse_obj(txn) - if filter_by_ids and txn["safeTxHash"] not in filter_by_ids: - continue # NOTE: Skip transactions not in the filter - - if filter_by_missing_signers and filter_by_missing_signers.issubset( - set(conf["owner"] for conf in txn["confirmations"]) - ): - # NOTE: Skip if all signers from `filter_by_missing_signers` - # are in `txn.confirmations` - continue - - yield ExecutedTxData.parse_obj(txn) if isExecuted else UnexecutedTxData.parse_obj(txn) + url = data["next"] def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: url = ( From 10932e592d40048f6fd7d4bee1206b6117917be1 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:05:15 -0400 Subject: [PATCH 009/134] feat: add SafeClient mock (for testing) --- ape_safe/accounts.py | 7 ++-- ape_safe/client.py | 76 +++++++++++++++++++++++++++++++++++++++++++- tests/conftest.py | 1 - 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index a617c3f..9409fb2 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -15,7 +15,7 @@ from eth_utils import keccak, to_bytes, to_int from ethpm_types import ContractType -from .client import SafeClient, SafeTx, SafeTxConfirmation +from .client import BaseSafeClient, MockSafeClient, SafeClient, SafeTx, SafeTxConfirmation from .exceptions import NoLocalSigners, NotASigner, NotEnoughSignatures, handle_safe_logic_error @@ -102,10 +102,13 @@ def fallback_handler(self) -> Optional[ContractInstance]: return None @cached_property - def client(self) -> SafeClient: + def client(self) -> BaseSafeClient: if self.provider.chain_id not in self.account_file["deployed_chain_ids"]: raise # Not valid on this chain + if self.provider.network.name == "local": + return MockSafeClient(contract=self.contract) + return SafeClient(address=self.address, chain_id=self.provider.chain_id) @property diff --git a/ape_safe/client.py b/ape_safe/client.py index 5604e4a..bfdecfb 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -2,11 +2,16 @@ from datetime import datetime from enum import Enum from functools import reduce -from typing import Iterator, List, NewType, Optional, Set, Union +from typing import Dict, Iterator, List, NewType, Optional, Set, Union import requests # type: ignore +from ape.contracts import ContractInstance from ape.types import AddressType, HexBytes, MessageSignature +from ape.utils import ManagerAccessMixin from eip712.common import SafeTxV1, SafeTxV2 +from eip712.messages import hash_eip712_message +from eth_account import Account +from eth_utils import keccak from pydantic import BaseModel from .exceptions import ClientResponseError, ClientUnsupportedChainError @@ -284,3 +289,72 @@ def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): if not response.ok: raise ClientResponseError(url, response) + + +class MockSafeClient(BaseSafeClient, ManagerAccessMixin): + def __init__(self, contract: ContractInstance): + self.contract = contract + self.transactions: Dict[SafeTxID, SafeApiTxData] = {} + self.transactions_by_nonce: Dict[int, List[SafeTxID]] = {} + + @property + def safe_details(self) -> SafeDetails: + slot = keccak(text="fallback_manager.handler.address") + value = self.provider.get_storage_at(self.contract.address, slot) + fallback_address = self.network_manager.ecosystem.decode_address(value[-20:]) + + return SafeDetails( + address=self.contract.address, + nonce=self.get_next_nonce(), + threshold=self.contract.getThreshold(), + owners=self.contract.getOwners(), + masterCopy=self.contract.masterCopy(), + modules=self.contract.getModules(), + # TODO: Add fallback handler getter + fallbackHandler=fallback_address, + guard=self.contract.getGuard(), + version=self.contract.VERSION(), + ) + + def get_next_nonce(self) -> int: + return self.contract._view_methods_["nonce"]() + + def _all_transactions( + self, + ) -> Iterator[SafeApiTxData]: + for nonce in sorted(self.transactions_by_nonce.keys(), reverse=True): + yield from map(self.transactions.get, self.transactions_by_nonce[nonce]) + + def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: + if safe_tx_data := self.transactions.get(safe_tx_hash): + yield from safe_tx_data.confirmations + + def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature]] = None): + safe_tx_data = UnexecutedTxData.from_safe_tx(safe_tx) + if sigs: + safe_tx_data.confirmations.extend( + SafeTxConfirmation( + owner=Account.recover_message(hash_eip712_message(safe_tx), sig), + submissionDate=datetime.now(), + signature=sig.encode_rsv(), + signatureType=SignatureType.EOA, + ) + for sig in sigs + ) + + self.transactions[safe_tx_data.safeTxHash] = safe_tx_data + + if safe_tx_data.nonce in self.transactions_by_nonce: + self.transactions_by_nonce[safe_tx_data.nonce].append(safe_tx_data.safeTxHash) + else: + self.transactions_by_nonce[safe_tx_data.nonce] = [safe_tx_data.safeTxHash] + + def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): + self.transactions[safe_tx_hash].confirmations.append( + SafeTxConfirmation( + owner=Account.recover_message(safe_tx_hash, signature), + submissionDate=datetime.now(), + signature=signature.encode_rsv(), + signatureType=SignatureType.EOA, + ) + ) diff --git a/tests/conftest.py b/tests/conftest.py index 9fe0e5f..d7e405d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -93,5 +93,4 @@ def safe_data_file(chain, safe_contract): @pytest.fixture def safe(safe_data_file): - # TODO: Mock `SafeAccount.client` or use local client return SafeAccount(account_file_path=safe_data_file) From f707648691cb98bc2031dce5cc3513ecbc77ef96 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:07:25 -0400 Subject: [PATCH 010/134] refactor: add client origin string to POST requests --- ape_safe/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index bfdecfb..f965a23 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -275,7 +275,7 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature ) url = f"{self.transaction_service_url}/api/v1/multisig-transactions" - response = requests.post(url, json=tx_data.dict()) + response = requests.post(url, json={"origin": "ApeWorX/ape-safe", **tx_data.dict()}) if not response.ok: raise ClientResponseError(url, response) @@ -285,7 +285,9 @@ def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): f"{self.transaction_service_url}/api" f"/v1/multisig-transactions/{str(safe_tx_hash)}/confirmations" ) - response = requests.post(url, json={"signature": signature.encode_vrs().hex()}) + response = requests.post( + url, json={"origin": "ApeWorX/ape-safe", "signature": signature.encode_vrs().hex()} + ) if not response.ok: raise ClientResponseError(url, response) From 3c6d3b806b90eff936dcfb1eafbd1de0f253af90 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:23:33 -0400 Subject: [PATCH 011/134] test: use new Ape mock events comparison API --- tests/test_account.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index 9d9498c..2b0c46f 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -27,10 +27,11 @@ def test_swap_owner(safe, accounts, OWNERS, mode): impersonate=impersonate, ) - assert not receipt.events.filter(safe.contract.ExecutionFailure) - assert receipt.events.filter(safe.contract.ExecutionSuccess) - assert receipt.events.filter(safe.contract.AddedOwner)[0].owner == new_owner - assert receipt.events.filter(safe.contract.RemovedOwner)[0].owner == old_owner + assert receipt.events == [ + safe.contract.RemovedOwner(owner=old_owner), + safe.contract.AddedOwner(owner=new_owner), + safe.contract.ExecutionSuccess(), + ] assert old_owner not in safe.signers assert new_owner.address in safe.signers @@ -50,9 +51,10 @@ def test_add_owner(safe, accounts, OWNERS, mode): impersonate=impersonate, ) - assert not receipt.events.filter(safe.contract.ExecutionFailure) - assert receipt.events.filter(safe.contract.ExecutionSuccess) - assert receipt.events.filter(safe.contract.AddedOwner)[0].owner == new_owner + assert receipt.events == [ + safe.contract.AddedOwner(owner=new_owner), + safe.contract.ExecutionSuccess(), + ] assert new_owner.address in safe.signers @@ -64,26 +66,25 @@ def test_remove_owner(safe, OWNERS, mode): pytest.skip("Can't remove the only owner") old_owner = safe.signers[0] + new_threshold = max(len(OWNERS) - 1, safe.confirmations_required - 1) + threshold_changed = new_threshold != safe.confirmations_required prev_owner = safe.compute_prev_signer(old_owner) - # TODO: Remove `gas_limit` by allowing forking to compute gas limit receipt = safe.contract.removeOwner( prev_owner, old_owner, # Can't set the threshold to zero or more than the number of owners after removal - max(len(OWNERS) - 1, safe.confirmations_required - 1), + new_threshold, sender=safe, impersonate=impersonate, ) - # TODO: Add fucntionality to ContractEvent such that this can work - # assert receipt.events == [ - # safe.contract.ExecutionSuccess(), - # safe.contract.RemovedOwner(owner=old_owner), - # ] - - assert not receipt.events.filter(safe.contract.ExecutionFailure) - assert receipt.events.filter(safe.contract.ExecutionSuccess) - assert receipt.events.filter(safe.contract.RemovedOwner)[0].owner == old_owner + expected_events = [ + safe.contract.RemovedOwner(owner=old_owner), + safe.contract.ExecutionSuccess(), + ] + if threshold_changed: + expected_events.insert(1, safe.contract.ChangedThreshold(threshold=new_threshold)) + assert receipt.events == expected_events assert old_owner not in safe.signers From 87ef9f08696865ef2a5dce4efad929de3f4fffcc Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Thu, 29 Jun 2023 16:37:15 -0400 Subject: [PATCH 012/134] fix: wrong signature encoding --- ape_safe/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index f965a23..15a7653 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -271,7 +271,7 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature tx_data = UnexecutedTxData.from_safe_tx(safe_tx) if sigs: tx_data.signatures = HexBytes( - reduce(lambda raw_sig, next_sig: raw_sig + next_sig.encode_vrs(), sigs, b"") + reduce(lambda raw_sig, next_sig: raw_sig + next_sig.encode_rsv(), sigs, b"") ) url = f"{self.transaction_service_url}/api/v1/multisig-transactions" @@ -286,7 +286,7 @@ def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): f"/v1/multisig-transactions/{str(safe_tx_hash)}/confirmations" ) response = requests.post( - url, json={"origin": "ApeWorX/ape-safe", "signature": signature.encode_vrs().hex()} + url, json={"origin": "ApeWorX/ape-safe", "signature": signature.encode_rsv().hex()} ) if not response.ok: From 20a903883a0d4d51351923c0d543b758d8c70c27 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:43:52 -0400 Subject: [PATCH 013/134] refactor: move signature ordering algorithm to utils --- ape_safe/accounts.py | 18 +++++------------ ape_safe/client.py | 48 ++++++++++++++++++++++++++------------------ ape_safe/utils.py | 12 +++++++++++ 3 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 ape_safe/utils.py diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 9409fb2..18905ba 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -17,6 +17,7 @@ from .client import BaseSafeClient, MockSafeClient, SafeClient, SafeTx, SafeTxConfirmation from .exceptions import NoLocalSigners, NotASigner, NotEnoughSignatures, handle_safe_logic_error +from .utils import order_by_signer class AccountContainer(AccountContainerAPI): @@ -216,7 +217,9 @@ def create_execute_transaction( **txn_options, ) -> TransactionAPI: exec_args = list(safe_tx._body_["message"].values())[:-1] # NOTE: Skip `nonce` - encoded_signatures = self._encode_signatures(signatures) + encoded_signatures = HexBytes( + b"".join(sig.encode_rsv() for sig in order_by_signer(signatures)) + ) # NOTE: executes a `ProviderAPI.prepare_transaction`, which may produce `ContractLogicError` return self.contract.execTransaction.as_transaction( @@ -244,17 +247,6 @@ def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI: # NOTE: Need to override `AccountAPI` behavior for balance checks return self.provider.prepare_transaction(txn) - def _encode_signatures(self, signatures: Dict[AddressType, MessageSignature]) -> HexBytes: - # NOTE: Must order signatures in ascending order of signer address (converted to int) - def addr_to_int(a: AddressType) -> int: - return to_int(hexstr=a) - - return HexBytes( - b"".join( - signatures[signer].encode_rsv() for signer in sorted(signatures, key=addr_to_int) - ) - ) - def _safe_tx_exec_args(self, safe_tx: SafeTx) -> List: return list(safe_tx._body_["message"].values()) @@ -298,7 +290,7 @@ def _impersonated_call(self, txn: TransactionAPI, **safe_tx_and_call_kwargs) -> ) return self.contract.execTransaction( *safe_tx_exec_args[:-1], # NOTE: Skip nonce - self._encode_signatures(signatures), + HexBytes(b"".join(sig.encode_rsv() for sig in order_by_signer(signatures))), **safe_tx_and_call_kwargs, ) diff --git a/ape_safe/client.py b/ape_safe/client.py index 15a7653..2c703d5 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -10,11 +10,11 @@ from ape.utils import ManagerAccessMixin from eip712.common import SafeTxV1, SafeTxV2 from eip712.messages import hash_eip712_message -from eth_account import Account from eth_utils import keccak from pydantic import BaseModel from .exceptions import ClientResponseError, ClientUnsupportedChainError +from .utils import order_by_signer SafeTx = Union[SafeTxV1, SafeTxV2] SafeTxID = NewType("SafeTxID", HexBytes) @@ -188,11 +188,13 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati ... @abstractmethod - def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature]] = None): + def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): ... @abstractmethod - def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): + def post_signature( + self, safe_tx_hash: SafeTxID, signer: AddressType, signature: MessageSignature + ): ... @@ -267,12 +269,15 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati yield from map(SafeTxConfirmation.parse_obj, data["results"]) url = data["next"] - def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature]] = None): + def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): tx_data = UnexecutedTxData.from_safe_tx(safe_tx) - if sigs: - tx_data.signatures = HexBytes( - reduce(lambda raw_sig, next_sig: raw_sig + next_sig.encode_rsv(), sigs, b"") + tx_data.signatures = HexBytes( + reduce( + lambda raw_sig, next_sig: raw_sig + next_sig.encode_rsv(), + order_by_signer(sigs), + b"", ) + ) url = f"{self.transaction_service_url}/api/v1/multisig-transactions" response = requests.post(url, json={"origin": "ApeWorX/ape-safe", **tx_data.dict()}) @@ -280,7 +285,9 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature if not response.ok: raise ClientResponseError(url, response) - def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): + def post_signature( + self, safe_tx_hash: SafeTxID, signer: AddressType, signature: MessageSignature + ): url = ( f"{self.transaction_service_url}/api" f"/v1/multisig-transactions/{str(safe_tx_hash)}/confirmations" @@ -331,18 +338,17 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati if safe_tx_data := self.transactions.get(safe_tx_hash): yield from safe_tx_data.confirmations - def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature]] = None): + def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): safe_tx_data = UnexecutedTxData.from_safe_tx(safe_tx) - if sigs: - safe_tx_data.confirmations.extend( - SafeTxConfirmation( - owner=Account.recover_message(hash_eip712_message(safe_tx), sig), - submissionDate=datetime.now(), - signature=sig.encode_rsv(), - signatureType=SignatureType.EOA, - ) - for sig in sigs + safe_tx_data.confirmations.extend( + SafeTxConfirmation( + owner=signer, + submissionDate=datetime.now(), + signature=sig.encode_rsv(), + signatureType=SignatureType.EOA, ) + for signer, sig in sigs.items() + ) self.transactions[safe_tx_data.safeTxHash] = safe_tx_data @@ -351,10 +357,12 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Optional[List[MessageSignature else: self.transactions_by_nonce[safe_tx_data.nonce] = [safe_tx_data.safeTxHash] - def post_signature(self, safe_tx_hash: SafeTxID, signature: MessageSignature): + def post_signature( + self, safe_tx_hash: SafeTxID, signer: AddressType, signature: MessageSignature + ): self.transactions[safe_tx_hash].confirmations.append( SafeTxConfirmation( - owner=Account.recover_message(safe_tx_hash, signature), + owner=signer, submissionDate=datetime.now(), signature=signature.encode_rsv(), signatureType=SignatureType.EOA, diff --git a/ape_safe/utils.py b/ape_safe/utils.py new file mode 100644 index 0000000..dfdff50 --- /dev/null +++ b/ape_safe/utils.py @@ -0,0 +1,12 @@ +from typing import Dict, List + +from ape.types import AddressType, MessageSignature +from eth_utils import to_int + + +def order_by_signer(signatures: Dict[AddressType, MessageSignature]) -> List[MessageSignature]: + # NOTE: Must order signatures in ascending order of signer address (converted to int) + def addr_to_int(a: AddressType) -> int: + return to_int(hexstr=a) + + return list(signatures[signer] for signer in sorted(signatures, key=addr_to_int)) From 5457a0bb7549d57db52aeb09c77974da033095e4 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:45:18 -0400 Subject: [PATCH 014/134] fix: load UnexecutedSafeTxData from SafeTx properly --- ape_safe/client.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 2c703d5..f612e76 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -83,15 +83,19 @@ class UnexecutedTxData(BaseModel): modified: datetime safeTxHash: SafeTxID confirmationsRequired: int - confirmations: List[SafeTxConfirmation] - trusted: bool + confirmations: List[SafeTxConfirmation] = [] + trusted: bool = True signatures: Optional[HexBytes] = None @classmethod - def from_safe_tx(cls, safe_tx: SafeTx) -> "UnexecutedTxData": + def from_safe_tx(cls, safe_tx: SafeTx, confirmationsRequired: int) -> "UnexecutedTxData": return cls( # type: ignore[arg-type] safe=safe_tx._verifyingContract_, - **safe_tx, + submissionDate=datetime.now(), + modified=datetime.now(), + confirmationsRequired=confirmationsRequired, + safeTxHash=hash_eip712_message(safe_tx), + **safe_tx._body_["message"], ) def __str__(self) -> str: @@ -270,7 +274,7 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati url = data["next"] def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): - tx_data = UnexecutedTxData.from_safe_tx(safe_tx) + tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) tx_data.signatures = HexBytes( reduce( lambda raw_sig, next_sig: raw_sig + next_sig.encode_rsv(), @@ -339,7 +343,7 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati yield from safe_tx_data.confirmations def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): - safe_tx_data = UnexecutedTxData.from_safe_tx(safe_tx) + safe_tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) safe_tx_data.confirmations.extend( SafeTxConfirmation( owner=signer, From e7fe1e8ddced1ab1be325045db70e819be78d29e Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:46:11 -0400 Subject: [PATCH 015/134] fix: handle scenario where modules/guard is not supported by safe ver --- ape_safe/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index f612e76..781b810 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -7,7 +7,7 @@ import requests # type: ignore from ape.contracts import ContractInstance from ape.types import AddressType, HexBytes, MessageSignature -from ape.utils import ManagerAccessMixin +from ape.utils import ZERO_ADDRESS, ManagerAccessMixin from eip712.common import SafeTxV1, SafeTxV2 from eip712.messages import hash_eip712_message from eth_utils import keccak @@ -322,10 +322,10 @@ def safe_details(self) -> SafeDetails: threshold=self.contract.getThreshold(), owners=self.contract.getOwners(), masterCopy=self.contract.masterCopy(), - modules=self.contract.getModules(), + modules=self.contract.getModules() if hasattr(self.contract, "getModules") else [], # TODO: Add fallback handler getter fallbackHandler=fallback_address, - guard=self.contract.getGuard(), + guard=self.contract.getGuard() if hasattr(self.contract, "getGuard") else ZERO_ADDRESS, version=self.contract.VERSION(), ) From 79d6924265038475ba16b4efc028b8b8869ce5db Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:49:01 -0400 Subject: [PATCH 016/134] refactor: add api confirmations to all confs, handle client error --- ape_safe/accounts.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 18905ba..5ad8acf 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -16,7 +16,13 @@ from ethpm_types import ContractType from .client import BaseSafeClient, MockSafeClient, SafeClient, SafeTx, SafeTxConfirmation -from .exceptions import NoLocalSigners, NotASigner, NotEnoughSignatures, handle_safe_logic_error +from .exceptions import ( + NoLocalSigners, + NotASigner, + NotEnoughSignatures, + SafeClientException, + handle_safe_logic_error, +) from .utils import order_by_signer @@ -200,15 +206,6 @@ def get_signatures( if sig := signer.sign_message(safe_tx.signable_message): yield signer.address, sig - def get_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: - safe_tx_hash = hash_eip712_message(safe_tx) - return { - conf.owner: MessageSignature( - r=conf.signature[:32], s=conf.signature[32:64], v=conf.signature[64] - ) - for conf in self.client.get_confirmations(safe_tx_hash) - } - @handle_safe_logic_error() def create_execute_transaction( self, @@ -306,6 +303,20 @@ def call( # type: ignore[override] return super().call(txn, **call_kwargs) + def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: + safe_tx_hash = hash_eip712_message(safe_tx) + try: + client_confs = self.client.get_confirmations(safe_tx_hash) + except SafeClientException: + return {} + + return { + conf.owner: MessageSignature( + r=conf.signature[:32], s=conf.signature[32:64], v=conf.signature[64] + ) + for conf in client_confs + } + def _contract_approvals(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: safe_tx_exec_args = self._safe_tx_exec_args(safe_tx) safe_tx_hash = self.contract.getTransactionHash(*safe_tx_exec_args) @@ -317,8 +328,10 @@ def _contract_approvals(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSigna } def _all_approvals(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: - # TODO: Combine with approvals from SafeAPI - return self._contract_approvals(safe_tx) + approvals = self.get_api_confirmations(safe_tx) + # NOTE: Do this last because it should take precedence + approvals.update(self._contract_approvals(safe_tx)) + return approvals def sign_transaction( self, From fe2c7629f96ccd5d3b5f11eb0ce792e59243b6c8 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:49:54 -0400 Subject: [PATCH 017/134] refactor: extract submitter loader into it's own method --- ape_safe/accounts.py | 47 +++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 5ad8acf..1d03c6b 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -240,6 +240,28 @@ def compute_prev_signer(self, signer: Union[str, AddressType, BaseAddress]) -> A # NOTE: SENTINEL_OWNERS is the "previous" address to index 0 return AddressType("0x0000000000000000000000000000000000000001") # type: ignore[arg-type] + def load_submitter( + self, + submitter: Union[AddressType, str, None] = None, + ) -> AccountAPI: + if submitter is None: + if len(self.local_signers) == 0: + raise NoLocalSigners() + + return self.local_signers[0] + + elif ( + submitter_address := self.conversion_manager.convert(submitter, AddressType) + in self.account_manager + ): + return self.account_manager[submitter_address] + + elif isinstance(submitter, str) and submitter in self.account_manager.aliases: + return self.account_manager.load(submitter) + + else: + raise # Cannot handle `submitter=type(submitter)` + def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI: # NOTE: Need to override `AccountAPI` behavior for balance checks return self.provider.prepare_transaction(txn) @@ -349,25 +371,11 @@ def sign_transaction( if not submit and submitter: raise # Cannot specify a submitter if not submitting - elif submit and not submitter: - if len(self.local_signers) == 0: - raise NoLocalSigners() - - submitter = self.local_signers[0] - logger.info(f"No submitter specified, so using: {submitter}") - - # NOTE: Works whether `submit` is set or not below here - elif ( - submitter_address := self.conversion_manager.convert(submitter, AddressType) - in self.account_manager - ): - submitter = self.account_manager[submitter_address] - - elif isinstance(submitter, str) and submitter in self.account_manager.aliases: - submitter = self.account_manager.load(submitter) - elif not isinstance(submitter, AccountAPI): - raise # Cannot handle `submitter=type(submitter)` + submitter_not_specified = submitter is None + submitter = self.load_submitter(submitter) + if submitter_not_specified: + logger.info(f"No submitter specified, so using: {submitter}") # Invariant: `submitter` should be either `AccountAPI` or we are not submitting here assert isinstance(submitter, AccountAPI) or not submit @@ -419,8 +427,7 @@ def skip_signer(signer: AccountAPI): and len(sigs_by_signer) >= signatures_required ): # We need to encode the submitter's address for Safe to decode - # NOTE: Should only be triggered if the `submitter` is also a signer - if len(sigs_by_signer) < self.confirmations_required: + if submitter.address in self.signers: sigs_by_signer[submitter.address] = self._preapproved_signature(submitter) # Inherit gas args from safe_tx, if set From 89e7f4706c718caf12365337338e9fad23c82538 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:50:26 -0400 Subject: [PATCH 018/134] feat: add utility function for submitting already conf'd SafeTx --- ape_safe/accounts.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 1d03c6b..c1ecb81 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -355,6 +355,20 @@ def _all_approvals(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature] approvals.update(self._contract_approvals(safe_tx)) return approvals + def submit_safe_tx( + self, + safe_tx: SafeTx, + submitter: Union[AccountAPI, AddressType, str, None] = None, + **txn_options, + ) -> ReceiptAPI: + signatures = self._all_approvals(safe_tx) + txn = self.create_execute_transaction(safe_tx, signatures, **txn_options) + + if not isinstance(submitter, AccountAPI): + submitter = self.load_submitter(submitter) + + return submitter.call(txn) + def sign_transaction( self, txn: TransactionAPI, From 1990cf5bf9deb926c95a167bb6c9717df408aecc Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:50:59 -0400 Subject: [PATCH 019/134] feat: submit to Safe API if not submitting transaction --- ape_safe/accounts.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index c1ecb81..10accd6 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -477,5 +477,7 @@ def skip_signer(signer: AccountAPI): f"Collected {len(sigs_by_signer)}/{self.confirmations_required} signatures " f"for Safe {self.address}#{safe_tx.nonce}" # TODO: put URI ) - # TODO: Submit safe_tx and sigs to Safe API + + # NOTE: Signatures don't have to be in order for Safe API post + self.client.post_transaction(safe_tx, sigs_by_signer) return None From 14b3318b8d4dbe2b2f7007be43eac981d459ae17 Mon Sep 17 00:00:00 2001 From: doggie <3859395+fubuloubu@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:51:27 -0400 Subject: [PATCH 020/134] test: show skipping submit can be executed from mock client --- tests/test_account.py | 71 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index 2b0c46f..db6a06c 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -1,4 +1,5 @@ import pytest +from ape.exceptions import SignatureError def test_init(safe, OWNERS, THRESHOLD, safe_contract): @@ -8,9 +9,11 @@ def test_init(safe, OWNERS, THRESHOLD, safe_contract): assert safe.next_nonce == 0 -@pytest.mark.parametrize("mode", ["impersonate", "sign"]) +@pytest.mark.parametrize("mode", ["impersonate", "api", "sign"]) def test_swap_owner(safe, accounts, OWNERS, mode): impersonate = mode == "impersonate" + submit = mode != "api" + old_owner = safe.signers[0] new_owner = accounts[len(OWNERS)] # replace owner 1 with account N + 1 assert new_owner.address not in safe.signers @@ -18,15 +21,31 @@ def test_swap_owner(safe, accounts, OWNERS, mode): prev_owner = safe.compute_prev_signer(old_owner) - # TODO: Remove `gas_limit` by allowing forking to compute gas limit - receipt = safe.contract.swapOwner( + exec_transaction = lambda: safe.contract.swapOwner( # noqa: E731 prev_owner, old_owner, new_owner, sender=safe, impersonate=impersonate, + submit=submit, ) + if submit: + receipt = exec_transaction() + + else: + # Attempting to execute should emit a `SignatureError` and push `safe_tx` to mock client + assert len(list(safe.client.get_transactions(confirmed=False))) == 0 + with pytest.raises(SignatureError): + exec_transaction() + + assert len(list(safe.client.get_transactions(confirmed=False))) == 1 + + # `safe_tx` is in mock client, extreact it and execute it successfully this time + safe_tx_data = next(safe.client.get_transactions(confirmed=False)) + safe_tx = safe.create_safe_tx(**safe_tx_data.dict()) + receipt = safe.submit_safe_tx(safe_tx) + assert receipt.events == [ safe.contract.RemovedOwner(owner=old_owner), safe.contract.AddedOwner(owner=new_owner), @@ -37,20 +56,38 @@ def test_swap_owner(safe, accounts, OWNERS, mode): assert new_owner.address in safe.signers -@pytest.mark.parametrize("mode", ["impersonate", "sign"]) +@pytest.mark.parametrize("mode", ["impersonate", "api", "sign"]) def test_add_owner(safe, accounts, OWNERS, mode): impersonate = mode == "impersonate" + submit = mode != "api" + new_owner = accounts[len(OWNERS)] # replace owner 1 with account N + 1 assert new_owner.address not in safe.signers - # TODO: Remove `gas_limit` by allowing forking to compute gas limit - receipt = safe.contract.addOwnerWithThreshold( + exec_transaction = lambda: safe.contract.addOwnerWithThreshold( # noqa: E731 new_owner, safe.confirmations_required, sender=safe, impersonate=impersonate, + submit=submit, ) + if submit: + receipt = exec_transaction() + + else: + # Attempting to execute should emit a `SignatureError` and push `safe_tx` to mock client + assert len(list(safe.client.get_transactions(confirmed=False))) == 0 + with pytest.raises(SignatureError): + exec_transaction() + + assert len(list(safe.client.get_transactions(confirmed=False))) == 1 + + # `safe_tx` is in mock client, extreact it and execute it successfully this time + safe_tx_data = next(safe.client.get_transactions(confirmed=False)) + safe_tx = safe.create_safe_tx(**safe_tx_data.dict()) + receipt = safe.submit_safe_tx(safe_tx) + assert receipt.events == [ safe.contract.AddedOwner(owner=new_owner), safe.contract.ExecutionSuccess(), @@ -59,9 +96,10 @@ def test_add_owner(safe, accounts, OWNERS, mode): assert new_owner.address in safe.signers -@pytest.mark.parametrize("mode", ["impersonate", "sign"]) +@pytest.mark.parametrize("mode", ["impersonate", "api", "sign"]) def test_remove_owner(safe, OWNERS, mode): impersonate = mode == "impersonate" + submit = mode != "api" if len(OWNERS) == 1: pytest.skip("Can't remove the only owner") @@ -70,15 +108,32 @@ def test_remove_owner(safe, OWNERS, mode): threshold_changed = new_threshold != safe.confirmations_required prev_owner = safe.compute_prev_signer(old_owner) - receipt = safe.contract.removeOwner( + exec_transaction = lambda: safe.contract.removeOwner( # noqa: E731 prev_owner, old_owner, # Can't set the threshold to zero or more than the number of owners after removal new_threshold, sender=safe, impersonate=impersonate, + submit=submit, ) + if submit: + receipt = exec_transaction() + + else: + # Attempting to execute should emit a `SignatureError` and push `safe_tx` to mock client + assert len(list(safe.client.get_transactions(confirmed=False))) == 0 + with pytest.raises(SignatureError): + exec_transaction() + + assert len(list(safe.client.get_transactions(confirmed=False))) == 1 + + # `safe_tx` is in mock client, extreact it and execute it successfully this time + safe_tx_data = next(safe.client.get_transactions(confirmed=False)) + safe_tx = safe.create_safe_tx(**safe_tx_data.dict()) + receipt = safe.submit_safe_tx(safe_tx) + expected_events = [ safe.contract.RemovedOwner(owner=old_owner), safe.contract.ExecutionSuccess(), From 3b86495a00644f91ec7f86041df57c16f84fc523 Mon Sep 17 00:00:00 2001 From: Dalena Date: Thu, 7 Sep 2023 13:10:47 -0500 Subject: [PATCH 021/134] fix: change safe_tx_hash to hex value --- ape_safe/accounts.py | 2 +- ape_safe/exceptions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index d51c403..9f683a3 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -337,7 +337,7 @@ def call( # type: ignore[override] return super().call(txn, **call_kwargs) def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: - safe_tx_hash = hash_eip712_message(safe_tx) + safe_tx_hash = hash_eip712_message(safe_tx).hex() try: client_confs = self.client.get_confirmations(safe_tx_hash) except SafeClientException: diff --git a/ape_safe/exceptions.py b/ape_safe/exceptions.py index 177a970..c4ec54d 100644 --- a/ape_safe/exceptions.py +++ b/ape_safe/exceptions.py @@ -112,5 +112,5 @@ def __init__(self, chain_id: int): class ClientResponseError(SafeClientException): def __init__(self, endpoint_url: str, response: Response): - error_str = response.json()["message"] + error_str = response.text super().__init__(f"Exception when calling '{endpoint_url}':\n{error_str}") From f1dc4ba2a691bac343d8e6f6568287667e6e64fa Mon Sep 17 00:00:00 2001 From: Dalena Date: Fri, 8 Sep 2023 09:35:46 -0500 Subject: [PATCH 022/134] docs: add more documentation --- ape_safe/accounts.py | 59 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 9f683a3..e126794 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -51,6 +51,10 @@ def accounts(self) -> Iterator[AccountAPI]: def save_account(self, alias: str, address: str): """ Save a new Safe to your ape configuration. + + Args: + alias (str): The alias to save the Safe under. + address (str): The address of the Safe account. """ chain_id = self.provider.chain_id account_data = {"address": address, "deployed_chain_ids": [chain_id]} @@ -58,10 +62,25 @@ def save_account(self, alias: str, address: str): path.write_text(json.dumps(account_data)) def load_account(self, alias: str) -> "SafeAccount": + """ + Loads the Safe account. + + Args: + alias (str): The alias the Safe account is saved under. + + Returns: + ``SafeAccount``: The Safe account loaded. + """ account_path = self.data_folder.joinpath(f"{alias}.json") return SafeAccount(account_file_path=account_path) def delete_account(self, alias: str): + """ + Deletes the local Safe account. + + Args: + alias (str): The alias the Safe account is saved under. + """ path = self.data_folder.joinpath(f"{alias}.json") if path.exists(): @@ -164,6 +183,16 @@ def safe_tx_def(self) -> Type[SafeTx]: ) def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) -> SafeTx: + """ + Creates the Safe transaction. + + Args: + txn (Optional[``TransactionAPI``]): The transaction + **safe_tx_kwargs: The safe transactions specifications, such as ``submitter``. + + Returns: + ``SafeTx``: The Safe Transaction to be used. + """ safe_tx = {} safe_tx["to"] = safe_tx_kwargs.get( "to", txn.receiver if txn else self.address # Self-call, e.g. rejection @@ -372,6 +401,17 @@ def submit_safe_tx( submitter: Union[AccountAPI, AddressType, str, None] = None, **txn_options, ) -> ReceiptAPI: + """ + Submit the safe transaction using the submitter after all signatures have been collected. + + Args: + safe_tx (``SafeTX``): The safe transaction to submit. + submitter (Union[``AccountAPI``, ``AddressType``, str, ``None``]): + The submitter to use for the transaction. Defaults to ``None``. + + Returns: + :class:`~ape.api.transactions.ReceiptAPI` + """ signatures = self._all_approvals(safe_tx) txn = self.create_execute_transaction(safe_tx, signatures, **txn_options) @@ -389,7 +429,24 @@ def sign_transaction( signatures_required: Optional[int] = None, # NOTE: Required if increasing threshold **signer_options, ) -> Optional[TransactionAPI]: - # TODO: Docstring (override AccountAPI) + """ + Sign the created safe transaction for the safe client to post. + **NOTE** ``signatures_required`` is required if the transaction is increasting the threshold. + + Args: + txn (``TransactionAPI``): The contract transaction. + submit (bool): The option to submit the transaction. Defaults to ``True``. + submittter (Union[``AccountAPI``, ``AddressType``, str, None]): + Determine who is submitting the transaction. Defaults to ``None``. + skip (Optional[List[Union[``AccountAPI, `AddressType``, str]]]): + Allow bypassing any specified signer. Defaults to ``None``. + signatures_required (Optional[int]): + The amount of signers required to confirm the transaction. Defaults to ``None``. + **signer_options: Other signer options. + + Returns: + Optional[``TransactionAPI``]: Returns ``None`` if the transaction is successful. + """ safe_tx = self.create_safe_tx(txn, **signer_options) # Determine who is submitting the transaction (if enough signatures are gathered) From ef095b6890ca6ca713679acb1eb92eee1c0b10cb Mon Sep 17 00:00:00 2001 From: Dalena Date: Fri, 8 Sep 2023 09:44:57 -0500 Subject: [PATCH 023/134] fix: empty raises --- ape_safe/_cli.py | 4 ++-- ape_safe/accounts.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 33e6955..6fa0ffe 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -87,7 +87,7 @@ def remove(cli_ctx, alias): safe_container = accounts.containers["safe"] if alias not in safe_container.aliases: - raise + raise ValueError("There is no {alias} in the safe accounts.") address = safe_container.load_account(alias).address if click.confirm(f"Remove safe {address} ({alias})"): @@ -108,7 +108,7 @@ def pending(network, sign_with_local_signers, execute, alias): if execute in accounts.aliases: submitter = accounts.load(execute) else: - raise + raise ValueError("Execute not in account aliases.") elif execute is True: submitter = safe.local_signers[0] diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index e126794..4779b41 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -300,7 +300,7 @@ def load_submitter( return self.account_manager.load(submitter) else: - raise # Cannot handle `submitter=type(submitter)` + raise ValueError(f"Cannot handle {submitter}={type(submitter)}") def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI: # NOTE: Need to override `AccountAPI` behavior for balance checks From ba27c8e1718999414c95d5e7ea68901127b9cb71 Mon Sep 17 00:00:00 2001 From: Dalena Date: Thu, 14 Sep 2023 15:28:36 -0700 Subject: [PATCH 024/134] fix: reivew comments --- .pre-commit-config.yaml | 2 +- ape_safe/_cli.py | 10 ++++++---- ape_safe/accounts.py | 26 +++++++++++++------------- ape_safe/client.py | 1 - ape_safe/exceptions.py | 2 +- setup.py | 1 + 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c6bae2..73e2b1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: rev: v1.5.1 hooks: - id: mypy - additional_dependencies: [types-setuptools, pydantic] + additional_dependencies: [types-requests, types-setuptools, pydantic] - repo: https://github.com/executablebooks/mdformat rev: 0.7.17 diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 6fa0ffe..6d76fce 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -7,7 +7,7 @@ network_option, non_existing_alias_argument, ) -from ape.exceptions import ChainError +from ape.exceptions import AccountsError, ChainError from ape.types import AddressType from .accounts import SafeAccount @@ -87,7 +87,7 @@ def remove(cli_ctx, alias): safe_container = accounts.containers["safe"] if alias not in safe_container.aliases: - raise ValueError("There is no {alias} in the safe accounts.") + raise AccountsError("There is no account with the alias `{alias}` in the safe accounts.") address = safe_container.load_account(alias).address if click.confirm(f"Remove safe {address} ({alias})"): @@ -107,8 +107,10 @@ def pending(network, sign_with_local_signers, execute, alias): if isinstance(execute, str): if execute in accounts.aliases: submitter = accounts.load(execute) + elif execute in accounts: + submitter = accounts[execute] else: - raise ValueError("Execute not in account aliases.") + raise AccountsError(f"`--execute` value '{execute}` not found in local accounts.") elif execute is True: submitter = safe.local_signers[0] @@ -120,7 +122,7 @@ def pending(network, sign_with_local_signers, execute, alias): else: click.echo(f"Txn {safe_tx.nonce}: ({len(confirmations)}/{safe.confirmations_required})") - if execute is not False: + if not execute: signatures = safe.get_confirmations(safe_tx) if len(signatures) >= safe.confirmations_required and click.confirm( f"Submit Txn {safe_tx.nonce}" diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 4779b41..0846a54 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -26,8 +26,7 @@ SafeClientException, handle_safe_logic_error, ) - -from .utils import order_by_signer +from ape_safe.utils import order_by_signer class AccountContainer(AccountContainerAPI): @@ -63,20 +62,20 @@ def save_account(self, alias: str, address: str): def load_account(self, alias: str) -> "SafeAccount": """ - Loads the Safe account. + Load the Safe account. Args: alias (str): The alias the Safe account is saved under. Returns: - ``SafeAccount``: The Safe account loaded. + ``:class:~ape_safe.accounts.SafeAccount``: The Safe account loaded. """ account_path = self.data_folder.joinpath(f"{alias}.json") return SafeAccount(account_file_path=account_path) def delete_account(self, alias: str): """ - Deletes the local Safe account. + Delete the local Safe account. Args: alias (str): The alias the Safe account is saved under. @@ -184,14 +183,14 @@ def safe_tx_def(self) -> Type[SafeTx]: def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) -> SafeTx: """ - Creates the Safe transaction. + Create the Safe transaction. Args: - txn (Optional[``TransactionAPI``]): The transaction + txn (Optional[``TransactionAPI``]): The transaction **safe_tx_kwargs: The safe transactions specifications, such as ``submitter``. Returns: - ``SafeTx``: The Safe Transaction to be used. + ``:class:~ape_safe.client.SafeTx``: The Safe Transaction to be used. """ safe_tx = {} safe_tx["to"] = safe_tx_kwargs.get( @@ -300,7 +299,7 @@ def load_submitter( return self.account_manager.load(submitter) else: - raise ValueError(f"Cannot handle {submitter}={type(submitter)}") + raise ValueError(f"Cannot handle {submitter}={type(submitter)}") def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI: # NOTE: Need to override `AccountAPI` behavior for balance checks @@ -410,7 +409,7 @@ def submit_safe_tx( The submitter to use for the transaction. Defaults to ``None``. Returns: - :class:`~ape.api.transactions.ReceiptAPI` + ``ReceiptAPI`` """ signatures = self._all_approvals(safe_tx) txn = self.create_execute_transaction(safe_tx, signatures, **txn_options) @@ -431,14 +430,15 @@ def sign_transaction( ) -> Optional[TransactionAPI]: """ Sign the created safe transaction for the safe client to post. - **NOTE** ``signatures_required`` is required if the transaction is increasting the threshold. + **NOTE** ``signatures_required`` is required if the transaction is increasting the + threshold. Args: txn (``TransactionAPI``): The contract transaction. submit (bool): The option to submit the transaction. Defaults to ``True``. - submittter (Union[``AccountAPI``, ``AddressType``, str, None]): + submittter (Union[``AccountAPI``, ``AddressType``, str, None]): Determine who is submitting the transaction. Defaults to ``None``. - skip (Optional[List[Union[``AccountAPI, `AddressType``, str]]]): + skip (Optional[List[Union[``AccountAPI, `AddressType``, str]]]): Allow bypassing any specified signer. Defaults to ``None``. signatures_required (Optional[int]): The amount of signers required to confirm the transaction. Defaults to ``None``. diff --git a/ape_safe/client.py b/ape_safe/client.py index 781b810..39b9b21 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -353,7 +353,6 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna ) for signer, sig in sigs.items() ) - self.transactions[safe_tx_data.safeTxHash] = safe_tx_data if safe_tx_data.nonce in self.transactions_by_nonce: diff --git a/ape_safe/exceptions.py b/ape_safe/exceptions.py index c4ec54d..e67d93d 100644 --- a/ape_safe/exceptions.py +++ b/ape_safe/exceptions.py @@ -3,7 +3,7 @@ from ape.exceptions import ApeException, ContractLogicError, SignatureError from ape.types import AddressType -from requests import Response # type: ignore[import] +from requests import Response class ApeSafeException(ApeException): diff --git a/setup.py b/setup.py index 3cf5248..713dc75 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ "lint": [ "black>=23.7.0,<24", # Auto-formatter and linter "mypy>=1.5.1,<2", # Static type analyzer + "types-requests", # Needed for mypy type shed "types-setuptools", # Needed for mypy type shed "flake8>=6.1.0,<7", # Style linter "isort>=5.10.1,<6", # Import sorting linter From 3aef2adf0f92aeac5711836afe0eca3707d53575 Mon Sep 17 00:00:00 2001 From: Dalena Date: Fri, 15 Sep 2023 10:21:50 -0700 Subject: [PATCH 025/134] fix: mock client transaction hash --- ape_safe/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 39b9b21..9b6b936 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -17,7 +17,7 @@ from .utils import order_by_signer SafeTx = Union[SafeTxV1, SafeTxV2] -SafeTxID = NewType("SafeTxID", HexBytes) +SafeTxID = NewType("SafeTxID", str) TRANSACTION_SERVICE_URL = { 1: "https://safe-transaction-mainnet.safe.global", @@ -94,7 +94,7 @@ def from_safe_tx(cls, safe_tx: SafeTx, confirmationsRequired: int) -> "Unexecute submissionDate=datetime.now(), modified=datetime.now(), confirmationsRequired=confirmationsRequired, - safeTxHash=hash_eip712_message(safe_tx), + safeTxHash=hash_eip712_message(safe_tx).hex(), **safe_tx._body_["message"], ) From a9316d4bcee66ea232d87413f407b0c01e258beb Mon Sep 17 00:00:00 2001 From: Dalena Date: Mon, 18 Sep 2023 10:27:07 -0700 Subject: [PATCH 026/134] fix: cli pending transactions --- ape_safe/_cli.py | 4 ++-- ape_safe/client.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 6d76fce..daca7f1 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -87,7 +87,7 @@ def remove(cli_ctx, alias): safe_container = accounts.containers["safe"] if alias not in safe_container.aliases: - raise AccountsError("There is no account with the alias `{alias}` in the safe accounts.") + raise AccountsError(f"There is no account with the alias `{alias}` in the safe accounts.") address = safe_container.load_account(alias).address if click.confirm(f"Remove safe {address} ({alias})"): @@ -123,7 +123,7 @@ def pending(network, sign_with_local_signers, execute, alias): click.echo(f"Txn {safe_tx.nonce}: ({len(confirmations)}/{safe.confirmations_required})") if not execute: - signatures = safe.get_confirmations(safe_tx) + signatures = safe.get_api_confirmations(safe_tx) if len(signatures) >= safe.confirmations_required and click.confirm( f"Submit Txn {safe_tx.nonce}" ): diff --git a/ape_safe/client.py b/ape_safe/client.py index 9b6b936..33e42f9 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -163,13 +163,13 @@ def get_transactions( if txn.nonce < starting_nonce: break # NOTE: order is largest nonce to smallest, so safe to break here - isConfirmed = len(txn.confirmations) >= txn.confirmationsRequired + is_confirmed = len(txn.confirmations) >= txn.confirmationsRequired if confirmed is not None: if not confirmed and isinstance(txn, ExecutedTxData): break # NOTE: Break at the first executed transaction - elif confirmed and not isConfirmed: + elif confirmed and not is_confirmed: continue # NOTE: Skip not confirmed transactions if txn.nonce < next_nonce and isinstance(txn, UnexecutedTxData): From 15fe09b8026459c3270caf11d4f196bd6830b6d5 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 19 Sep 2023 15:15:30 -0500 Subject: [PATCH 027/134] refactor: cli --- ape_safe/_cli.py | 119 ++++++++++++++++++++++++------------------ ape_safe/accounts.py | 2 +- ape_safe/client.py | 4 +- tests/test_account.py | 6 +-- 4 files changed, 74 insertions(+), 57 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index daca7f1..c94dbc2 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -1,5 +1,4 @@ import click -from ape import Contract, accounts, convert, networks from ape.cli import ( NetworkBoundCommand, ape_cli_context, @@ -7,17 +6,18 @@ network_option, non_existing_alias_argument, ) -from ape.exceptions import AccountsError, ChainError +from ape.exceptions import ChainError from ape.types import AddressType +from click import BadArgumentUsage, BadOptionUsage -from .accounts import SafeAccount -from .client import ExecutedTxData, SafeClient +from ape_safe.accounts import SafeAccount +from ape_safe.client import ExecutedTxData, SafeClient @click.group(short_help="Manage Safe accounts and view Safe API data") def cli(): """ - Command-line helpder for managing Safes. You can add Safes to your local accounts, + Command-line helper for managing Safes. You can add Safes to your local accounts, or view data from any Safe using the Safe API client. """ @@ -26,15 +26,16 @@ def cli(): @ape_cli_context() @network_option() def _list(cli_ctx, network): - safes = accounts.get_accounts_by_type(type_=SafeAccount) - num_of_accts = len(safes) + _ = network # Needed for NetworkBoundCommand + safes = cli_ctx.account_manager.get_accounts_by_type(type_=SafeAccount) + safes_length = len(safes) - if num_of_accts == 0: + if safes_length == 0: cli_ctx.logger.warning("No Safes found.") return - header = f"Found {num_of_accts} Safe" - header += "s:" if num_of_accts > 1 else ":" + header = f"Found {safes_length} Safe" + header += "s:" if safes_length > 1 else ":" click.echo(header) for account in safes: @@ -45,9 +46,8 @@ def _list(cli_ctx, network): try: extras.append(f"version: '{account.version}'") except ChainError: - cli_ctx.logger.warning( - f"Not connected to the network that {account.address} is deployed" - ) + # Not connected to the network where safe is deployed + pass extras_display = f" ({', '.join(extras)})" if extras else "" click.echo(f" {account.address}{extras_display}") @@ -59,10 +59,11 @@ def _list(cli_ctx, network): @click.argument("address", type=AddressType) @non_existing_alias_argument() def add(cli_ctx, network, address, alias): - address = convert(address, AddressType) - safe_contract = Contract(address) + _ = network # Needed for NetworkBoundCommand + address = cli_ctx.conversion_manager.convert(address, AddressType) + safe_contract = cli_ctx.chain_manager.contracts.instance_at(address) version_display = safe_contract.VERSION() - req_confs = safe_contract.getThreshold() + required_confirmations = safe_contract.getThreshold() signers_display = "\n - ".join(safe_contract.getOwners()) cli_ctx.logger.info( @@ -70,62 +71,77 @@ def add(cli_ctx, network, address, alias): network: {network} address: {safe_contract.address} version: {version_display} - required confirmations: {req_confs} + required confirmations: {required_confirmations} signers: - {signers_display} """ ) if click.confirm("Add safe"): - accounts.containers["safe"].save_account(alias, address) + cli_ctx.account_manager.containers["safe"].save_account(alias, address) @cli.command(short_help="Stop tracking a locally-tracked Safe") @ape_cli_context() @existing_alias_argument() def remove(cli_ctx, alias): - safe_container = accounts.containers["safe"] + safe_container = cli_ctx.account_manager.containers["safe"] if alias not in safe_container.aliases: - raise AccountsError(f"There is no account with the alias `{alias}` in the safe accounts.") + raise BadArgumentUsage( + f"There is no account with the alias `{alias}` in the safe accounts." + ) address = safe_container.load_account(alias).address if click.confirm(f"Remove safe {address} ({alias})"): safe_container.delete_account(alias) +def _execute_callback(ctx, param, val): + if isinstance(val, str): + if val in ctx.obj.account_manager.aliases: + return ctx.obj.account_manager.load(val) + elif val in ctx.obj.account_manager: + return ctx.obj.account_manager[val] + else: + raise BadOptionUsage( + "--execute", f"`--execute` value '{val}` not found in local accounts." + ) + + # Additional handling may occur in command definition. + return val + + @cli.command( cls=NetworkBoundCommand, short_help="See pending transactions for a locally-tracked Safe" ) +@ape_cli_context() @network_option() @click.option("sign_with_local_signers", "--sign", is_flag=True) -@click.option("--execute", is_flag=False, flag_value=True, default=False) +@click.option("--execute", is_flag=True, flag_value=True, default=False, callback=_execute_callback) @existing_alias_argument(account_type=SafeAccount) -def pending(network, sign_with_local_signers, execute, alias): - safe = accounts.load(alias) - - if isinstance(execute, str): - if execute in accounts.aliases: - submitter = accounts.load(execute) - elif execute in accounts: - submitter = accounts[execute] - else: - raise AccountsError(f"`--execute` value '{execute}` not found in local accounts.") +def pending(cli_ctx, network, sign_with_local_signers, execute, alias): + _ = network # Needed for NetworkBoundCommand + safe = cli_ctx.account_manager.load(alias) + submitter = execute + if submitter is True: + if not safe.local_signers: + cli_ctx.abort("Cannot execute without a local signer.") - elif execute is True: submitter = safe.local_signers[0] for safe_tx, confirmations in safe.pending_transactions(): + click.echo( + f"Transaction {safe_tx.nonce}: ({len(confirmations)}/{safe.confirmations_required})" + ) + if sign_with_local_signers and len(confirmations) < safe.confirmations_required: pass # TODO: sign `safe_tx` with local signers not in `confirmations` - else: - click.echo(f"Txn {safe_tx.nonce}: ({len(confirmations)}/{safe.confirmations_required})") - if not execute: signatures = safe.get_api_confirmations(safe_tx) if len(signatures) >= safe.confirmations_required and click.confirm( - f"Submit Txn {safe_tx.nonce}" + f"Submit Transaction {safe_tx.nonce}" ): submitter.call(safe.create_execute_transaction(safe_tx, signatures)) @@ -136,14 +152,15 @@ def pending(network, sign_with_local_signers, execute, alias): @click.argument("txn-ids", type=int, nargs=-1) @ape_cli_context() def reject(cli_ctx, network, alias, txn_ids): - safe = accounts.load(alias) - pending = safe.client.get_transactions(starting_nonce=safe.next_nonce) + _ = network # Needed for NetworkBoundCommand + safe = cli_ctx.account_manager.load(alias) + pending_transactions = safe.client.get_transactions(starting_nonce=safe.next_nonce) for txn_id in txn_ids: try: - txn = next(txn for txn in pending if txn_id == txn.nonce) + txn = next(txn for txn in pending_transactions if txn_id == txn.nonce) except StopIteration: - cli_ctx.logger.error(f"Transaction ID '{txn_id}' is not a pending transaction.") + # NOTE: Not a pending transaction. continue if click.confirm(f"{txn}\nCancel Transaction?"): @@ -154,19 +171,19 @@ def reject(cli_ctx, network, alias, txn_ids): cls=NetworkBoundCommand, short_help="View and filter all transactions for a given Safe using Safe API", ) +@ape_cli_context() @network_option() @click.argument("address", type=AddressType) -@click.option("--confirmed", type=bool, default=None) -def all_txns(network, address, confirmed): - safe_container = accounts.containers["safe"] - - if address in safe_container.aliases: - address = safe_container.load_account(address).address - - else: - address = convert(address, AddressType) - - client = SafeClient(address=address, chain_id=networks.provider.chain_id) +@click.option("--confirmed", is_flag=True, default=None) +def all_txns(cli_ctx, network, address, confirmed): + _ = network # Needed for NetworkBoundCommand + safe_container = cli_ctx.account_manager.containers["safe"] + address = ( + safe_container.load_account(address).address + if address in safe_container.aliases + else cli_ctx.conversion_manager.convert(address, AddressType) + ) + client = SafeClient(address=address, chain_id=cli_ctx.chain_manager.provider.chain_id) for txn in client.get_transactions(confirmed=confirmed): if isinstance(txn, ExecutedTxData): diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 0846a54..0ad7a24 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -136,7 +136,7 @@ def client(self) -> BaseSafeClient: if chain_id not in self.account_file["deployed_chain_ids"]: raise ClientUnavailable(f"Safe client not valid on chain '{chain_id}'.") - if self.provider.network.name == "local": + if self.provider.network.name == LOCAL_NETWORK_NAME: return MockSafeClient(contract=self.contract) return SafeClient(address=self.address, chain_id=self.provider.chain_id) diff --git a/ape_safe/client.py b/ape_safe/client.py index 33e42f9..10f29c8 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -88,12 +88,12 @@ class UnexecutedTxData(BaseModel): signatures: Optional[HexBytes] = None @classmethod - def from_safe_tx(cls, safe_tx: SafeTx, confirmationsRequired: int) -> "UnexecutedTxData": + def from_safe_tx(cls, safe_tx: SafeTx, confirmations_required: int) -> "UnexecutedTxData": return cls( # type: ignore[arg-type] safe=safe_tx._verifyingContract_, submissionDate=datetime.now(), modified=datetime.now(), - confirmationsRequired=confirmationsRequired, + confirmationsRequired=confirmations_required, safeTxHash=hash_eip712_message(safe_tx).hex(), **safe_tx._body_["message"], ) diff --git a/tests/test_account.py b/tests/test_account.py index db6a06c..07b222a 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -41,7 +41,7 @@ def test_swap_owner(safe, accounts, OWNERS, mode): assert len(list(safe.client.get_transactions(confirmed=False))) == 1 - # `safe_tx` is in mock client, extreact it and execute it successfully this time + # `safe_tx` is in mock client, extract it and execute it successfully this time safe_tx_data = next(safe.client.get_transactions(confirmed=False)) safe_tx = safe.create_safe_tx(**safe_tx_data.dict()) receipt = safe.submit_safe_tx(safe_tx) @@ -83,7 +83,7 @@ def test_add_owner(safe, accounts, OWNERS, mode): assert len(list(safe.client.get_transactions(confirmed=False))) == 1 - # `safe_tx` is in mock client, extreact it and execute it successfully this time + # `safe_tx` is in mock client, extract it and execute it successfully this time safe_tx_data = next(safe.client.get_transactions(confirmed=False)) safe_tx = safe.create_safe_tx(**safe_tx_data.dict()) receipt = safe.submit_safe_tx(safe_tx) @@ -129,7 +129,7 @@ def test_remove_owner(safe, OWNERS, mode): assert len(list(safe.client.get_transactions(confirmed=False))) == 1 - # `safe_tx` is in mock client, extreact it and execute it successfully this time + # `safe_tx` is in mock client, extract it and execute it successfully this time safe_tx_data = next(safe.client.get_transactions(confirmed=False)) safe_tx = safe.create_safe_tx(**safe_tx_data.dict()) receipt = safe.submit_safe_tx(safe_tx) From 8ec71d8ad74d90f7c9aaf75ca6a240bf1085b75c Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 19 Sep 2023 15:31:57 -0500 Subject: [PATCH 028/134] refactor: small fixes --- ape_safe/accounts.py | 129 ++++++++++++++++++++++------------------- ape_safe/client.py | 8 +-- ape_safe/exceptions.py | 8 ++- setup.py | 3 + 4 files changed, 84 insertions(+), 64 deletions(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 0ad7a24..3e19597 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -19,6 +19,7 @@ from ape_safe.client import BaseSafeClient, MockSafeClient, SafeClient, SafeTx, SafeTxConfirmation from ape_safe.exceptions import ( + ApeSafeError, ClientUnavailable, NoLocalSigners, NotASigner, @@ -39,13 +40,19 @@ def aliases(self) -> Iterator[str]: for p in self._account_files: yield p.stem - def __len__(self) -> int: - return len([*self._account_files]) - @property def accounts(self) -> Iterator[AccountAPI]: for account_file in self._account_files: - yield SafeAccount(account_file_path=account_file) # type: ignore + yield SafeAccount(account_file_path=account_file) + + def __len__(self) -> int: + return len([*self._account_files]) + + def __setitem__(self, alias: str, address: str): + self.save_account(alias, address) + + def __delitem__(self, alias: str): + self.delete_account(alias) def save_account(self, alias: str, address: str): """ @@ -57,7 +64,10 @@ def save_account(self, alias: str, address: str): """ chain_id = self.provider.chain_id account_data = {"address": address, "deployed_chain_ids": [chain_id]} - path = self.data_folder.joinpath(f"{alias}.json") + path = self._get_path(alias) + if path.is_file(): + raise ApeSafeError(f"Safe with alias '{alias}' already exists.") + path.write_text(json.dumps(account_data)) def load_account(self, alias: str) -> "SafeAccount": @@ -70,20 +80,37 @@ def load_account(self, alias: str) -> "SafeAccount": Returns: ``:class:~ape_safe.accounts.SafeAccount``: The Safe account loaded. """ - account_path = self.data_folder.joinpath(f"{alias}.json") + account_path = self._get_path(alias) + if not account_path.is_file(): + raise ApeSafeError(f"Safe with '{alias}' does not exist") + return SafeAccount(account_file_path=account_path) def delete_account(self, alias: str): """ Delete the local Safe account. + **NOTE**: If the account does not exist, nothing happens. Args: alias (str): The alias the Safe account is saved under. """ - path = self.data_folder.joinpath(f"{alias}.json") + self._get_path(alias).unlink(missing_ok=True) + + def _get_path(self, alias: str) -> Path: + return self.data_folder.joinpath(f"{alias}.json") + + +def get_signatures( + safe_tx: SafeTx, + signers: Iterable[AccountAPI], +) -> Iterator[Tuple[AddressType, MessageSignature]]: + for signer in signers: + if sig := signer.sign_message(safe_tx.signable_message): + yield signer.address, sig + - if path.exists(): - path.unlink() +def _safe_tx_exec_args(safe_tx: SafeTx) -> List: + return list(safe_tx._body_["message"].values()) class SafeAccount(AccountAPI): @@ -125,10 +152,9 @@ def fallback_handler(self) -> Optional[ContractInstance]: slot = keccak(text="fallback_manager.handler.address") value = self.provider.get_storage_at(self.address, slot) address = self.network_manager.ecosystem.decode_address(value[-20:]) - if address != ZERO_ADDRESS: - return self.chain_manager.contracts.instance_at(address) - else: - return None + return ( + self.chain_manager.contracts.instance_at(address) if address != ZERO_ADDRESS else None + ) @cached_property def client(self) -> BaseSafeClient: @@ -136,7 +162,7 @@ def client(self) -> BaseSafeClient: if chain_id not in self.account_file["deployed_chain_ids"]: raise ClientUnavailable(f"Safe client not valid on chain '{chain_id}'.") - if self.provider.network.name == LOCAL_NETWORK_NAME: + elif self.provider.network.name == LOCAL_NETWORK_NAME: return MockSafeClient(contract=self.contract) return SafeClient(address=self.address, chain_id=self.provider.chain_id) @@ -192,32 +218,23 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) Returns: ``:class:~ape_safe.client.SafeTx``: The Safe Transaction to be used. """ - safe_tx = {} - safe_tx["to"] = safe_tx_kwargs.get( - "to", txn.receiver if txn else self.address # Self-call, e.g. rejection - ) - safe_tx["value"] = safe_tx_kwargs.get("value", txn.value if txn else 0) - safe_tx["data"] = safe_tx_kwargs.get("data", txn.data if txn else b"") - safe_tx["nonce"] = safe_tx_kwargs.get( - "nonce", self.next_nonce - ) # NOTE: Caution do NOT use self.nonce - safe_tx["operation"] = safe_tx_kwargs.get("operation", 0) - - safe_tx["safeTxGas"] = safe_tx_kwargs.get("safeTxGas", 0) - safe_tx["baseGas"] = safe_tx_kwargs.get("baseGas", 0) - safe_tx["gasPrice"] = safe_tx_kwargs.get("gasPrice", 0) - safe_tx["gasToken"] = safe_tx_kwargs.get( - "gasToken", "0x0000000000000000000000000000000000000000" - ) - safe_tx["refundReceiver"] = safe_tx_kwargs.get( - "refundReceiver", "0x0000000000000000000000000000000000000000" - ) - + safe_tx = { + "to": txn.receiver if txn else self.address, # Self-call, e.g. rejection + "value": txn.value if txn else 0, + "data": txn.data if txn else b"", + "nonce": self.next_nonce, + "operation": 0, + "safeTxGas": 0, + "gasPrice": 0, + "gasToken": ZERO_ADDRESS, + "refundReceiver": ZERO_ADDRESS, + } + safe_tx = {**safe_tx, **{k: v for k, v in safe_tx_kwargs.items() if k in safe_tx}} return self.safe_tx_def(**safe_tx) def pending_transactions(self) -> Iterator[Tuple[SafeTx, List[SafeTxConfirmation]]]: - for unexec_tx_data in self.client.get_transactions(confirmed=False): - yield self.create_safe_tx(**unexec_tx_data.dict()), unexec_tx_data.confirmations + for executed_tx in self.client.get_transactions(confirmed=False): + yield self.create_safe_tx(**executed_tx.dict()), executed_tx.confirmations @property def local_signers(self) -> List[AccountAPI]: @@ -236,15 +253,6 @@ def local_signers(self) -> List[AccountAPI]: return list(container[address] for address in self.signers if address in container) - def get_signatures( - self, - safe_tx: SafeTx, - signers: Iterable[AccountAPI], - ) -> Iterator[Tuple[AddressType, MessageSignature]]: - for signer in signers: - if sig := signer.sign_message(safe_tx.signable_message): - yield signer.address, sig - @handle_safe_logic_error() def create_execute_transaction( self, @@ -305,9 +313,6 @@ def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI: # NOTE: Need to override `AccountAPI` behavior for balance checks return self.provider.prepare_transaction(txn) - def _safe_tx_exec_args(self, safe_tx: SafeTx) -> List: - return list(safe_tx._body_["message"].values()) - def _preapproved_signature( self, signer: Union[AddressType, BaseAddress, str] ) -> MessageSignature: @@ -323,12 +328,13 @@ def _preapproved_signature( @handle_safe_logic_error() def _impersonated_call(self, txn: TransactionAPI, **safe_tx_and_call_kwargs) -> ReceiptAPI: safe_tx = self.create_safe_tx(txn, **safe_tx_and_call_kwargs) - safe_tx_exec_args = self._safe_tx_exec_args(safe_tx) + safe_tx_exec_args = _safe_tx_exec_args(safe_tx) signatures = {} # Bypass signature collection logic and attempt to submit by impersonation # NOTE: Only works for fork and local network providers that support `set_storage` safe_tx_hash = self.contract.getTransactionHash(*safe_tx_exec_args) + signer_address = None for signer_address in self.signers[: self.confirmations_required]: # NOTE: `approvedHashes` is `address => safe_tx_hash => num_confs` @ slot 8 # TODO: Use native ape slot indexing, once available @@ -367,7 +373,7 @@ def call( # type: ignore[override] def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: safe_tx_hash = hash_eip712_message(safe_tx).hex() try: - client_confs = self.client.get_confirmations(safe_tx_hash) + client_confirmations = self.client.get_confirmations(safe_tx_hash) except SafeClientException: return {} @@ -375,11 +381,11 @@ def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSig conf.owner: MessageSignature( r=conf.signature[:32], s=conf.signature[32:64], v=conf.signature[64] ) - for conf in client_confs + for conf in client_confirmations } def _contract_approvals(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: - safe_tx_exec_args = self._safe_tx_exec_args(safe_tx) + safe_tx_exec_args = _safe_tx_exec_args(safe_tx) safe_tx_hash = self.contract.getTransactionHash(*safe_tx_exec_args) return { @@ -436,7 +442,7 @@ def sign_transaction( Args: txn (``TransactionAPI``): The contract transaction. submit (bool): The option to submit the transaction. Defaults to ``True``. - submittter (Union[``AccountAPI``, ``AddressType``, str, None]): + submitter (Union[``AccountAPI``, ``AddressType``, str, None]): Determine who is submitting the transaction. Defaults to ``None``. skip (Optional[List[Union[``AccountAPI, `AddressType``, str]]]): Allow bypassing any specified signer. Defaults to ``None``. @@ -457,10 +463,13 @@ def sign_transaction( submitter_not_specified = submitter is None submitter = self.load_submitter(submitter) if submitter_not_specified: - logger.info(f"No submitter specified, so using: {submitter}") + logger.info(f"No submitter specified, so using: '{submitter.address}'.") - # Invariant: `submitter` should be either `AccountAPI` or we are not submitting here - assert isinstance(submitter, AccountAPI) or not submit + if not isinstance(submitter, AccountAPI) or not submitter: + # Invariant + raise ValueError( + "`submitter` should be either `AccountAPI` or we are not submitting here." + ) # Garner either M or M - 1 signatures, depending on if we are submitting # and whether the submitter is also a signer (both must be true to submit M - 1). @@ -496,7 +505,7 @@ def skip_signer(signer: AccountAPI): sigs_by_signer.update( dict( islice( - self.get_signatures(safe_tx, available_signers), + get_signatures(safe_tx, available_signers), signatures_required - len(sigs_by_signer), ) ) @@ -516,7 +525,7 @@ def skip_signer(signer: AccountAPI): gas_args = {"gas_limit": txn.gas_limit} if txn.type == TransactionType.STATIC: - gas_args["gas_price"] = txn.gas_price # type: ignore[attr-defined] + gas_args["gas_price"] = txn.gas_price else: gas_args["max_fee"] = txn.max_fee @@ -548,4 +557,6 @@ def skip_signer(signer: AccountAPI): # NOTE: Signatures don't have to be in order for Safe API post self.client.post_transaction(safe_tx, sigs_by_signer) + + # Return None so that Ape does not try to submit the transaction. return None diff --git a/ape_safe/client.py b/ape_safe/client.py index 10f29c8..24a7f29 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -4,7 +4,7 @@ from functools import reduce from typing import Dict, Iterator, List, NewType, Optional, Set, Union -import requests # type: ignore +import requests from ape.contracts import ContractInstance from ape.types import AddressType, HexBytes, MessageSignature from ape.utils import ZERO_ADDRESS, ManagerAccessMixin @@ -13,8 +13,8 @@ from eth_utils import keccak from pydantic import BaseModel -from .exceptions import ClientResponseError, ClientUnsupportedChainError -from .utils import order_by_signer +from ape_safe.exceptions import ClientResponseError, ClientUnsupportedChainError +from ape_safe.utils import order_by_signer SafeTx = Union[SafeTxV1, SafeTxV2] SafeTxID = NewType("SafeTxID", str) @@ -89,7 +89,7 @@ class UnexecutedTxData(BaseModel): @classmethod def from_safe_tx(cls, safe_tx: SafeTx, confirmations_required: int) -> "UnexecutedTxData": - return cls( # type: ignore[arg-type] + return cls( safe=safe_tx._verifyingContract_, submissionDate=datetime.now(), modified=datetime.now(), diff --git a/ape_safe/exceptions.py b/ape_safe/exceptions.py index e67d93d..92fb781 100644 --- a/ape_safe/exceptions.py +++ b/ape_safe/exceptions.py @@ -1,7 +1,7 @@ from contextlib import ContextDecorator from typing import Optional, Type -from ape.exceptions import ApeException, ContractLogicError, SignatureError +from ape.exceptions import AccountsError, ApeException, ContractLogicError, SignatureError from ape.types import AddressType from requests import Response @@ -10,6 +10,12 @@ class ApeSafeException(ApeException): pass +class ApeSafeError(ApeSafeException, AccountsError): + """ + An error to raise in place of AccountsError for the ``ape-safe`` plugin. + """ + + class NotASigner(ApeSafeException): def __init__(self, signer: AddressType): super().__init__(f"{signer} is not a valid signer.") diff --git a/setup.py b/setup.py index 713dc75..f116faa 100644 --- a/setup.py +++ b/setup.py @@ -58,6 +58,9 @@ install_requires=[ "eth-ape>=0.6.11,<0.7.0", "eip712>=0.2.0,<0.3.0", + "requests>=2.31.0,<3", + "pydantic", # Use same version as eth-ape + "eth-utils", # Use same version as eth-ape ], entry_points={ "ape_cli_subcommands": [ From 31b86e61de23937021a858a890d8bc94de28bee6 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 19 Sep 2023 15:38:39 -0500 Subject: [PATCH 029/134] fix: submit kwarg fix --- ape_safe/accounts.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 3e19597..19200aa 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -365,6 +365,14 @@ def call( # type: ignore[override] impersonate: bool = False, **call_kwargs, ) -> ReceiptAPI: + # NOTE: The `or True` at the end is in case we get `submit=None`, + # meaning use the default. + default_submit = not impersonate + submit = ( + call_kwargs.pop("submit_transaction", call_kwargs.pop("submit", default_submit)) + or not default_submit + ) + call_kwargs["submit"] = submit if impersonate: return self._impersonated_call(txn, **call_kwargs) From cc416d3db341eb0dd8120de50b8e6297ec4b7171 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 19 Sep 2023 15:44:25 -0500 Subject: [PATCH 030/134] feat: sign on pending --- ape_safe/_cli.py | 4 +++- ape_safe/accounts.py | 33 +++++++++++++++++++++++++++++-- ape_safe/client.py | 45 ++++++++++++++++++++++++++++++++++++++---- ape_safe/exceptions.py | 14 ++++++++++--- docs/detailed.rst | 2 +- 5 files changed, 87 insertions(+), 11 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index c94dbc2..0dfa656 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -135,8 +135,10 @@ def pending(cli_ctx, network, sign_with_local_signers, execute, alias): f"Transaction {safe_tx.nonce}: ({len(confirmations)}/{safe.confirmations_required})" ) + # Add signatures, if was requested to do so. if sign_with_local_signers and len(confirmations) < safe.confirmations_required: - pass # TODO: sign `safe_tx` with local signers not in `confirmations` + safe.add_signatures(safe_tx, confirmations) + cli_ctx.logger.success(f"Added signature to transaction {safe_tx.nonce}") if not execute: signatures = safe.get_api_confirmations(safe_tx) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 19200aa..fa91d32 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -1,7 +1,7 @@ import json from itertools import islice from pathlib import Path -from typing import Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union +from typing import Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union, cast from ape.api import AccountAPI, AccountContainerAPI, ReceiptAPI, TransactionAPI from ape.api.address import BaseAddress @@ -17,7 +17,14 @@ from eth_utils import keccak, to_bytes, to_int from ethpm_types import ContractType -from ape_safe.client import BaseSafeClient, MockSafeClient, SafeClient, SafeTx, SafeTxConfirmation +from ape_safe.client import ( + BaseSafeClient, + MockSafeClient, + SafeClient, + SafeTx, + SafeTxConfirmation, + SafeTxID, +) from ape_safe.exceptions import ( ApeSafeError, ClientUnavailable, @@ -568,3 +575,25 @@ def skip_signer(signer: AccountAPI): # Return None so that Ape does not try to submit the transaction. return None + + def add_signatures(self, safe_tx: SafeTx, confirmations: List[SafeTxConfirmation]): + if not self.local_signers: + raise ApeSafeError("Cannot sign without local signers.") + + elif len(confirmations) >= self.confirmations_required: + raise ApeSafeError("Signatures full.") + + signers = [ + ls for ls in self.local_signers if ls.address not in [c.owner for c in confirmations] + ] + for signer in signers: + if not (tx_hash_result := next((c.transactionHash for c in confirmations), None)): + tx_hash_result = self.contract.getTransactionHash(*safe_tx) + + if tx_hash_result is None: + raise ApeSafeError("Failed to get transaction hash.") + + tx_hash = HexBytes(tx_hash_result).hex() + signature = signer.sign_message(safe_tx.signable_message) + if signature: + self.client.post_signature(cast(SafeTxID, tx_hash), signer.address, signature) diff --git a/ape_safe/client.py b/ape_safe/client.py index 24a7f29..6e00757 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -13,7 +13,11 @@ from eth_utils import keccak from pydantic import BaseModel -from ape_safe.exceptions import ClientResponseError, ClientUnsupportedChainError +from ape_safe.exceptions import ( + ClientResponseError, + ClientUnsupportedChainError, + MultisigTransactionNotFoundError, +) from ape_safe.utils import order_by_signer SafeTx = Union[SafeTxV1, SafeTxV2] @@ -98,6 +102,21 @@ def from_safe_tx(cls, safe_tx: SafeTx, confirmations_required: int) -> "Unexecut **safe_tx._body_["message"], ) + @property + def base_tx_dict(self) -> Dict: + return { + "to": self.to, + "value": self.value, + "data": self.data, + "operation": self.operation, + "safeTxGas": self.safeTxGas, + "baseGas": self.baseGas, + "gasPrice": self.gasPrice, + "gasToken": self.gasToken, + "refundReceiver": self.refundReceiver, + "nonce": self.nonce, + } + def __str__(self) -> str: # TODO: Decode data data_hex = self.data.hex() if self.data else "" @@ -282,9 +301,21 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna b"", ) ) + post_dict: Dict = {} + for key, value in tx_data.dict().items(): + if isinstance(value, HexBytes): + post_dict[key] = value.hex() + elif isinstance(value, OperationType): + post_dict[key] = int(value) + elif isinstance(value, datetime): + # not needed + continue + else: + post_dict[key] = value - url = f"{self.transaction_service_url}/api/v1/multisig-transactions" - response = requests.post(url, json={"origin": "ApeWorX/ape-safe", **tx_data.dict()}) + url = f"{self.transaction_service_url}/api/v1/safes/{tx_data.safe}/multisig-transactions" + json_data = {"origin": "ApeWorX/ape-safe", **post_dict} + response = requests.post(url, json=json_data) if not response.ok: raise ClientResponseError(url, response) @@ -292,15 +323,21 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna def post_signature( self, safe_tx_hash: SafeTxID, signer: AddressType, signature: MessageSignature ): + if not isinstance(safe_tx_hash, str): + raise TypeError("Expecting str-like type for 'safe_tx_hash'.") + url = ( f"{self.transaction_service_url}/api" - f"/v1/multisig-transactions/{str(safe_tx_hash)}/confirmations" + f"/v1/multisig-transactions/{safe_tx_hash}/confirmations" ) response = requests.post( url, json={"origin": "ApeWorX/ape-safe", "signature": signature.encode_rsv().hex()} ) if not response.ok: + if "The requested resource was not found on this server" in response.text: + raise MultisigTransactionNotFoundError(safe_tx_hash, url, response) + raise ClientResponseError(url, response) diff --git a/ape_safe/exceptions.py b/ape_safe/exceptions.py index 92fb781..2ecb162 100644 --- a/ape_safe/exceptions.py +++ b/ape_safe/exceptions.py @@ -117,6 +117,14 @@ def __init__(self, chain_id: int): class ClientResponseError(SafeClientException): - def __init__(self, endpoint_url: str, response: Response): - error_str = response.text - super().__init__(f"Exception when calling '{endpoint_url}':\n{error_str}") + def __init__(self, endpoint_url: str, response: Response, message: Optional[str] = None): + self.endpoint_url = endpoint_url + self.response = response + message = message or f"Exception when calling '{endpoint_url}':\n{response.text}" + super().__init__(message) + + +class MultisigTransactionNotFoundError(ClientResponseError): + def __init__(self, tx_hash: str, endpoint_url: str, response: Response): + message = f"Multisig transaction '{tx_hash}' not found." + super().__init__(endpoint_url, response, message=message) diff --git a/docs/detailed.rst b/docs/detailed.rst index bbfca03..dd44924 100644 --- a/docs/detailed.rst +++ b/docs/detailed.rst @@ -63,7 +63,7 @@ Play around the same way you would do with a normal account: >>> safe.post_transaction(safe_tx) # Post an additional confirmation to the transaction service - >>> signtature = safe.sign_transaction(safe_tx) + >>> signature = safe.sign_transaction(safe_tx) >>> safe.post_signature(safe_tx, signature) # Retrieve pending transactions from the transaction service From 07d5c5b6fc925ab2acafaeb6f62bffdadeddeaf6 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 19 Sep 2023 15:48:34 -0500 Subject: [PATCH 031/134] fix: make requests work again --- ape_safe/_cli.py | 9 +++++++-- ape_safe/accounts.py | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 0dfa656..bd5d5c9 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -78,7 +78,10 @@ def add(cli_ctx, network, address, alias): ) if click.confirm("Add safe"): - cli_ctx.account_manager.containers["safe"].save_account(alias, address) + container = cli_ctx.account_manager.containers["safe"] + container.save_account(alias, address) + + cli_ctx.logger.success(f"Safe '{address}' ({alias}) added.") @cli.command(short_help="Stop tracking a locally-tracked Safe") @@ -96,6 +99,8 @@ def remove(cli_ctx, alias): if click.confirm(f"Remove safe {address} ({alias})"): safe_container.delete_account(alias) + cli_ctx.logger.success(f"Safe '{address}' ({alias}) removed.") + def _execute_callback(ctx, param, val): if isinstance(val, str): @@ -138,7 +143,7 @@ def pending(cli_ctx, network, sign_with_local_signers, execute, alias): # Add signatures, if was requested to do so. if sign_with_local_signers and len(confirmations) < safe.confirmations_required: safe.add_signatures(safe_tx, confirmations) - cli_ctx.logger.success(f"Added signature to transaction {safe_tx.nonce}") + cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") if not execute: signatures = safe.get_api_confirmations(safe_tx) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index fa91d32..bcdc2ca 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -386,6 +386,7 @@ def call( # type: ignore[override] return super().call(txn, **call_kwargs) def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: + safe_tx.to = safe_tx.to or ZERO_ADDRESS safe_tx_hash = hash_eip712_message(safe_tx).hex() try: client_confirmations = self.client.get_confirmations(safe_tx_hash) From 3c40b5d251b496874be3d9c989599486a6096db5 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 19 Sep 2023 15:58:40 -0500 Subject: [PATCH 032/134] chore: small fixes --- ape_safe/_cli.py | 21 +++++++-------------- ape_safe/accounts.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index bd5d5c9..5c6c662 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -11,7 +11,7 @@ from click import BadArgumentUsage, BadOptionUsage from ape_safe.accounts import SafeAccount -from ape_safe.client import ExecutedTxData, SafeClient +from ape_safe.client import ExecutedTxData @click.group(short_help="Manage Safe accounts and view Safe API data") @@ -28,14 +28,14 @@ def cli(): def _list(cli_ctx, network): _ = network # Needed for NetworkBoundCommand safes = cli_ctx.account_manager.get_accounts_by_type(type_=SafeAccount) - safes_length = len(safes) + number_of_safes = len(safes) - if safes_length == 0: + if number_of_safes == 0: cli_ctx.logger.warning("No Safes found.") return - header = f"Found {safes_length} Safe" - header += "s:" if safes_length > 1 else ":" + header = f"Found {number_of_safes} Safe" + header += "s:" if number_of_safes > 1 else ":" click.echo(header) for account in safes: @@ -91,9 +91,7 @@ def remove(cli_ctx, alias): safe_container = cli_ctx.account_manager.containers["safe"] if alias not in safe_container.aliases: - raise BadArgumentUsage( - f"There is no account with the alias `{alias}` in the safe accounts." - ) + raise BadArgumentUsage(f"There is no safe with the alias `{alias}`.") address = safe_container.load_account(alias).address if click.confirm(f"Remove safe {address} ({alias})"): @@ -185,12 +183,7 @@ def reject(cli_ctx, network, alias, txn_ids): def all_txns(cli_ctx, network, address, confirmed): _ = network # Needed for NetworkBoundCommand safe_container = cli_ctx.account_manager.containers["safe"] - address = ( - safe_container.load_account(address).address - if address in safe_container.aliases - else cli_ctx.conversion_manager.convert(address, AddressType) - ) - client = SafeClient(address=address, chain_id=cli_ctx.chain_manager.provider.chain_id) + client = safe_container._get_client(address) for txn in client.get_transactions(confirmed=confirmed): if isinstance(txn, ExecutedTxData): diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index bcdc2ca..bbd8e70 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -103,6 +103,23 @@ def delete_account(self, alias: str): """ self._get_path(alias).unlink(missing_ok=True) + def _get_client(self, key: str) -> BaseSafeClient: + if key in self.aliases: + safe = self.load_account(key) + return safe.client + + elif key in self: + account = self[key] # type: ignore[index] + if not isinstance(account, SafeAccount): + raise TypeError("Safe container returned non-safe account.") + + return account.client + + else: + # Is not locally managed. + address = self.conversion_manager.convert(key, AddressType) + return SafeClient(address=address, chain_id=self.chain_manager.provider.chain_id) + def _get_path(self, alias: str) -> Path: return self.data_folder.joinpath(f"{alias}.json") From bd361f9fd83c29428ab8986a647679348e2e5859 Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 20 Sep 2023 13:43:00 -0500 Subject: [PATCH 033/134] fix: remove unnecessary confirmation error --- ape_safe/accounts.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index bbd8e70..d36317d 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -598,9 +598,6 @@ def add_signatures(self, safe_tx: SafeTx, confirmations: List[SafeTxConfirmation if not self.local_signers: raise ApeSafeError("Cannot sign without local signers.") - elif len(confirmations) >= self.confirmations_required: - raise ApeSafeError("Signatures full.") - signers = [ ls for ls in self.local_signers if ls.address not in [c.owner for c in confirmations] ] From 6f79d355c91305b505598c066a3280703952280e Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 20 Sep 2023 13:46:06 -0500 Subject: [PATCH 034/134] docs: add service url notes --- ape_safe/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 6e00757..c1a5150 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -24,6 +24,9 @@ SafeTxID = NewType("SafeTxID", str) TRANSACTION_SERVICE_URL = { + # NOTE: If URLs need to be updated, a list of available service URLs can be found at + # https://docs.safe.global/safe-core-api/available-services. + # NOTE: There should be no trailing slashes at the end of the URL. 1: "https://safe-transaction-mainnet.safe.global", 5: "https://safe-transaction-goerli.safe.global", 10: "https://safe-transaction-optimism.safe.global", @@ -33,10 +36,10 @@ 250: "https://safe-txservice.fantom.network", 288: "https://safe-transaction.mainnet.boba.network", # NOTE: Not supported yet - # 8453: "https://safe-transaction-base.safe.global/", + # 8453: "https://safe-transaction-base.safe.global", 42161: "https://safe-transaction-arbitrum.safe.global", 43114: "https://safe-transaction-avalanche.safe.global", - 84531: "https://safe-transaction-base-testnet.safe.global/", + 84531: "https://safe-transaction-base-testnet.safe.global", } From d2f06582e9336224e5f424ed2073f2b116ab7162 Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 20 Sep 2023 13:47:13 -0500 Subject: [PATCH 035/134] fix: exception handler response --- ape_safe/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ape_safe/exceptions.py b/ape_safe/exceptions.py index 2ecb162..5ce29c4 100644 --- a/ape_safe/exceptions.py +++ b/ape_safe/exceptions.py @@ -120,7 +120,7 @@ class ClientResponseError(SafeClientException): def __init__(self, endpoint_url: str, response: Response, message: Optional[str] = None): self.endpoint_url = endpoint_url self.response = response - message = message or f"Exception when calling '{endpoint_url}':\n{response.text}" + message = message or f"Exception when calling '{endpoint_url}':\n{response}" super().__init__(message) From 1acec78c22ac3586329a9e9db80b492037d36bad Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 20 Sep 2023 14:43:44 -0500 Subject: [PATCH 036/134] feat: add safe tx argument --- ape_safe/client.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index c1a5150..2cb2c70 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -324,8 +324,14 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna raise ClientResponseError(url, response) def post_signature( - self, safe_tx_hash: SafeTxID, signer: AddressType, signature: MessageSignature + self, safe_tx_or_hash: Union[SafeTx, SafeTxID], signature: MessageSignature ): + if isinstance(safe_tx_or_hash, SafeTx): + safe_tx = safe_tx_or_hash + safe_tx_hash = hash_eip712_message(safe_tx).hex() + elif isinstance(safe_tx_or_hash, SafeTxID): + safe_tx_hash = safe_tx_or_hash + if not isinstance(safe_tx_hash, str): raise TypeError("Expecting str-like type for 'safe_tx_hash'.") From fac48e3650c27ffc50628e43c3f6d372ab19daa0 Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 20 Sep 2023 16:44:05 -0500 Subject: [PATCH 037/134] fix: change default operation delegatecall --- ape_safe/multisend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ape_safe/multisend.py b/ape_safe/multisend.py index 92d7c86..85ec801 100644 --- a/ape_safe/multisend.py +++ b/ape_safe/multisend.py @@ -6,7 +6,7 @@ from ape.utils import ManagerAccessMixin, cached_property from eth_abi.packed import encode_packed -from .exceptions import UnsupportedChainError, ValueRequired +from ape_safe.exceptions import UnsupportedChainError, ValueRequired MULTISEND_CODE = HexBytes( "0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100dc6004803603602" @@ -145,7 +145,7 @@ def add( self, call, *args, - delegatecall=False, + delegatecall=True, value=0, ): """ From fe27eb7c0cb88ff76fcef329da1f55c0d23b802e Mon Sep 17 00:00:00 2001 From: Dalena Date: Fri, 22 Sep 2023 12:40:46 -0500 Subject: [PATCH 038/134] chore: mypy --- ape_safe/client.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 2cb2c70..4773ef0 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -2,7 +2,7 @@ from datetime import datetime from enum import Enum from functools import reduce -from typing import Dict, Iterator, List, NewType, Optional, Set, Union +from typing import Dict, Iterator, List, NewType, Optional, Set, Union, get_args import requests from ape.contracts import ContractInstance @@ -24,7 +24,7 @@ SafeTxID = NewType("SafeTxID", str) TRANSACTION_SERVICE_URL = { - # NOTE: If URLs need to be updated, a list of available service URLs can be found at + # NOTE: If URLs need to be updated, a list of available service URLs can be found at # https://docs.safe.global/safe-core-api/available-services. # NOTE: There should be no trailing slashes at the end of the URL. 1: "https://safe-transaction-mainnet.safe.global", @@ -324,12 +324,15 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna raise ClientResponseError(url, response) def post_signature( - self, safe_tx_or_hash: Union[SafeTx, SafeTxID], signature: MessageSignature + self, + safe_tx_or_hash: Union[SafeTx, SafeTxID], + signer: AddressType, + signature: MessageSignature, ): - if isinstance(safe_tx_or_hash, SafeTx): + if isinstance(safe_tx_or_hash, get_args(SafeTx)): safe_tx = safe_tx_or_hash safe_tx_hash = hash_eip712_message(safe_tx).hex() - elif isinstance(safe_tx_or_hash, SafeTxID): + elif isinstance(safe_tx_or_hash, get_args(SafeTxID)): safe_tx_hash = safe_tx_or_hash if not isinstance(safe_tx_hash, str): @@ -407,9 +410,12 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna self.transactions_by_nonce[safe_tx_data.nonce] = [safe_tx_data.safeTxHash] def post_signature( - self, safe_tx_hash: SafeTxID, signer: AddressType, signature: MessageSignature + self, + safe_tx_or_hash: Union[SafeTx, SafeTxID], + signer: AddressType, + signature: MessageSignature, ): - self.transactions[safe_tx_hash].confirmations.append( + self.transactions[safe_tx_or_hash].confirmations.append( SafeTxConfirmation( owner=signer, submissionDate=datetime.now(), From 806d811147089199fd915d676f488bfa85d57f3e Mon Sep 17 00:00:00 2001 From: Dalena Date: Tue, 26 Sep 2023 09:49:49 -0500 Subject: [PATCH 039/134] fix: multisend operation kwarg --- ape_safe/multisend.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ape_safe/multisend.py b/ape_safe/multisend.py index 85ec801..78fdbc3 100644 --- a/ape_safe/multisend.py +++ b/ape_safe/multisend.py @@ -145,7 +145,7 @@ def add( self, call, *args, - delegatecall=True, + delegatecall=False, value=0, ): """ @@ -214,6 +214,10 @@ def __call__(self, **txn_kwargs) -> ReceiptAPI: :class:`~ape.api.transactions.ReceiptAPI` """ self._validate_calls(**txn_kwargs) + if "operation" not in txn_kwargs and ( + "impersonate" not in txn_kwargs or not txn_kwargs["impersonate"] + ): + txn_kwargs["operation"] = 1 return self.handler(b"".join(self.encoded_calls), **txn_kwargs) def as_transaction(self, **txn_kwargs) -> TransactionAPI: @@ -226,6 +230,10 @@ def as_transaction(self, **txn_kwargs) -> TransactionAPI: self._validate_calls(**txn_kwargs) # NOTE: Will fail using `self.handler.as_transaction` because handler # expects to be called only via delegatecall + if "operation" not in txn_kwargs and ( + "impersonate" not in txn_kwargs or not txn_kwargs["impersonate"] + ): + txn_kwargs["operation"] = 1 return self.network_manager.ecosystem.create_transaction( receiver=self.handler.contract.address, data=self.handler.encode_input(b"".join(self.encoded_calls)), From 1578a241de243c3ae5b13c2ed17a0d608b6334de Mon Sep 17 00:00:00 2001 From: Dalena Date: Tue, 26 Sep 2023 10:12:17 -0500 Subject: [PATCH 040/134] chore: mypy --- ape_safe/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 4773ef0..ed02c81 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -219,7 +219,10 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna @abstractmethod def post_signature( - self, safe_tx_hash: SafeTxID, signer: AddressType, signature: MessageSignature + self, + safe_tx_or_hash: Union[SafeTx, SafeTxID], + signer: AddressType, + signature: MessageSignature, ): ... From 880f43374e5ccdeffd352ef147013a8421addac3 Mon Sep 17 00:00:00 2001 From: Dalena Date: Thu, 28 Sep 2023 11:03:16 -0500 Subject: [PATCH 041/134] fix: txn_kwargs impersonate --- ape_safe/multisend.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ape_safe/multisend.py b/ape_safe/multisend.py index 78fdbc3..02a5376 100644 --- a/ape_safe/multisend.py +++ b/ape_safe/multisend.py @@ -214,9 +214,7 @@ def __call__(self, **txn_kwargs) -> ReceiptAPI: :class:`~ape.api.transactions.ReceiptAPI` """ self._validate_calls(**txn_kwargs) - if "operation" not in txn_kwargs and ( - "impersonate" not in txn_kwargs or not txn_kwargs["impersonate"] - ): + if "operation" not in txn_kwargs and (not txn_kwargs.get("impersonate", False)): txn_kwargs["operation"] = 1 return self.handler(b"".join(self.encoded_calls), **txn_kwargs) @@ -230,9 +228,7 @@ def as_transaction(self, **txn_kwargs) -> TransactionAPI: self._validate_calls(**txn_kwargs) # NOTE: Will fail using `self.handler.as_transaction` because handler # expects to be called only via delegatecall - if "operation" not in txn_kwargs and ( - "impersonate" not in txn_kwargs or not txn_kwargs["impersonate"] - ): + if "operation" not in txn_kwargs and (not txn_kwargs.get("impersonate", False)): txn_kwargs["operation"] = 1 return self.network_manager.ecosystem.create_transaction( receiver=self.handler.contract.address, From 33ab14d5f8c4648fc4c2f724c64ec2abf81260e9 Mon Sep 17 00:00:00 2001 From: Dalena Date: Thu, 12 Oct 2023 13:23:43 -0500 Subject: [PATCH 042/134] test: multisend operation --- tests/conftest.py | 16 + tests/contracts/Token.json | 6487 +++++++++ tests/contracts/VyperVault.json | 21618 ++++++++++++++++++++++++++++++ tests/test_multisend.py | 31 + 4 files changed, 28152 insertions(+) create mode 100644 tests/contracts/Token.json create mode 100644 tests/contracts/VyperVault.json create mode 100644 tests/test_multisend.py diff --git a/tests/conftest.py b/tests/conftest.py index d7e405d..af072d9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,10 +3,14 @@ from pathlib import Path import pytest +from ape.contracts import ContractContainer from ape.utils import ZERO_ADDRESS +from ethpm_types import ContractType from ape_safe.accounts import SafeAccount +projects_directory = Path(__file__).parent / "contracts" + @pytest.fixture(scope="session") def deployer(accounts): @@ -94,3 +98,15 @@ def safe_data_file(chain, safe_contract): @pytest.fixture def safe(safe_data_file): return SafeAccount(account_file_path=safe_data_file) + + +@pytest.fixture +def token(deployer): + contract = ContractType.parse_file(projects_directory / "Token.json") + return deployer.deploy(ContractContainer(contract)) + + +@pytest.fixture +def vault(deployer, token): + vault = ContractContainer(ContractType.parse_file(projects_directory / "VyperVault.json")) + return deployer.deploy(vault, token) diff --git a/tests/contracts/Token.json b/tests/contracts/Token.json new file mode 100644 index 0000000..7570a0f --- /dev/null +++ b/tests/contracts/Token.json @@ -0,0 +1,6487 @@ +{ + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "allowance", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "DEBUG_mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "arg0", + "type": "address" + }, + { + "name": "arg1", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "ast": { + "ast_type": "Module", + "children": [ + { + "ast_type": "ImportFrom", + "children": [], + "classification": 0, + "col_offset": 0, + "end_col_offset": 34, + "end_lineno": 2, + "lineno": 2, + "name": "ERC20", + "src": { + "jump_code": "", + "length": 34, + "start": 17 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 17, + "end_lineno": 4, + "lineno": 4, + "src": { + "jump_code": "", + "length": 5, + "start": 65 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 10, + "end_lineno": 4, + "lineno": 4, + "src": { + "jump_code": "", + "length": 10, + "start": 53 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 4, + "lineno": 4, + "src": { + "jump_code": "", + "length": 17, + "start": 53 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 20, + "end_col_offset": 27, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 7, + "start": 92 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 13, + "end_col_offset": 19, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 6, + "start": 85 + } + } + ], + "classification": 0, + "col_offset": 13, + "end_col_offset": 28, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 15, + "start": 85 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 11, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 11, + "start": 72 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 28, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 28, + "start": 72 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Tuple", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 26, + "end_col_offset": 33, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 7, + "start": 127 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 35, + "end_col_offset": 42, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 7, + "start": 136 + } + } + ], + "classification": 0, + "col_offset": 26, + "end_col_offset": 42, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 16, + "start": 127 + } + } + ], + "classification": 0, + "col_offset": 26, + "end_col_offset": 42, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 16, + "start": 127 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 18, + "end_col_offset": 25, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 7, + "start": 119 + } + } + ], + "classification": 0, + "col_offset": 18, + "end_col_offset": 43, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 25, + "start": 119 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 6, + "start": 112 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 44, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 33, + "start": 112 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 9, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 9, + "start": 101 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 44, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 44, + "start": 101 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Tuple", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 26, + "end_col_offset": 33, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 7, + "start": 172 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Tuple", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 43, + "end_col_offset": 50, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 7, + "start": 189 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 52, + "end_col_offset": 59, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 7, + "start": 198 + } + } + ], + "classification": 0, + "col_offset": 43, + "end_col_offset": 59, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 16, + "start": 189 + } + } + ], + "classification": 0, + "col_offset": 43, + "end_col_offset": 59, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 16, + "start": 189 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 35, + "end_col_offset": 42, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 7, + "start": 181 + } + } + ], + "classification": 0, + "col_offset": 35, + "end_col_offset": 60, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 25, + "start": 181 + } + } + ], + "classification": 0, + "col_offset": 26, + "end_col_offset": 60, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 34, + "start": 172 + } + } + ], + "classification": 0, + "col_offset": 26, + "end_col_offset": 60, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 34, + "start": 172 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 18, + "end_col_offset": 25, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 7, + "start": 164 + } + } + ], + "classification": 0, + "col_offset": 18, + "end_col_offset": 61, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 43, + "start": 164 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 6, + "start": 157 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 62, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 51, + "start": 157 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 9, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 9, + "start": 146 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 62, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 62, + "start": 146 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 22, + "end_col_offset": 24, + "end_lineno": 10, + "lineno": 10, + "src": { + "jump_code": "", + "length": 2, + "start": 232 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 24, + "end_lineno": 10, + "lineno": 10, + "src": { + "jump_code": "", + "length": 2, + "start": 232 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 15, + "end_col_offset": 21, + "end_lineno": 10, + "lineno": 10, + "src": { + "jump_code": "", + "length": 6, + "start": 225 + } + } + ], + "classification": 0, + "col_offset": 15, + "end_col_offset": 25, + "end_lineno": 10, + "lineno": 10, + "src": { + "jump_code": "", + "length": 10, + "start": 225 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 6, + "end_col_offset": 14, + "end_lineno": 10, + "lineno": 10, + "src": { + "jump_code": "", + "length": 8, + "start": 216 + } + } + ], + "classification": 0, + "col_offset": 6, + "end_col_offset": 26, + "end_lineno": 10, + "lineno": 10, + "src": { + "jump_code": "", + "length": 20, + "start": 216 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 4, + "end_lineno": 10, + "lineno": 10, + "src": { + "jump_code": "", + "length": 4, + "start": 210 + } + }, + { + "ast_type": "Str", + "children": [], + "classification": 0, + "col_offset": 29, + "end_col_offset": 41, + "end_lineno": 10, + "lineno": 10, + "src": { + "jump_code": "", + "length": 12, + "start": 239 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 41, + "end_lineno": 10, + "lineno": 10, + "src": { + "jump_code": "", + "length": 41, + "start": 210 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 24, + "end_col_offset": 25, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 1, + "start": 276 + } + } + ], + "classification": 0, + "col_offset": 24, + "end_col_offset": 25, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 1, + "start": 276 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 23, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 6, + "start": 269 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 26, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 9, + "start": 269 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 8, + "start": 260 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 27, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 19, + "start": 260 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 6, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 6, + "start": 252 + } + }, + { + "ast_type": "Str", + "children": [], + "classification": 0, + "col_offset": 30, + "end_col_offset": 36, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 6, + "start": 282 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 36, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 36, + "start": 252 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 24, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 5, + "start": 308 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 10, + "end_col_offset": 18, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 8, + "start": 299 + } + } + ], + "classification": 0, + "col_offset": 10, + "end_col_offset": 25, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 15, + "start": 299 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 8, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 8, + "start": 289 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 28, + "end_col_offset": 30, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 2, + "start": 317 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 30, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 30, + "start": 289 + } + }, + { + "ast_type": "EventDef", + "children": [ + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 20, + "end_col_offset": 27, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 7, + "start": 357 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 7, + "start": 349 + } + } + ], + "classification": 0, + "col_offset": 12, + "end_col_offset": 28, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 16, + "start": 349 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 6, + "start": 341 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 24, + "start": 341 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 29, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 7, + "start": 388 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 14, + "end_col_offset": 21, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 7, + "start": 380 + } + } + ], + "classification": 0, + "col_offset": 14, + "end_col_offset": 30, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 16, + "start": 380 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 12, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 8, + "start": 370 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 26, + "start": 370 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 17, + "lineno": 17, + "src": { + "jump_code": "", + "length": 7, + "start": 409 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 17, + "lineno": 17, + "src": { + "jump_code": "", + "length": 6, + "start": 401 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 19, + "end_lineno": 17, + "lineno": 17, + "src": { + "jump_code": "", + "length": 15, + "start": 401 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 19, + "end_lineno": 17, + "lineno": 14, + "name": "Transfer", + "src": { + "jump_code": "", + "length": 95, + "start": 321 + } + }, + { + "ast_type": "EventDef", + "children": [ + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 26, + "end_lineno": 20, + "lineno": 20, + "src": { + "jump_code": "", + "length": 7, + "start": 453 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 18, + "end_lineno": 20, + "lineno": 20, + "src": { + "jump_code": "", + "length": 7, + "start": 445 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 27, + "end_lineno": 20, + "lineno": 20, + "src": { + "jump_code": "", + "length": 16, + "start": 445 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 9, + "end_lineno": 20, + "lineno": 20, + "src": { + "jump_code": "", + "length": 5, + "start": 438 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 27, + "end_lineno": 20, + "lineno": 20, + "src": { + "jump_code": "", + "length": 23, + "start": 438 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 21, + "end_col_offset": 28, + "end_lineno": 21, + "lineno": 21, + "src": { + "jump_code": "", + "length": 7, + "start": 483 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 13, + "end_col_offset": 20, + "end_lineno": 21, + "lineno": 21, + "src": { + "jump_code": "", + "length": 7, + "start": 475 + } + } + ], + "classification": 0, + "col_offset": 13, + "end_col_offset": 29, + "end_lineno": 21, + "lineno": 21, + "src": { + "jump_code": "", + "length": 16, + "start": 475 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 11, + "end_lineno": 21, + "lineno": 21, + "src": { + "jump_code": "", + "length": 7, + "start": 466 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 29, + "end_lineno": 21, + "lineno": 21, + "src": { + "jump_code": "", + "length": 25, + "start": 466 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 15, + "end_col_offset": 22, + "end_lineno": 22, + "lineno": 22, + "src": { + "jump_code": "", + "length": 7, + "start": 507 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 13, + "end_lineno": 22, + "lineno": 22, + "src": { + "jump_code": "", + "length": 9, + "start": 496 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 22, + "end_lineno": 22, + "lineno": 22, + "src": { + "jump_code": "", + "length": 18, + "start": 496 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 22, + "end_lineno": 22, + "lineno": 19, + "name": "Approval", + "src": { + "jump_code": "", + "length": 96, + "start": 418 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 3, + "end_lineno": 27, + "lineno": 27, + "src": { + "jump_code": "", + "length": 3, + "start": 533 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 28, + "lineno": 28, + "src": { + "jump_code": "", + "length": 4, + "start": 570 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 28, + "lineno": 28, + "src": { + "jump_code": "", + "length": 11, + "start": 563 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 25, + "lineno": 25, + "src": { + "jump_code": "", + "length": 4, + "start": 518 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 26, + "lineno": 26, + "src": { + "jump_code": "", + "length": 8, + "start": 524 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 21, + "end_col_offset": 23, + "end_lineno": 27, + "lineno": 27, + "src": { + "jump_code": "", + "length": 2, + "start": 554 + } + } + ], + "classification": 0, + "col_offset": 21, + "end_col_offset": 23, + "end_lineno": 27, + "lineno": 27, + "src": { + "jump_code": "", + "length": 2, + "start": 554 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 14, + "end_col_offset": 20, + "end_lineno": 27, + "lineno": 27, + "src": { + "jump_code": "", + "length": 6, + "start": 547 + } + } + ], + "classification": 0, + "col_offset": 14, + "end_col_offset": 24, + "end_lineno": 27, + "lineno": 27, + "src": { + "jump_code": "", + "length": 10, + "start": 547 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 15, + "end_lineno": 28, + "lineno": 27, + "name": "name", + "src": { + "jump_code": "", + "length": 41, + "start": 533 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 3, + "end_lineno": 33, + "lineno": 33, + "src": { + "jump_code": "", + "length": 3, + "start": 593 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 34, + "lineno": 34, + "src": { + "jump_code": "", + "length": 6, + "start": 631 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 17, + "end_lineno": 34, + "lineno": 34, + "src": { + "jump_code": "", + "length": 13, + "start": 624 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 31, + "lineno": 31, + "src": { + "jump_code": "", + "length": 4, + "start": 578 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 32, + "lineno": 32, + "src": { + "jump_code": "", + "length": 8, + "start": 584 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 23, + "end_col_offset": 24, + "end_lineno": 33, + "lineno": 33, + "src": { + "jump_code": "", + "length": 1, + "start": 616 + } + } + ], + "classification": 0, + "col_offset": 23, + "end_col_offset": 24, + "end_lineno": 33, + "lineno": 33, + "src": { + "jump_code": "", + "length": 1, + "start": 616 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 16, + "end_col_offset": 22, + "end_lineno": 33, + "lineno": 33, + "src": { + "jump_code": "", + "length": 6, + "start": 609 + } + } + ], + "classification": 0, + "col_offset": 16, + "end_col_offset": 25, + "end_lineno": 33, + "lineno": 33, + "src": { + "jump_code": "", + "length": 9, + "start": 609 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 34, + "lineno": 33, + "name": "symbol", + "src": { + "jump_code": "", + "length": 44, + "start": 593 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 3, + "end_lineno": 39, + "lineno": 39, + "src": { + "jump_code": "", + "length": 3, + "start": 656 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 19, + "end_lineno": 40, + "lineno": 40, + "src": { + "jump_code": "", + "length": 8, + "start": 692 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 19, + "end_lineno": 40, + "lineno": 40, + "src": { + "jump_code": "", + "length": 15, + "start": 685 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 37, + "lineno": 37, + "src": { + "jump_code": "", + "length": 4, + "start": 641 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 38, + "lineno": 38, + "src": { + "jump_code": "", + "length": 8, + "start": 647 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 18, + "end_col_offset": 23, + "end_lineno": 39, + "lineno": 39, + "src": { + "jump_code": "", + "length": 5, + "start": 674 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 19, + "end_lineno": 40, + "lineno": 39, + "name": "decimals", + "src": { + "jump_code": "", + "length": 44, + "start": 656 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 23, + "end_col_offset": 30, + "end_lineno": 44, + "lineno": 44, + "src": { + "jump_code": "", + "length": 7, + "start": 736 + } + } + ], + "classification": 0, + "col_offset": 13, + "end_col_offset": 30, + "end_lineno": 44, + "lineno": 44, + "src": { + "jump_code": "", + "length": 17, + "start": 726 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 40, + "end_col_offset": 47, + "end_lineno": 44, + "lineno": 44, + "src": { + "jump_code": "", + "length": 7, + "start": 753 + } + } + ], + "classification": 0, + "col_offset": 32, + "end_col_offset": 47, + "end_lineno": 44, + "lineno": 44, + "src": { + "jump_code": "", + "length": 15, + "start": 745 + } + } + ], + "classification": 1, + "col_offset": 13, + "end_col_offset": 47, + "end_lineno": 44, + "lineno": 44, + "src": { + "jump_code": "", + "length": 34, + "start": 726 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 40, + "end_lineno": 45, + "lineno": 45, + "src": { + "jump_code": "", + "length": 36, + "start": 775 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 22, + "end_lineno": 45, + "lineno": 45, + "src": { + "jump_code": "", + "length": 3, + "start": 790 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 29, + "end_lineno": 45, + "lineno": 45, + "src": { + "jump_code": "", + "length": 10, + "start": 790 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 29, + "end_lineno": 45, + "lineno": 45, + "src": { + "jump_code": "", + "length": 10, + "start": 790 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 45, + "lineno": 45, + "src": { + "jump_code": "", + "length": 4, + "start": 775 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 45, + "lineno": 45, + "src": { + "jump_code": "", + "length": 14, + "start": 775 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 45, + "lineno": 45, + "src": { + "jump_code": "", + "length": 26, + "start": 775 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 34, + "end_col_offset": 40, + "end_lineno": 45, + "lineno": 45, + "src": { + "jump_code": "", + "length": 6, + "start": 805 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 40, + "end_lineno": 45, + "lineno": 45, + "src": { + "jump_code": "", + "length": 36, + "start": 775 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Add", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 46, + "lineno": 46, + "src": { + "jump_code": "", + "length": 34, + "start": 816 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 27, + "end_lineno": 46, + "lineno": 46, + "src": { + "jump_code": "", + "length": 8, + "start": 831 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 27, + "end_lineno": 46, + "lineno": 46, + "src": { + "jump_code": "", + "length": 8, + "start": 831 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 46, + "lineno": 46, + "src": { + "jump_code": "", + "length": 4, + "start": 816 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 46, + "lineno": 46, + "src": { + "jump_code": "", + "length": 14, + "start": 816 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 46, + "lineno": 46, + "src": { + "jump_code": "", + "length": 24, + "start": 816 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 32, + "end_col_offset": 38, + "end_lineno": 46, + "lineno": 46, + "src": { + "jump_code": "", + "length": 6, + "start": 844 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 46, + "lineno": 46, + "src": { + "jump_code": "", + "length": 34, + "start": 816 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 20, + "end_lineno": 47, + "lineno": 47, + "src": { + "jump_code": "", + "length": 3, + "start": 868 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 27, + "end_lineno": 47, + "lineno": 47, + "src": { + "jump_code": "", + "length": 10, + "start": 868 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 29, + "end_col_offset": 37, + "end_lineno": 47, + "lineno": 47, + "src": { + "jump_code": "", + "length": 8, + "start": 880 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 39, + "end_col_offset": 45, + "end_lineno": 47, + "lineno": 47, + "src": { + "jump_code": "", + "length": 6, + "start": 890 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 47, + "lineno": 47, + "src": { + "jump_code": "", + "length": 8, + "start": 859 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 46, + "end_lineno": 47, + "lineno": 47, + "src": { + "jump_code": "", + "length": 38, + "start": 859 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 46, + "end_lineno": 47, + "lineno": 47, + "src": { + "jump_code": "", + "length": 42, + "start": 855 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "NameConstant", + "children": [], + "classification": 0, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 48, + "lineno": 48, + "src": { + "jump_code": "", + "length": 4, + "start": 909 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 48, + "lineno": 48, + "src": { + "jump_code": "", + "length": 11, + "start": 902 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 43, + "lineno": 43, + "src": { + "jump_code": "", + "length": 8, + "start": 704 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 52, + "end_col_offset": 56, + "end_lineno": 44, + "lineno": 44, + "src": { + "jump_code": "", + "length": 4, + "start": 765 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 15, + "end_lineno": 48, + "lineno": 44, + "name": "transfer", + "src": { + "jump_code": "", + "length": 200, + "start": 713 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 21, + "end_col_offset": 28, + "end_lineno": 52, + "lineno": 52, + "src": { + "jump_code": "", + "length": 7, + "start": 947 + } + } + ], + "classification": 0, + "col_offset": 12, + "end_col_offset": 28, + "end_lineno": 52, + "lineno": 52, + "src": { + "jump_code": "", + "length": 16, + "start": 938 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 38, + "end_col_offset": 45, + "end_lineno": 52, + "lineno": 52, + "src": { + "jump_code": "", + "length": 7, + "start": 964 + } + } + ], + "classification": 0, + "col_offset": 30, + "end_col_offset": 45, + "end_lineno": 52, + "lineno": 52, + "src": { + "jump_code": "", + "length": 15, + "start": 956 + } + } + ], + "classification": 1, + "col_offset": 12, + "end_col_offset": 45, + "end_lineno": 52, + "lineno": 52, + "src": { + "jump_code": "", + "length": 33, + "start": 938 + } + }, + { + "ast_type": "Assign", + "children": [ + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 31, + "end_col_offset": 38, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 7, + "start": 1013 + } + } + ], + "classification": 0, + "col_offset": 31, + "end_col_offset": 38, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 7, + "start": 1013 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 22, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 3, + "start": 1001 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 29, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 10, + "start": 1001 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 29, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 10, + "start": 1001 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 4, + "start": 986 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 14, + "start": 986 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 26, + "start": 986 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 39, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 35, + "start": 986 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 42, + "end_col_offset": 48, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 6, + "start": 1024 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 48, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 44, + "start": 986 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 20, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 3, + "start": 1048 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 27, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 10, + "start": 1048 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 29, + "end_col_offset": 36, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 7, + "start": 1060 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 38, + "end_col_offset": 44, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 6, + "start": 1069 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 8, + "start": 1039 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 45, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 37, + "start": 1039 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 45, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 41, + "start": 1035 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "NameConstant", + "children": [], + "classification": 0, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 55, + "lineno": 55, + "src": { + "jump_code": "", + "length": 4, + "start": 1088 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 55, + "lineno": 55, + "src": { + "jump_code": "", + "length": 11, + "start": 1081 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 51, + "lineno": 51, + "src": { + "jump_code": "", + "length": 8, + "start": 917 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 50, + "end_col_offset": 54, + "end_lineno": 52, + "lineno": 52, + "src": { + "jump_code": "", + "length": 4, + "start": 976 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 15, + "end_lineno": 55, + "lineno": 52, + "name": "approve", + "src": { + "jump_code": "", + "length": 166, + "start": 926 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 25, + "end_col_offset": 32, + "end_lineno": 59, + "lineno": 59, + "src": { + "jump_code": "", + "length": 7, + "start": 1130 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 32, + "end_lineno": 59, + "lineno": 59, + "src": { + "jump_code": "", + "length": 15, + "start": 1122 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 51, + "end_lineno": 59, + "lineno": 59, + "src": { + "jump_code": "", + "length": 7, + "start": 1149 + } + } + ], + "classification": 0, + "col_offset": 34, + "end_col_offset": 51, + "end_lineno": 59, + "lineno": 59, + "src": { + "jump_code": "", + "length": 17, + "start": 1139 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 61, + "end_col_offset": 68, + "end_lineno": 59, + "lineno": 59, + "src": { + "jump_code": "", + "length": 7, + "start": 1166 + } + } + ], + "classification": 0, + "col_offset": 53, + "end_col_offset": 68, + "end_lineno": 59, + "lineno": 59, + "src": { + "jump_code": "", + "length": 15, + "start": 1158 + } + } + ], + "classification": 1, + "col_offset": 17, + "end_col_offset": 68, + "end_lineno": 59, + "lineno": 59, + "src": { + "jump_code": "", + "length": 51, + "start": 1122 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 48, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 44, + "start": 1188 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 27, + "end_col_offset": 30, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 3, + "start": 1211 + } + } + ], + "classification": 0, + "col_offset": 27, + "end_col_offset": 37, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 10, + "start": 1211 + } + } + ], + "classification": 0, + "col_offset": 27, + "end_col_offset": 37, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 10, + "start": 1211 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 25, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 6, + "start": 1203 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 25, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 6, + "start": 1203 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 4, + "start": 1188 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 14, + "start": 1188 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 26, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 22, + "start": 1188 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 34, + "start": 1188 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 42, + "end_col_offset": 48, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 6, + "start": 1226 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 48, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 44, + "start": 1188 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 36, + "end_lineno": 61, + "lineno": 61, + "src": { + "jump_code": "", + "length": 32, + "start": 1237 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 25, + "end_lineno": 61, + "lineno": 61, + "src": { + "jump_code": "", + "length": 6, + "start": 1252 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 25, + "end_lineno": 61, + "lineno": 61, + "src": { + "jump_code": "", + "length": 6, + "start": 1252 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 61, + "lineno": 61, + "src": { + "jump_code": "", + "length": 4, + "start": 1237 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 61, + "lineno": 61, + "src": { + "jump_code": "", + "length": 14, + "start": 1237 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 26, + "end_lineno": 61, + "lineno": 61, + "src": { + "jump_code": "", + "length": 22, + "start": 1237 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 30, + "end_col_offset": 36, + "end_lineno": 61, + "lineno": 61, + "src": { + "jump_code": "", + "length": 6, + "start": 1263 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 36, + "end_lineno": 61, + "lineno": 61, + "src": { + "jump_code": "", + "length": 32, + "start": 1237 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Add", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 62, + "lineno": 62, + "src": { + "jump_code": "", + "length": 34, + "start": 1274 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 27, + "end_lineno": 62, + "lineno": 62, + "src": { + "jump_code": "", + "length": 8, + "start": 1289 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 27, + "end_lineno": 62, + "lineno": 62, + "src": { + "jump_code": "", + "length": 8, + "start": 1289 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 62, + "lineno": 62, + "src": { + "jump_code": "", + "length": 4, + "start": 1274 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 62, + "lineno": 62, + "src": { + "jump_code": "", + "length": 14, + "start": 1274 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 62, + "lineno": 62, + "src": { + "jump_code": "", + "length": 24, + "start": 1274 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 32, + "end_col_offset": 38, + "end_lineno": 62, + "lineno": 62, + "src": { + "jump_code": "", + "length": 6, + "start": 1302 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 62, + "lineno": 62, + "src": { + "jump_code": "", + "length": 34, + "start": 1274 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 23, + "end_lineno": 63, + "lineno": 63, + "src": { + "jump_code": "", + "length": 6, + "start": 1326 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 25, + "end_col_offset": 33, + "end_lineno": 63, + "lineno": 63, + "src": { + "jump_code": "", + "length": 8, + "start": 1334 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 35, + "end_col_offset": 41, + "end_lineno": 63, + "lineno": 63, + "src": { + "jump_code": "", + "length": 6, + "start": 1344 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 63, + "lineno": 63, + "src": { + "jump_code": "", + "length": 8, + "start": 1317 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 42, + "end_lineno": 63, + "lineno": 63, + "src": { + "jump_code": "", + "length": 34, + "start": 1317 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 42, + "end_lineno": 63, + "lineno": 63, + "src": { + "jump_code": "", + "length": 38, + "start": 1313 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "NameConstant", + "children": [], + "classification": 0, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 64, + "lineno": 64, + "src": { + "jump_code": "", + "length": 4, + "start": 1363 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 64, + "lineno": 64, + "src": { + "jump_code": "", + "length": 11, + "start": 1356 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 58, + "lineno": 58, + "src": { + "jump_code": "", + "length": 8, + "start": 1096 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 73, + "end_col_offset": 77, + "end_lineno": 59, + "lineno": 59, + "src": { + "jump_code": "", + "length": 4, + "start": 1178 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 15, + "end_lineno": 64, + "lineno": 59, + "name": "transferFrom", + "src": { + "jump_code": "", + "length": 262, + "start": 1105 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 25, + "end_col_offset": 32, + "end_lineno": 68, + "lineno": 68, + "src": { + "jump_code": "", + "length": 7, + "start": 1405 + } + } + ], + "classification": 0, + "col_offset": 15, + "end_col_offset": 32, + "end_lineno": 68, + "lineno": 68, + "src": { + "jump_code": "", + "length": 17, + "start": 1395 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 42, + "end_col_offset": 49, + "end_lineno": 68, + "lineno": 68, + "src": { + "jump_code": "", + "length": 7, + "start": 1422 + } + } + ], + "classification": 0, + "col_offset": 34, + "end_col_offset": 49, + "end_lineno": 68, + "lineno": 68, + "src": { + "jump_code": "", + "length": 15, + "start": 1414 + } + } + ], + "classification": 1, + "col_offset": 15, + "end_col_offset": 49, + "end_lineno": 68, + "lineno": 68, + "src": { + "jump_code": "", + "length": 34, + "start": 1395 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Add", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 69, + "lineno": 69, + "src": { + "jump_code": "", + "length": 34, + "start": 1436 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 27, + "end_lineno": 69, + "lineno": 69, + "src": { + "jump_code": "", + "length": 8, + "start": 1451 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 27, + "end_lineno": 69, + "lineno": 69, + "src": { + "jump_code": "", + "length": 8, + "start": 1451 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 69, + "lineno": 69, + "src": { + "jump_code": "", + "length": 4, + "start": 1436 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 69, + "lineno": 69, + "src": { + "jump_code": "", + "length": 14, + "start": 1436 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 69, + "lineno": 69, + "src": { + "jump_code": "", + "length": 24, + "start": 1436 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 32, + "end_col_offset": 38, + "end_lineno": 69, + "lineno": 69, + "src": { + "jump_code": "", + "length": 6, + "start": 1464 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 69, + "lineno": 69, + "src": { + "jump_code": "", + "length": 34, + "start": 1436 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Add", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 70, + "lineno": 70, + "src": { + "jump_code": "", + "length": 26, + "start": 1475 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 70, + "lineno": 70, + "src": { + "jump_code": "", + "length": 4, + "start": 1475 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 20, + "end_lineno": 70, + "lineno": 70, + "src": { + "jump_code": "", + "length": 16, + "start": 1475 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 30, + "end_lineno": 70, + "lineno": 70, + "src": { + "jump_code": "", + "length": 6, + "start": 1495 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 70, + "lineno": 70, + "src": { + "jump_code": "", + "length": 26, + "start": 1475 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 67, + "lineno": 67, + "src": { + "jump_code": "", + "length": 8, + "start": 1371 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 30, + "end_lineno": 70, + "lineno": 68, + "name": "DEBUG_mint", + "src": { + "jump_code": "", + "length": 121, + "start": 1380 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 30, + "end_lineno": 70, + "lineno": 1, + "name": "Token.vy", + "src": { + "jump_code": "", + "length": 1501 + } + }, + "contractName": "Token", + "deploymentBytecode": { + "bytecode": "0x61047a56600436101561000d5761046b565b60046000601c3760005134610471576306fdde0381186100b657610120806020808252600a60e0527f5465737420546f6b656e000000000000000000000000000000000000000000006101005260e0818401808280516020018083828460045afa905050508051806020830101818260206001820306601f8201039050033682375050805160200160206001820306601f82010390509050905090508101905090509050610120f35b6395d89b41811861015057610120806020808252600460e0527f54455354000000000000000000000000000000000000000000000000000000006101005260e0818401808280516020018083828460045afa905050508051806020830101818260206001820306601f8201039050033682375050805160200160206001820306601f82010390509050905090508101905090509050610120f35b63313ce567811861016657601260e052602060e0f35b63a9059cbb811861020a576004358060a01c6104715760e05260013360a052608052604060802080546024358082106104715780820390509050815550600160e05160a052608052604060802080546024358181830110610471578082019050905081555060e051337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602435610100526020610100a36001610100526020610100f35b63095ea7b38118610282576004358060a01c6104715760e05260243560023360a052608052604060802060e05160a05260805260406080205560e051337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925602435610100526020610100a36001610100526020610100f35b6323b872dd811861036d576004358060a01c6104715760e0526024358060a01c6104715761010052600260e05160a05260805260406080203360a052608052604060802080546044358082106104715780820390509050815550600160e05160a05260805260406080208054604435808210610471578082039050905081555060016101005160a05260805260406080208054604435818183011061047157808201905090508155506101005160e0517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef604435610120526020610120a36001610120526020610120f35b6341a9680381186103ca576004358060a01c6104715760e052600160e05160a05260805260406080208054602435818183011061047157808201905090508155506000805460243581818301106104715780820190509050815550005b6318160ddd81186103e15760005460e052602060e0f35b6370a082318118610416576004358060a01c6104715760e052600160e05160a052608052604060802054610100526020610100f35b63dd62ed3e8118610469576004358060a01c6104715760e0526024358060a01c6104715761010052600260e05160a05260805260406080206101005160a052608052604060802054610120526020610120f35b505b60006000fd5b600080fd5b61000461047a0361000460003961000461047a036000f3" + }, + "dev_messages": {}, + "devdoc": {}, + "pcmap": { + "1000": { + "location": [ + 7, + 11, + 7, + 44 + ] + }, + "1001": { + "location": [ + 7, + 11, + 7, + 44 + ] + }, + "1004": { + "location": [ + 7, + 11, + 7, + 44 + ] + }, + "1018": { + "location": [ + 7, + 11, + 7, + 44 + ] + }, + "1036": { + "location": [ + 7, + 11, + 7, + 44 + ] + }, + "1040": { + "location": [ + 7, + 11, + 7, + 44 + ] + }, + "1042": { + "location": [ + 7, + 11, + 7, + 44 + ] + }, + "1045": { + "location": [ + 7, + 11, + 7, + 44 + ] + }, + "1046": { + "location": [ + 7, + 11, + 7, + 44 + ] + }, + "1047": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1052": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1053": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1054": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1057": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1071": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1086": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1119": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1123": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1125": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1128": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "1129": { + "location": [ + 8, + 11, + 8, + 62 + ] + }, + "113": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "114": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "144": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "145": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "146": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "147": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "149": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "164": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "165": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "166": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "167": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "168": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "169": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "170": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "171": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "172": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "173": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "174": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "175": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "176": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "177": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "178": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "181": { + "location": [ + 27, + 0, + 28, + 15 + ] + }, + "182": { + "location": [ + 27, + 0, + 28, + 15 + ] + }, + "183": { + "location": [ + 33, + 0, + 34, + 17 + ] + }, + "188": { + "location": [ + 33, + 0, + 34, + 17 + ] + }, + "189": { + "location": [ + 33, + 0, + 34, + 17 + ] + }, + "190": { + "location": [ + 33, + 0, + 34, + 17 + ] + }, + "193": { + "location": [ + 33, + 0, + 34, + 17 + ] + }, + "194": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "197": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "198": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "200": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "201": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "202": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "203": { + "location": [ + 34, + 11, + 34, + 17 + ] + }, + "205": { + "location": [ + 34, + 11, + 34, + 17 + ] + }, + "207": { + "location": [ + 34, + 11, + 34, + 17 + ] + }, + "208": { + "location": [ + 34, + 11, + 34, + 17 + ] + }, + "241": { + "location": [ + 34, + 11, + 34, + 17 + ] + }, + "244": { + "location": [ + 34, + 11, + 34, + 17 + ] + }, + "245": { + "location": [ + 34, + 11, + 34, + 17 + ] + }, + "247": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "248": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "249": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "250": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "251": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "267": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "268": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "29": { + "location": [ + 27, + 0, + 28, + 15 + ] + }, + "298": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "299": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "300": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "301": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "303": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "318": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "319": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "320": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "321": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "322": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "323": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "324": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "325": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "326": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "327": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "328": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "329": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "330": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "331": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "332": { + "location": [ + 34, + 4, + 34, + 17 + ] + }, + "335": { + "location": [ + 33, + 0, + 34, + 17 + ] + }, + "336": { + "location": [ + 33, + 0, + 34, + 17 + ] + }, + "337": { + "location": [ + 39, + 0, + 40, + 19 + ] + }, + "34": { + "location": [ + 27, + 0, + 28, + 15 + ] + }, + "342": { + "location": [ + 39, + 0, + 40, + 19 + ] + }, + "343": { + "location": [ + 39, + 0, + 40, + 19 + ] + }, + "344": { + "location": [ + 39, + 0, + 40, + 19 + ] + }, + "347": { + "location": [ + 39, + 0, + 40, + 19 + ] + }, + "348": { + "location": [ + 40, + 11, + 40, + 19 + ] + }, + "35": { + "location": [ + 27, + 0, + 28, + 15 + ] + }, + "350": { + "location": [ + 40, + 4, + 40, + 19 + ] + }, + "353": { + "location": [ + 40, + 4, + 40, + 19 + ] + }, + "355": { + "location": [ + 40, + 4, + 40, + 19 + ] + }, + "357": { + "location": [ + 39, + 0, + 40, + 19 + ] + }, + "358": { + "location": [ + 39, + 0, + 40, + 19 + ] + }, + "359": { + "location": [ + 44, + 0, + 48, + 15 + ] + }, + "36": { + "location": [ + 27, + 0, + 28, + 15 + ] + }, + "364": { + "location": [ + 44, + 0, + 48, + 15 + ] + }, + "365": { + "location": [ + 44, + 0, + 48, + 15 + ] + }, + "366": { + "location": [ + 44, + 0, + 48, + 15 + ] + }, + "369": { + "location": [ + 44, + 0, + 48, + 15 + ] + }, + "383": { + "location": [ + 44, + 0, + 48, + 15 + ] + }, + "384": { + "location": [ + 45, + 4, + 45, + 18 + ] + }, + "386": { + "location": [ + 45, + 19, + 45, + 29 + ] + }, + "387": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "389": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "39": { + "location": [ + 27, + 0, + 28, + 15 + ] + }, + "390": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "392": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "393": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "395": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "397": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "399": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "40": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "400": { + "location": [ + 45, + 34, + 45, + 40 + ] + }, + "402": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "413": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "414": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "415": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "416": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "417": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "418": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "419": { + "location": [ + 45, + 4, + 45, + 40 + ] + }, + "420": { + "location": [ + 46, + 4, + 46, + 18 + ] + }, + "422": { + "location": [ + 46, + 19, + 46, + 27 + ] + }, + "425": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "427": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "428": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "43": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "430": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "431": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "433": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "435": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "437": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "438": { + "location": [ + 45, + 34, + 45, + 40 + ] + }, + "44": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "440": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "453": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "454": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "455": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "456": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "457": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "458": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "459": { + "location": [ + 46, + 4, + 46, + 38 + ] + }, + "46": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "460": { + "location": [ + 47, + 29, + 47, + 37 + ] + }, + "462": { + "location": [ + 47, + 4, + 47, + 46 + ] + }, + "463": { + "location": [ + 47, + 17, + 47, + 27 + ] + }, + "464": { + "location": [ + 47, + 4, + 47, + 46 + ] + }, + "47": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "48": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "49": { + "location": [ + 28, + 11, + 28, + 15 + ] + }, + "497": { + "location": [ + 45, + 34, + 45, + 40 + ] + }, + "500": { + "location": [ + 47, + 4, + 47, + 46 + ] + }, + "504": { + "location": [ + 47, + 4, + 47, + 46 + ] + }, + "506": { + "location": [ + 47, + 4, + 47, + 46 + ] + }, + "509": { + "location": [ + 47, + 4, + 47, + 46 + ] + }, + "51": { + "location": [ + 28, + 11, + 28, + 15 + ] + }, + "510": { + "location": [ + 48, + 11, + 48, + 15 + ] + }, + "512": { + "location": [ + 48, + 4, + 48, + 15 + ] + }, + "516": { + "location": [ + 48, + 4, + 48, + 15 + ] + }, + "518": { + "location": [ + 48, + 4, + 48, + 15 + ] + }, + "521": { + "location": [ + 44, + 0, + 48, + 15 + ] + }, + "522": { + "location": [ + 44, + 0, + 48, + 15 + ] + }, + "523": { + "location": [ + 52, + 0, + 55, + 15 + ] + }, + "528": { + "location": [ + 52, + 0, + 55, + 15 + ] + }, + "529": { + "location": [ + 52, + 0, + 55, + 15 + ] + }, + "53": { + "location": [ + 28, + 11, + 28, + 15 + ] + }, + "530": { + "location": [ + 52, + 0, + 55, + 15 + ] + }, + "533": { + "location": [ + 52, + 0, + 55, + 15 + ] + }, + "54": { + "location": [ + 28, + 11, + 28, + 15 + ] + }, + "547": { + "location": [ + 52, + 0, + 55, + 15 + ] + }, + "548": { + "location": [ + 53, + 42, + 53, + 48 + ] + }, + "551": { + "location": [ + 53, + 4, + 53, + 18 + ] + }, + "553": { + "location": [ + 53, + 19, + 53, + 29 + ] + }, + "565": { + "location": [ + 53, + 31, + 53, + 38 + ] + }, + "579": { + "location": [ + 53, + 4, + 53, + 48 + ] + }, + "580": { + "location": [ + 54, + 29, + 54, + 36 + ] + }, + "582": { + "location": [ + 54, + 4, + 54, + 45 + ] + }, + "583": { + "location": [ + 54, + 17, + 54, + 27 + ] + }, + "584": { + "location": [ + 54, + 4, + 54, + 45 + ] + }, + "617": { + "location": [ + 53, + 42, + 53, + 48 + ] + }, + "620": { + "location": [ + 54, + 4, + 54, + 45 + ] + }, + "624": { + "location": [ + 54, + 4, + 54, + 45 + ] + }, + "626": { + "location": [ + 54, + 4, + 54, + 45 + ] + }, + "629": { + "location": [ + 54, + 4, + 54, + 45 + ] + }, + "630": { + "location": [ + 55, + 11, + 55, + 15 + ] + }, + "632": { + "location": [ + 55, + 4, + 55, + 15 + ] + }, + "636": { + "location": [ + 55, + 4, + 55, + 15 + ] + }, + "638": { + "location": [ + 55, + 4, + 55, + 15 + ] + }, + "641": { + "location": [ + 52, + 0, + 55, + 15 + ] + }, + "642": { + "location": [ + 52, + 0, + 55, + 15 + ] + }, + "643": { + "location": [ + 59, + 0, + 64, + 15 + ] + }, + "648": { + "location": [ + 59, + 0, + 64, + 15 + ] + }, + "649": { + "location": [ + 59, + 0, + 64, + 15 + ] + }, + "650": { + "location": [ + 59, + 0, + 64, + 15 + ] + }, + "653": { + "location": [ + 59, + 0, + 64, + 15 + ] + }, + "667": { + "location": [ + 59, + 0, + 64, + 15 + ] + }, + "682": { + "location": [ + 59, + 0, + 64, + 15 + ] + }, + "683": { + "location": [ + 60, + 4, + 60, + 18 + ] + }, + "685": { + "location": [ + 60, + 19, + 60, + 25 + ] + }, + "699": { + "location": [ + 60, + 27, + 60, + 37 + ] + }, + "700": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "702": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "703": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "705": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "706": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "708": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "710": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "712": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "713": { + "location": [ + 60, + 42, + 60, + 48 + ] + }, + "715": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "726": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "727": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "728": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "729": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "730": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "731": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "732": { + "location": [ + 60, + 4, + 60, + 48 + ] + }, + "733": { + "location": [ + 61, + 4, + 61, + 18 + ] + }, + "735": { + "location": [ + 61, + 19, + 61, + 25 + ] + }, + "738": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "740": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "741": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "743": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "744": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "746": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "748": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "750": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "751": { + "location": [ + 60, + 42, + 60, + 48 + ] + }, + "753": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "764": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "765": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "766": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "767": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "768": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "769": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "770": { + "location": [ + 61, + 4, + 61, + 36 + ] + }, + "771": { + "location": [ + 62, + 4, + 62, + 18 + ] + }, + "773": { + "location": [ + 62, + 19, + 62, + 27 + ] + }, + "777": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "779": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "780": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "782": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "783": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "785": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "787": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "789": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "790": { + "location": [ + 60, + 42, + 60, + 48 + ] + }, + "792": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "805": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "806": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "807": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "808": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "809": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "810": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "811": { + "location": [ + 62, + 4, + 62, + 38 + ] + }, + "812": { + "location": [ + 63, + 25, + 63, + 33 + ] + }, + "815": { + "location": [ + 63, + 4, + 63, + 42 + ] + }, + "816": { + "location": [ + 63, + 17, + 63, + 23 + ] + }, + "818": { + "location": [ + 63, + 4, + 63, + 42 + ] + }, + "819": { + "location": [ + 63, + 4, + 63, + 42 + ] + }, + "852": { + "location": [ + 60, + 42, + 60, + 48 + ] + }, + "855": { + "location": [ + 63, + 4, + 63, + 42 + ] + }, + "859": { + "location": [ + 63, + 4, + 63, + 42 + ] + }, + "861": { + "location": [ + 63, + 4, + 63, + 42 + ] + }, + "864": { + "location": [ + 63, + 4, + 63, + 42 + ] + }, + "865": { + "location": [ + 64, + 11, + 64, + 15 + ] + }, + "867": { + "location": [ + 64, + 4, + 64, + 15 + ] + }, + "87": { + "location": [ + 28, + 11, + 28, + 15 + ] + }, + "871": { + "location": [ + 64, + 4, + 64, + 15 + ] + }, + "873": { + "location": [ + 64, + 4, + 64, + 15 + ] + }, + "876": { + "location": [ + 59, + 0, + 64, + 15 + ] + }, + "877": { + "location": [ + 59, + 0, + 64, + 15 + ] + }, + "878": { + "location": [ + 68, + 0, + 70, + 30 + ] + }, + "883": { + "location": [ + 68, + 0, + 70, + 30 + ] + }, + "884": { + "location": [ + 68, + 0, + 70, + 30 + ] + }, + "885": { + "location": [ + 68, + 0, + 70, + 30 + ] + }, + "888": { + "location": [ + 68, + 0, + 70, + 30 + ] + }, + "90": { + "location": [ + 28, + 11, + 28, + 15 + ] + }, + "902": { + "location": [ + 68, + 0, + 70, + 30 + ] + }, + "903": { + "location": [ + 69, + 4, + 69, + 18 + ] + }, + "905": { + "location": [ + 69, + 19, + 69, + 27 + ] + }, + "908": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "91": { + "location": [ + 28, + 11, + 28, + 15 + ] + }, + "910": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "911": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "913": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "914": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "916": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "918": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "920": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "921": { + "location": [ + 69, + 32, + 69, + 38 + ] + }, + "923": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "93": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "936": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "937": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "938": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "939": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "94": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "940": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "941": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "942": { + "location": [ + 69, + 4, + 69, + 38 + ] + }, + "943": { + "location": [ + 70, + 4, + 70, + 20 + ] + }, + "945": { + "location": [ + 70, + 4, + 70, + 20 + ] + }, + "946": { + "location": [ + 70, + 4, + 70, + 20 + ] + }, + "947": { + "location": [ + 69, + 32, + 69, + 38 + ] + }, + "949": { + "location": [ + 70, + 4, + 70, + 30 + ] + }, + "95": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "96": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "962": { + "location": [ + 70, + 4, + 70, + 30 + ] + }, + "963": { + "location": [ + 70, + 4, + 70, + 30 + ] + }, + "964": { + "location": [ + 70, + 4, + 70, + 30 + ] + }, + "965": { + "location": [ + 70, + 4, + 70, + 30 + ] + }, + "966": { + "location": [ + 70, + 4, + 70, + 30 + ] + }, + "967": { + "location": [ + 70, + 4, + 70, + 30 + ] + }, + "968": { + "location": [ + 70, + 4, + 70, + 30 + ] + }, + "969": { + "location": [ + 68, + 0, + 70, + 30 + ] + }, + "97": { + "location": [ + 28, + 4, + 28, + 15 + ] + }, + "970": { + "location": [ + 68, + 0, + 70, + 30 + ] + }, + "971": { + "location": [ + 6, + 13, + 6, + 28 + ] + }, + "976": { + "location": [ + 6, + 13, + 6, + 28 + ] + }, + "977": { + "location": [ + 6, + 13, + 6, + 28 + ] + }, + "978": { + "location": [ + 6, + 13, + 6, + 28 + ] + }, + "981": { + "location": [ + 6, + 13, + 6, + 28 + ] + }, + "985": { + "location": [ + 6, + 13, + 6, + 28 + ] + }, + "988": { + "location": [ + 6, + 13, + 6, + 28 + ] + }, + "990": { + "location": [ + 6, + 13, + 6, + 28 + ] + }, + "992": { + "location": [ + 6, + 13, + 6, + 28 + ] + }, + "993": { + "location": [ + 6, + 13, + 6, + 28 + ] + }, + "994": { + "location": [ + 7, + 11, + 7, + 44 + ] + }, + "999": { + "location": [ + 7, + 11, + 7, + 44 + ] + } + }, + "runtimeBytecode": { + "bytecode": "0x600436101561000d5761046b565b60046000601c3760005134610471576306fdde0381186100b657610120806020808252600a60e0527f5465737420546f6b656e000000000000000000000000000000000000000000006101005260e0818401808280516020018083828460045afa905050508051806020830101818260206001820306601f8201039050033682375050805160200160206001820306601f82010390509050905090508101905090509050610120f35b6395d89b41811861015057610120806020808252600460e0527f54455354000000000000000000000000000000000000000000000000000000006101005260e0818401808280516020018083828460045afa905050508051806020830101818260206001820306601f8201039050033682375050805160200160206001820306601f82010390509050905090508101905090509050610120f35b63313ce567811861016657601260e052602060e0f35b63a9059cbb811861020a576004358060a01c6104715760e05260013360a052608052604060802080546024358082106104715780820390509050815550600160e05160a052608052604060802080546024358181830110610471578082019050905081555060e051337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602435610100526020610100a36001610100526020610100f35b63095ea7b38118610282576004358060a01c6104715760e05260243560023360a052608052604060802060e05160a05260805260406080205560e051337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925602435610100526020610100a36001610100526020610100f35b6323b872dd811861036d576004358060a01c6104715760e0526024358060a01c6104715761010052600260e05160a05260805260406080203360a052608052604060802080546044358082106104715780820390509050815550600160e05160a05260805260406080208054604435808210610471578082039050905081555060016101005160a05260805260406080208054604435818183011061047157808201905090508155506101005160e0517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef604435610120526020610120a36001610120526020610120f35b6341a9680381186103ca576004358060a01c6104715760e052600160e05160a05260805260406080208054602435818183011061047157808201905090508155506000805460243581818301106104715780820190509050815550005b6318160ddd81186103e15760005460e052602060e0f35b6370a082318118610416576004358060a01c6104715760e052600160e05160a052608052604060802054610100526020610100f35b63dd62ed3e8118610469576004358060a01c6104715760e0526024358060a01c6104715761010052600260e05160a05260805260406080206101005160a052608052604060802054610120526020610120f35b505b60006000fd5b600080fd" + }, + "sourceId": "Token.vy", + "sourcemap": "-1:-1:0:-;;;;;:::-;;:::-;:::-;;;;;;;;;:::-;533:41;;;;:::-;563:11;;;;;;570:4;;;;;;;563:11;;;;;-1:-1;;;;;;;;;;;;;563:11;;-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;563:11;;;;;-1:-1;;;;;;;;;;;563:11;;;;;;;;;;;;;;;533:41;:::-;593:44;;;;:::-;624:13;;;;;;631:6;;;;;;;624:13;;;;;-1:-1;;;;;;;;;;;;;624:13;;-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;624:13;;;;;-1:-1;;;;;;;;;;;624:13;;;;;;;;;;;;;;;593:44;:::-;656;;;;:::-;692:8;685:15;-1:-1;685:15;;656:44;:::-;713:200;;;;:::-;-1:-1;;;;;;:::-;;713:200;775:14;790:10;775:36;;;;;;;-1:-1;775:36;805:6;775:36;-1:-1;;;;:::-;;;;775:36;;;;;;;816:14;831:8;-1:-1;816:34;;;;;;;-1:-1;816:34;805:6;816:34;-1:-1;;;;;;:::-;;;;816:34;;;;;;;880:8;855:42;868:10;855:42;805:6;-1:-1;855:42;-1:-1;855:42;;;909:4;902:11;-1:-1;902:11;;713:200;:::-;926:166;;;;:::-;-1:-1;;;;;;:::-;;926:166;1024:6;-1:-1;986:14;1001:10;-1:-1;;;;;;;1013:7;-1:-1;;;;;;;;986:44;1060:7;1035:41;1048:10;1035:41;1024:6;-1:-1;1035:41;-1:-1;1035:41;;;1088:4;1081:11;-1:-1;1081:11;;926:166;:::-;1105:262;;;;:::-;-1:-1;;;;;;:::-;;1105:262;-1:-1;;;;;;:::-;;1105:262;1188:14;1203:6;-1:-1;;;;;;;;1211:10;1188:44;;;;;;;-1:-1;1188:44;1226:6;1188:44;-1:-1;;;;:::-;;;;1188:44;;;;;;;1237:14;1252:6;-1:-1;1237:32;;;;;;;-1:-1;1237:32;1226:6;1237:32;-1:-1;;;;:::-;;;;1237:32;;;;;;;1274:14;1289:8;-1:-1;1274:34;;;;;;;-1:-1;1274:34;1226:6;1274:34;-1:-1;;;;;;:::-;;;;1274:34;;;;;;;1334:8;1313:38;1326:6;1313:38;;1226:6;-1:-1;1313:38;-1:-1;1313:38;;;1363:4;1356:11;-1:-1;1356:11;;1105:262;:::-;1380:121;;;;:::-;-1:-1;;;;;;:::-;;1380:121;1436:14;1451:8;-1:-1;1436:34;;;;;;;-1:-1;1436:34;1464:6;1436:34;-1:-1;;;;;;:::-;;;;1436:34;;;;;;;1475:16;;;1464:6;1475:26;-1:-1;;;;;;:::-;;;;1475:26;;;;;;;1380:121;:::-;85:15;;;;:::-;-1:-1;;85:15;-1:-1;85:15;;;:::-;112:33;;;;:::-;-1:-1;;;;;;:::-;;112:33;-1:-1;;;;;;;;;;;112:33;-1:-1;112:33;;;:::-;157:51;;;;:::-;-1:-1;;;;;;:::-;;157:51;-1:-1;;;;;;:::-;;157:51;-1:-1;;;;;;;;;;;;;;;;;;;;157:51;-1:-1;157:51;;;:::-;-1:-1;:::-;;;;:::-;;;;", + "userdoc": {} +} \ No newline at end of file diff --git a/tests/contracts/VyperVault.json b/tests/contracts/VyperVault.json new file mode 100644 index 0000000..d75e752 --- /dev/null +++ b/tests/contracts/VyperVault.json @@ -0,0 +1,21618 @@ +{ + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "allowance", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "depositor", + "type": "address" + }, + { + "indexed": true, + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "name": "shares", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "withdrawer", + "type": "address" + }, + { + "indexed": true, + "name": "receiver", + "type": "address" + }, + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "name": "shares", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [ + { + "name": "asset", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "spender", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalAssets", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "shareAmount", + "type": "uint256" + } + ], + "name": "convertToAssets", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "assetAmount", + "type": "uint256" + } + ], + "name": "convertToShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "maxDeposit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "assets", + "type": "uint256" + } + ], + "name": "previewDeposit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "assets", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "assets", + "type": "uint256" + }, + { + "name": "receiver", + "type": "address" + } + ], + "name": "deposit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "maxMint", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "shares", + "type": "uint256" + } + ], + "name": "previewMint", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "shares", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "shares", + "type": "uint256" + }, + { + "name": "receiver", + "type": "address" + } + ], + "name": "mint", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "maxWithdraw", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "assets", + "type": "uint256" + } + ], + "name": "previewWithdraw", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "assets", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "assets", + "type": "uint256" + }, + { + "name": "receiver", + "type": "address" + } + ], + "name": "withdraw", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "assets", + "type": "uint256" + }, + { + "name": "receiver", + "type": "address" + }, + { + "name": "owner", + "type": "address" + } + ], + "name": "withdraw", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "maxRedeem", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "shares", + "type": "uint256" + } + ], + "name": "previewRedeem", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "shares", + "type": "uint256" + } + ], + "name": "redeem", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "shares", + "type": "uint256" + }, + { + "name": "receiver", + "type": "address" + } + ], + "name": "redeem", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "shares", + "type": "uint256" + }, + { + "name": "receiver", + "type": "address" + }, + { + "name": "owner", + "type": "address" + } + ], + "name": "redeem", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "amount", + "type": "uint256" + } + ], + "name": "DEBUG_steal_tokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "arg0", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "arg0", + "type": "address" + }, + { + "name": "arg1", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "asset", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "ast": { + "ast_type": "Module", + "children": [ + { + "ast_type": "ImportFrom", + "children": [], + "classification": 0, + "col_offset": 0, + "end_col_offset": 34, + "end_lineno": 2, + "lineno": 2, + "name": "ERC20", + "src": { + "jump_code": "", + "length": 34, + "start": 17 + } + }, + { + "ast_type": "Import", + "children": [], + "classification": 0, + "col_offset": 0, + "end_col_offset": 25, + "end_lineno": 4, + "lineno": 4, + "name": "ERC4626", + "src": { + "jump_code": "", + "length": 25, + "start": 53 + } + }, + { + "ast_type": "ImplementsDecl", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 17, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 5, + "start": 92 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 10, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 10, + "start": 80 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 17, + "start": 80 + } + }, + { + "ast_type": "ImplementsDecl", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 7, + "start": 110 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 10, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 10, + "start": 98 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 19, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 19, + "start": 98 + } + }, + { + "ast_type": "VariableDecl", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 20, + "end_col_offset": 27, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 7, + "start": 158 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 11, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 11, + "start": 138 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 28, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 28, + "start": 138 + } + }, + { + "ast_type": "VariableDecl", + "children": [ + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Tuple", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 26, + "end_col_offset": 33, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 7, + "start": 193 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 35, + "end_col_offset": 42, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 7, + "start": 202 + } + } + ], + "classification": 0, + "col_offset": 26, + "end_col_offset": 42, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 16, + "start": 193 + } + } + ], + "classification": 0, + "col_offset": 18, + "end_col_offset": 43, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 25, + "start": 185 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 18, + "end_col_offset": 25, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 7, + "start": 185 + } + } + ], + "classification": 0, + "col_offset": 18, + "end_col_offset": 43, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 25, + "start": 185 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 9, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 9, + "start": 167 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 44, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 44, + "start": 167 + } + }, + { + "ast_type": "VariableDecl", + "children": [ + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Tuple", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 26, + "end_col_offset": 33, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 7, + "start": 238 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Tuple", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 43, + "end_col_offset": 50, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 7, + "start": 255 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 52, + "end_col_offset": 59, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 7, + "start": 264 + } + } + ], + "classification": 0, + "col_offset": 43, + "end_col_offset": 59, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 16, + "start": 255 + } + } + ], + "classification": 0, + "col_offset": 35, + "end_col_offset": 60, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 25, + "start": 247 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 35, + "end_col_offset": 42, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 7, + "start": 247 + } + } + ], + "classification": 0, + "col_offset": 35, + "end_col_offset": 60, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 25, + "start": 247 + } + } + ], + "classification": 0, + "col_offset": 26, + "end_col_offset": 60, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 34, + "start": 238 + } + } + ], + "classification": 0, + "col_offset": 18, + "end_col_offset": 61, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 43, + "start": 230 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 18, + "end_col_offset": 25, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 7, + "start": 230 + } + } + ], + "classification": 0, + "col_offset": 18, + "end_col_offset": 61, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 43, + "start": 230 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 9, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 9, + "start": 212 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 62, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 62, + "start": 212 + } + }, + { + "ast_type": "VariableDecl", + "children": [ + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 22, + "end_col_offset": 24, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 2, + "start": 298 + } + } + ], + "classification": 0, + "col_offset": 15, + "end_col_offset": 25, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 10, + "start": 291 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 15, + "end_col_offset": 21, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 6, + "start": 291 + } + } + ], + "classification": 0, + "col_offset": 15, + "end_col_offset": 25, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 10, + "start": 291 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 4, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 4, + "start": 276 + } + }, + { + "ast_type": "Str", + "children": [], + "classification": 0, + "col_offset": 29, + "end_col_offset": 41, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 12, + "start": 305 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 41, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 41, + "start": 276 + } + }, + { + "ast_type": "VariableDecl", + "children": [ + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 24, + "end_col_offset": 25, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 1, + "start": 342 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 26, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 9, + "start": 335 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 23, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 6, + "start": 335 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 26, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 9, + "start": 335 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 6, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 6, + "start": 318 + } + }, + { + "ast_type": "Str", + "children": [], + "classification": 0, + "col_offset": 30, + "end_col_offset": 37, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 7, + "start": 348 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 37, + "end_lineno": 16, + "lineno": 16, + "src": { + "jump_code": "", + "length": 37, + "start": 318 + } + }, + { + "ast_type": "VariableDecl", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 24, + "end_lineno": 17, + "lineno": 17, + "src": { + "jump_code": "", + "length": 5, + "start": 375 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 8, + "end_lineno": 17, + "lineno": 17, + "src": { + "jump_code": "", + "length": 8, + "start": 356 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 28, + "end_col_offset": 30, + "end_lineno": 17, + "lineno": 17, + "src": { + "jump_code": "", + "length": 2, + "start": 384 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 30, + "end_lineno": 17, + "lineno": 17, + "src": { + "jump_code": "", + "length": 30, + "start": 356 + } + }, + { + "ast_type": "EventDef", + "children": [ + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 20, + "end_col_offset": 27, + "end_lineno": 20, + "lineno": 20, + "src": { + "jump_code": "", + "length": 7, + "start": 424 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 20, + "lineno": 20, + "src": { + "jump_code": "", + "length": 7, + "start": 416 + } + } + ], + "classification": 0, + "col_offset": 12, + "end_col_offset": 28, + "end_lineno": 20, + "lineno": 20, + "src": { + "jump_code": "", + "length": 16, + "start": 416 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 20, + "lineno": 20, + "src": { + "jump_code": "", + "length": 6, + "start": 408 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 20, + "lineno": 20, + "src": { + "jump_code": "", + "length": 24, + "start": 408 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 29, + "end_lineno": 21, + "lineno": 21, + "src": { + "jump_code": "", + "length": 7, + "start": 455 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 14, + "end_col_offset": 21, + "end_lineno": 21, + "lineno": 21, + "src": { + "jump_code": "", + "length": 7, + "start": 447 + } + } + ], + "classification": 0, + "col_offset": 14, + "end_col_offset": 30, + "end_lineno": 21, + "lineno": 21, + "src": { + "jump_code": "", + "length": 16, + "start": 447 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 12, + "end_lineno": 21, + "lineno": 21, + "src": { + "jump_code": "", + "length": 8, + "start": 437 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 21, + "lineno": 21, + "src": { + "jump_code": "", + "length": 26, + "start": 437 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 22, + "lineno": 22, + "src": { + "jump_code": "", + "length": 7, + "start": 476 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 22, + "lineno": 22, + "src": { + "jump_code": "", + "length": 6, + "start": 468 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 19, + "end_lineno": 22, + "lineno": 22, + "src": { + "jump_code": "", + "length": 15, + "start": 468 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 19, + "end_lineno": 22, + "lineno": 19, + "name": "Transfer", + "src": { + "jump_code": "", + "length": 95, + "start": 388 + } + }, + { + "ast_type": "EventDef", + "children": [ + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 26, + "end_lineno": 25, + "lineno": 25, + "src": { + "jump_code": "", + "length": 7, + "start": 520 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 18, + "end_lineno": 25, + "lineno": 25, + "src": { + "jump_code": "", + "length": 7, + "start": 512 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 27, + "end_lineno": 25, + "lineno": 25, + "src": { + "jump_code": "", + "length": 16, + "start": 512 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 9, + "end_lineno": 25, + "lineno": 25, + "src": { + "jump_code": "", + "length": 5, + "start": 505 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 27, + "end_lineno": 25, + "lineno": 25, + "src": { + "jump_code": "", + "length": 23, + "start": 505 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 21, + "end_col_offset": 28, + "end_lineno": 26, + "lineno": 26, + "src": { + "jump_code": "", + "length": 7, + "start": 550 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 13, + "end_col_offset": 20, + "end_lineno": 26, + "lineno": 26, + "src": { + "jump_code": "", + "length": 7, + "start": 542 + } + } + ], + "classification": 0, + "col_offset": 13, + "end_col_offset": 29, + "end_lineno": 26, + "lineno": 26, + "src": { + "jump_code": "", + "length": 16, + "start": 542 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 11, + "end_lineno": 26, + "lineno": 26, + "src": { + "jump_code": "", + "length": 7, + "start": 533 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 29, + "end_lineno": 26, + "lineno": 26, + "src": { + "jump_code": "", + "length": 25, + "start": 533 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 15, + "end_col_offset": 22, + "end_lineno": 27, + "lineno": 27, + "src": { + "jump_code": "", + "length": 7, + "start": 574 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 13, + "end_lineno": 27, + "lineno": 27, + "src": { + "jump_code": "", + "length": 9, + "start": 563 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 22, + "end_lineno": 27, + "lineno": 27, + "src": { + "jump_code": "", + "length": 18, + "start": 563 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 22, + "end_lineno": 27, + "lineno": 24, + "name": "Approval", + "src": { + "jump_code": "", + "length": 96, + "start": 485 + } + }, + { + "ast_type": "VariableDecl", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 14, + "end_col_offset": 19, + "end_lineno": 31, + "lineno": 31, + "src": { + "jump_code": "", + "length": 5, + "start": 618 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 5, + "end_lineno": 31, + "lineno": 31, + "src": { + "jump_code": "", + "length": 5, + "start": 604 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 20, + "end_lineno": 31, + "lineno": 31, + "src": { + "jump_code": "", + "length": 20, + "start": 604 + } + }, + { + "ast_type": "EventDef", + "children": [ + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 23, + "end_col_offset": 30, + "end_lineno": 34, + "lineno": 34, + "src": { + "jump_code": "", + "length": 7, + "start": 664 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 15, + "end_col_offset": 22, + "end_lineno": 34, + "lineno": 34, + "src": { + "jump_code": "", + "length": 7, + "start": 656 + } + } + ], + "classification": 0, + "col_offset": 15, + "end_col_offset": 31, + "end_lineno": 34, + "lineno": 34, + "src": { + "jump_code": "", + "length": 16, + "start": 656 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 13, + "end_lineno": 34, + "lineno": 34, + "src": { + "jump_code": "", + "length": 9, + "start": 645 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 31, + "end_lineno": 34, + "lineno": 34, + "src": { + "jump_code": "", + "length": 27, + "start": 645 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 29, + "end_lineno": 35, + "lineno": 35, + "src": { + "jump_code": "", + "length": 7, + "start": 695 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 14, + "end_col_offset": 21, + "end_lineno": 35, + "lineno": 35, + "src": { + "jump_code": "", + "length": 7, + "start": 687 + } + } + ], + "classification": 0, + "col_offset": 14, + "end_col_offset": 30, + "end_lineno": 35, + "lineno": 35, + "src": { + "jump_code": "", + "length": 16, + "start": 687 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 12, + "end_lineno": 35, + "lineno": 35, + "src": { + "jump_code": "", + "length": 8, + "start": 677 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 35, + "lineno": 35, + "src": { + "jump_code": "", + "length": 26, + "start": 677 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 36, + "lineno": 36, + "src": { + "jump_code": "", + "length": 7, + "start": 716 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 36, + "lineno": 36, + "src": { + "jump_code": "", + "length": 6, + "start": 708 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 19, + "end_lineno": 36, + "lineno": 36, + "src": { + "jump_code": "", + "length": 15, + "start": 708 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 37, + "lineno": 37, + "src": { + "jump_code": "", + "length": 7, + "start": 736 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 37, + "lineno": 37, + "src": { + "jump_code": "", + "length": 6, + "start": 728 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 19, + "end_lineno": 37, + "lineno": 37, + "src": { + "jump_code": "", + "length": 15, + "start": 728 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 19, + "end_lineno": 37, + "lineno": 33, + "name": "Deposit", + "src": { + "jump_code": "", + "length": 117, + "start": 626 + } + }, + { + "ast_type": "EventDef", + "children": [ + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 31, + "end_lineno": 40, + "lineno": 40, + "src": { + "jump_code": "", + "length": 7, + "start": 785 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 16, + "end_col_offset": 23, + "end_lineno": 40, + "lineno": 40, + "src": { + "jump_code": "", + "length": 7, + "start": 777 + } + } + ], + "classification": 0, + "col_offset": 16, + "end_col_offset": 32, + "end_lineno": 40, + "lineno": 40, + "src": { + "jump_code": "", + "length": 16, + "start": 777 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 14, + "end_lineno": 40, + "lineno": 40, + "src": { + "jump_code": "", + "length": 10, + "start": 765 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 32, + "end_lineno": 40, + "lineno": 40, + "src": { + "jump_code": "", + "length": 28, + "start": 765 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 29, + "end_lineno": 41, + "lineno": 41, + "src": { + "jump_code": "", + "length": 7, + "start": 816 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 14, + "end_col_offset": 21, + "end_lineno": 41, + "lineno": 41, + "src": { + "jump_code": "", + "length": 7, + "start": 808 + } + } + ], + "classification": 0, + "col_offset": 14, + "end_col_offset": 30, + "end_lineno": 41, + "lineno": 41, + "src": { + "jump_code": "", + "length": 16, + "start": 808 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 12, + "end_lineno": 41, + "lineno": 41, + "src": { + "jump_code": "", + "length": 8, + "start": 798 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 41, + "lineno": 41, + "src": { + "jump_code": "", + "length": 26, + "start": 798 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 26, + "end_lineno": 42, + "lineno": 42, + "src": { + "jump_code": "", + "length": 7, + "start": 844 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 18, + "end_lineno": 42, + "lineno": 42, + "src": { + "jump_code": "", + "length": 7, + "start": 836 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 27, + "end_lineno": 42, + "lineno": 42, + "src": { + "jump_code": "", + "length": 16, + "start": 836 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 9, + "end_lineno": 42, + "lineno": 42, + "src": { + "jump_code": "", + "length": 5, + "start": 829 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 27, + "end_lineno": 42, + "lineno": 42, + "src": { + "jump_code": "", + "length": 23, + "start": 829 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 43, + "lineno": 43, + "src": { + "jump_code": "", + "length": 7, + "start": 865 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 43, + "lineno": 43, + "src": { + "jump_code": "", + "length": 6, + "start": 857 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 19, + "end_lineno": 43, + "lineno": 43, + "src": { + "jump_code": "", + "length": 15, + "start": 857 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 44, + "lineno": 44, + "src": { + "jump_code": "", + "length": 7, + "start": 885 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 44, + "lineno": 44, + "src": { + "jump_code": "", + "length": 6, + "start": 877 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 19, + "end_lineno": 44, + "lineno": 44, + "src": { + "jump_code": "", + "length": 15, + "start": 877 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 19, + "end_lineno": 44, + "lineno": 39, + "name": "Withdraw", + "src": { + "jump_code": "", + "length": 147, + "start": 745 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 20, + "end_col_offset": 25, + "end_lineno": 48, + "lineno": 48, + "src": { + "jump_code": "", + "length": 5, + "start": 925 + } + } + ], + "classification": 0, + "col_offset": 13, + "end_col_offset": 25, + "end_lineno": 48, + "lineno": 48, + "src": { + "jump_code": "", + "length": 12, + "start": 918 + } + } + ], + "classification": 1, + "col_offset": 13, + "end_col_offset": 25, + "end_lineno": 48, + "lineno": 48, + "src": { + "jump_code": "", + "length": 12, + "start": 918 + } + }, + { + "ast_type": "Assign", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 49, + "lineno": 49, + "src": { + "jump_code": "", + "length": 4, + "start": 937 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 14, + "end_lineno": 49, + "lineno": 49, + "src": { + "jump_code": "", + "length": 10, + "start": 937 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 22, + "end_lineno": 49, + "lineno": 49, + "src": { + "jump_code": "", + "length": 5, + "start": 950 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 22, + "end_lineno": 49, + "lineno": 49, + "src": { + "jump_code": "", + "length": 18, + "start": 937 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 47, + "lineno": 47, + "src": { + "jump_code": "", + "length": 8, + "start": 896 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 22, + "end_lineno": 49, + "lineno": 48, + "name": "__init__", + "src": { + "jump_code": "", + "length": 50, + "start": 905 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 3, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 3, + "start": 974 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 55, + "lineno": 55, + "src": { + "jump_code": "", + "length": 4, + "start": 1011 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 55, + "lineno": 55, + "src": { + "jump_code": "", + "length": 11, + "start": 1004 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 52, + "lineno": 52, + "src": { + "jump_code": "", + "length": 4, + "start": 959 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 53, + "lineno": 53, + "src": { + "jump_code": "", + "length": 8, + "start": 965 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 21, + "end_col_offset": 23, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 2, + "start": 995 + } + } + ], + "classification": 0, + "col_offset": 14, + "end_col_offset": 24, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 10, + "start": 988 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 14, + "end_col_offset": 20, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 6, + "start": 988 + } + } + ], + "classification": 0, + "col_offset": 14, + "end_col_offset": 24, + "end_lineno": 54, + "lineno": 54, + "src": { + "jump_code": "", + "length": 10, + "start": 988 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 15, + "end_lineno": 55, + "lineno": 54, + "name": "name", + "src": { + "jump_code": "", + "length": 41, + "start": 974 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 3, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 3, + "start": 1034 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 61, + "lineno": 61, + "src": { + "jump_code": "", + "length": 6, + "start": 1072 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 17, + "end_lineno": 61, + "lineno": 61, + "src": { + "jump_code": "", + "length": 13, + "start": 1065 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 58, + "lineno": 58, + "src": { + "jump_code": "", + "length": 4, + "start": 1019 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 59, + "lineno": 59, + "src": { + "jump_code": "", + "length": 8, + "start": 1025 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 23, + "end_col_offset": 24, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 1, + "start": 1057 + } + } + ], + "classification": 0, + "col_offset": 16, + "end_col_offset": 25, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 9, + "start": 1050 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 16, + "end_col_offset": 22, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 6, + "start": 1050 + } + } + ], + "classification": 0, + "col_offset": 16, + "end_col_offset": 25, + "end_lineno": 60, + "lineno": 60, + "src": { + "jump_code": "", + "length": 9, + "start": 1050 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 61, + "lineno": 60, + "name": "symbol", + "src": { + "jump_code": "", + "length": 44, + "start": 1034 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 3, + "end_lineno": 66, + "lineno": 66, + "src": { + "jump_code": "", + "length": 3, + "start": 1097 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 19, + "end_lineno": 67, + "lineno": 67, + "src": { + "jump_code": "", + "length": 8, + "start": 1133 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 19, + "end_lineno": 67, + "lineno": 67, + "src": { + "jump_code": "", + "length": 15, + "start": 1126 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 64, + "lineno": 64, + "src": { + "jump_code": "", + "length": 4, + "start": 1082 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 65, + "lineno": 65, + "src": { + "jump_code": "", + "length": 8, + "start": 1088 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 18, + "end_col_offset": 23, + "end_lineno": 66, + "lineno": 66, + "src": { + "jump_code": "", + "length": 5, + "start": 1115 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 19, + "end_lineno": 67, + "lineno": 66, + "name": "decimals", + "src": { + "jump_code": "", + "length": 44, + "start": 1097 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 23, + "end_col_offset": 30, + "end_lineno": 71, + "lineno": 71, + "src": { + "jump_code": "", + "length": 7, + "start": 1177 + } + } + ], + "classification": 0, + "col_offset": 13, + "end_col_offset": 30, + "end_lineno": 71, + "lineno": 71, + "src": { + "jump_code": "", + "length": 17, + "start": 1167 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 40, + "end_col_offset": 47, + "end_lineno": 71, + "lineno": 71, + "src": { + "jump_code": "", + "length": 7, + "start": 1194 + } + } + ], + "classification": 0, + "col_offset": 32, + "end_col_offset": 47, + "end_lineno": 71, + "lineno": 71, + "src": { + "jump_code": "", + "length": 15, + "start": 1186 + } + } + ], + "classification": 1, + "col_offset": 13, + "end_col_offset": 47, + "end_lineno": 71, + "lineno": 71, + "src": { + "jump_code": "", + "length": 34, + "start": 1167 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 40, + "end_lineno": 72, + "lineno": 72, + "src": { + "jump_code": "", + "length": 36, + "start": 1216 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 22, + "end_lineno": 72, + "lineno": 72, + "src": { + "jump_code": "", + "length": 3, + "start": 1231 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 29, + "end_lineno": 72, + "lineno": 72, + "src": { + "jump_code": "", + "length": 10, + "start": 1231 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 72, + "lineno": 72, + "src": { + "jump_code": "", + "length": 26, + "start": 1216 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 72, + "lineno": 72, + "src": { + "jump_code": "", + "length": 4, + "start": 1216 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 72, + "lineno": 72, + "src": { + "jump_code": "", + "length": 14, + "start": 1216 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 72, + "lineno": 72, + "src": { + "jump_code": "", + "length": 26, + "start": 1216 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 34, + "end_col_offset": 40, + "end_lineno": 72, + "lineno": 72, + "src": { + "jump_code": "", + "length": 6, + "start": 1246 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 40, + "end_lineno": 72, + "lineno": 72, + "src": { + "jump_code": "", + "length": 36, + "start": 1216 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Add", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 73, + "lineno": 73, + "src": { + "jump_code": "", + "length": 34, + "start": 1257 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 27, + "end_lineno": 73, + "lineno": 73, + "src": { + "jump_code": "", + "length": 8, + "start": 1272 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 73, + "lineno": 73, + "src": { + "jump_code": "", + "length": 24, + "start": 1257 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 73, + "lineno": 73, + "src": { + "jump_code": "", + "length": 4, + "start": 1257 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 73, + "lineno": 73, + "src": { + "jump_code": "", + "length": 14, + "start": 1257 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 73, + "lineno": 73, + "src": { + "jump_code": "", + "length": 24, + "start": 1257 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 32, + "end_col_offset": 38, + "end_lineno": 73, + "lineno": 73, + "src": { + "jump_code": "", + "length": 6, + "start": 1285 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 73, + "lineno": 73, + "src": { + "jump_code": "", + "length": 34, + "start": 1257 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 20, + "end_lineno": 74, + "lineno": 74, + "src": { + "jump_code": "", + "length": 3, + "start": 1309 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 27, + "end_lineno": 74, + "lineno": 74, + "src": { + "jump_code": "", + "length": 10, + "start": 1309 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 29, + "end_col_offset": 37, + "end_lineno": 74, + "lineno": 74, + "src": { + "jump_code": "", + "length": 8, + "start": 1321 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 39, + "end_col_offset": 45, + "end_lineno": 74, + "lineno": 74, + "src": { + "jump_code": "", + "length": 6, + "start": 1331 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 74, + "lineno": 74, + "src": { + "jump_code": "", + "length": 8, + "start": 1300 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 46, + "end_lineno": 74, + "lineno": 74, + "src": { + "jump_code": "", + "length": 38, + "start": 1300 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 46, + "end_lineno": 74, + "lineno": 74, + "src": { + "jump_code": "", + "length": 42, + "start": 1296 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "NameConstant", + "children": [], + "classification": 0, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 75, + "lineno": 75, + "src": { + "jump_code": "", + "length": 4, + "start": 1350 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 75, + "lineno": 75, + "src": { + "jump_code": "", + "length": 11, + "start": 1343 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 70, + "lineno": 70, + "src": { + "jump_code": "", + "length": 8, + "start": 1145 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 52, + "end_col_offset": 56, + "end_lineno": 71, + "lineno": 71, + "src": { + "jump_code": "", + "length": 4, + "start": 1206 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 15, + "end_lineno": 75, + "lineno": 71, + "name": "transfer", + "src": { + "jump_code": "", + "length": 200, + "start": 1154 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 21, + "end_col_offset": 28, + "end_lineno": 79, + "lineno": 79, + "src": { + "jump_code": "", + "length": 7, + "start": 1388 + } + } + ], + "classification": 0, + "col_offset": 12, + "end_col_offset": 28, + "end_lineno": 79, + "lineno": 79, + "src": { + "jump_code": "", + "length": 16, + "start": 1379 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 38, + "end_col_offset": 45, + "end_lineno": 79, + "lineno": 79, + "src": { + "jump_code": "", + "length": 7, + "start": 1405 + } + } + ], + "classification": 0, + "col_offset": 30, + "end_col_offset": 45, + "end_lineno": 79, + "lineno": 79, + "src": { + "jump_code": "", + "length": 15, + "start": 1397 + } + } + ], + "classification": 1, + "col_offset": 12, + "end_col_offset": 45, + "end_lineno": 79, + "lineno": 79, + "src": { + "jump_code": "", + "length": 33, + "start": 1379 + } + }, + { + "ast_type": "Assign", + "children": [ + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 31, + "end_col_offset": 38, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 7, + "start": 1454 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 39, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 35, + "start": 1427 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 22, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 3, + "start": 1442 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 29, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 10, + "start": 1442 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 26, + "start": 1427 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 4, + "start": 1427 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 14, + "start": 1427 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 26, + "start": 1427 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 39, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 35, + "start": 1427 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 42, + "end_col_offset": 48, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 6, + "start": 1465 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 48, + "end_lineno": 80, + "lineno": 80, + "src": { + "jump_code": "", + "length": 44, + "start": 1427 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 20, + "end_lineno": 81, + "lineno": 81, + "src": { + "jump_code": "", + "length": 3, + "start": 1489 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 27, + "end_lineno": 81, + "lineno": 81, + "src": { + "jump_code": "", + "length": 10, + "start": 1489 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 29, + "end_col_offset": 36, + "end_lineno": 81, + "lineno": 81, + "src": { + "jump_code": "", + "length": 7, + "start": 1501 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 38, + "end_col_offset": 44, + "end_lineno": 81, + "lineno": 81, + "src": { + "jump_code": "", + "length": 6, + "start": 1510 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 81, + "lineno": 81, + "src": { + "jump_code": "", + "length": 8, + "start": 1480 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 45, + "end_lineno": 81, + "lineno": 81, + "src": { + "jump_code": "", + "length": 37, + "start": 1480 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 45, + "end_lineno": 81, + "lineno": 81, + "src": { + "jump_code": "", + "length": 41, + "start": 1476 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "NameConstant", + "children": [], + "classification": 0, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 82, + "lineno": 82, + "src": { + "jump_code": "", + "length": 4, + "start": 1529 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 82, + "lineno": 82, + "src": { + "jump_code": "", + "length": 11, + "start": 1522 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 78, + "lineno": 78, + "src": { + "jump_code": "", + "length": 8, + "start": 1358 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 50, + "end_col_offset": 54, + "end_lineno": 79, + "lineno": 79, + "src": { + "jump_code": "", + "length": 4, + "start": 1417 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 15, + "end_lineno": 82, + "lineno": 79, + "name": "approve", + "src": { + "jump_code": "", + "length": 166, + "start": 1367 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 25, + "end_col_offset": 32, + "end_lineno": 86, + "lineno": 86, + "src": { + "jump_code": "", + "length": 7, + "start": 1571 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 32, + "end_lineno": 86, + "lineno": 86, + "src": { + "jump_code": "", + "length": 15, + "start": 1563 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 51, + "end_lineno": 86, + "lineno": 86, + "src": { + "jump_code": "", + "length": 7, + "start": 1590 + } + } + ], + "classification": 0, + "col_offset": 34, + "end_col_offset": 51, + "end_lineno": 86, + "lineno": 86, + "src": { + "jump_code": "", + "length": 17, + "start": 1580 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 61, + "end_col_offset": 68, + "end_lineno": 86, + "lineno": 86, + "src": { + "jump_code": "", + "length": 7, + "start": 1607 + } + } + ], + "classification": 0, + "col_offset": 53, + "end_col_offset": 68, + "end_lineno": 86, + "lineno": 86, + "src": { + "jump_code": "", + "length": 15, + "start": 1599 + } + } + ], + "classification": 1, + "col_offset": 17, + "end_col_offset": 68, + "end_lineno": 86, + "lineno": 86, + "src": { + "jump_code": "", + "length": 51, + "start": 1563 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 48, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 44, + "start": 1629 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 27, + "end_col_offset": 30, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 3, + "start": 1652 + } + } + ], + "classification": 0, + "col_offset": 27, + "end_col_offset": 37, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 10, + "start": 1652 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 34, + "start": 1629 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 25, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 6, + "start": 1644 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 26, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 22, + "start": 1629 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 4, + "start": 1629 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 14, + "start": 1629 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 26, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 22, + "start": 1629 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 34, + "start": 1629 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 42, + "end_col_offset": 48, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 6, + "start": 1667 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 48, + "end_lineno": 87, + "lineno": 87, + "src": { + "jump_code": "", + "length": 44, + "start": 1629 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 36, + "end_lineno": 88, + "lineno": 88, + "src": { + "jump_code": "", + "length": 32, + "start": 1678 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 25, + "end_lineno": 88, + "lineno": 88, + "src": { + "jump_code": "", + "length": 6, + "start": 1693 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 26, + "end_lineno": 88, + "lineno": 88, + "src": { + "jump_code": "", + "length": 22, + "start": 1678 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 88, + "lineno": 88, + "src": { + "jump_code": "", + "length": 4, + "start": 1678 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 88, + "lineno": 88, + "src": { + "jump_code": "", + "length": 14, + "start": 1678 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 26, + "end_lineno": 88, + "lineno": 88, + "src": { + "jump_code": "", + "length": 22, + "start": 1678 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 30, + "end_col_offset": 36, + "end_lineno": 88, + "lineno": 88, + "src": { + "jump_code": "", + "length": 6, + "start": 1704 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 36, + "end_lineno": 88, + "lineno": 88, + "src": { + "jump_code": "", + "length": 32, + "start": 1678 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Add", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 89, + "lineno": 89, + "src": { + "jump_code": "", + "length": 34, + "start": 1715 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 27, + "end_lineno": 89, + "lineno": 89, + "src": { + "jump_code": "", + "length": 8, + "start": 1730 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 89, + "lineno": 89, + "src": { + "jump_code": "", + "length": 24, + "start": 1715 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 89, + "lineno": 89, + "src": { + "jump_code": "", + "length": 4, + "start": 1715 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 89, + "lineno": 89, + "src": { + "jump_code": "", + "length": 14, + "start": 1715 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 89, + "lineno": 89, + "src": { + "jump_code": "", + "length": 24, + "start": 1715 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 32, + "end_col_offset": 38, + "end_lineno": 89, + "lineno": 89, + "src": { + "jump_code": "", + "length": 6, + "start": 1743 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 89, + "lineno": 89, + "src": { + "jump_code": "", + "length": 34, + "start": 1715 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 23, + "end_lineno": 90, + "lineno": 90, + "src": { + "jump_code": "", + "length": 6, + "start": 1767 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 25, + "end_col_offset": 33, + "end_lineno": 90, + "lineno": 90, + "src": { + "jump_code": "", + "length": 8, + "start": 1775 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 35, + "end_col_offset": 41, + "end_lineno": 90, + "lineno": 90, + "src": { + "jump_code": "", + "length": 6, + "start": 1785 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 90, + "lineno": 90, + "src": { + "jump_code": "", + "length": 8, + "start": 1758 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 42, + "end_lineno": 90, + "lineno": 90, + "src": { + "jump_code": "", + "length": 34, + "start": 1758 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 42, + "end_lineno": 90, + "lineno": 90, + "src": { + "jump_code": "", + "length": 38, + "start": 1754 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "NameConstant", + "children": [], + "classification": 0, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 91, + "lineno": 91, + "src": { + "jump_code": "", + "length": 4, + "start": 1804 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 91, + "lineno": 91, + "src": { + "jump_code": "", + "length": 11, + "start": 1797 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 85, + "lineno": 85, + "src": { + "jump_code": "", + "length": 8, + "start": 1537 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 73, + "end_col_offset": 77, + "end_lineno": 86, + "lineno": 86, + "src": { + "jump_code": "", + "length": 4, + "start": 1619 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 15, + "end_lineno": 91, + "lineno": 86, + "name": "transferFrom", + "src": { + "jump_code": "", + "length": 262, + "start": 1546 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [], + "classification": 1, + "col_offset": 0, + "end_col_offset": 3, + "end_lineno": 96, + "lineno": 96, + "src": { + "jump_code": "", + "length": 3, + "start": 1827 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 32, + "end_col_offset": 36, + "end_lineno": 97, + "lineno": 97, + "src": { + "jump_code": "", + "length": 4, + "start": 1889 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 97, + "lineno": 97, + "src": { + "jump_code": "", + "length": 4, + "start": 1868 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 21, + "end_lineno": 97, + "lineno": 97, + "src": { + "jump_code": "", + "length": 10, + "start": 1868 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 31, + "end_lineno": 97, + "lineno": 97, + "src": { + "jump_code": "", + "length": 20, + "start": 1868 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 37, + "end_lineno": 97, + "lineno": 97, + "src": { + "jump_code": "", + "length": 26, + "start": 1868 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 37, + "end_lineno": 97, + "lineno": 97, + "src": { + "jump_code": "", + "length": 33, + "start": 1861 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 94, + "lineno": 94, + "src": { + "jump_code": "", + "length": 4, + "start": 1812 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 95, + "lineno": 95, + "src": { + "jump_code": "", + "length": 8, + "start": 1818 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 21, + "end_col_offset": 28, + "end_lineno": 96, + "lineno": 96, + "src": { + "jump_code": "", + "length": 7, + "start": 1848 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 37, + "end_lineno": 97, + "lineno": 96, + "name": "totalAssets", + "src": { + "jump_code": "", + "length": 67, + "start": 1827 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 34, + "end_col_offset": 41, + "end_lineno": 102, + "lineno": 102, + "src": { + "jump_code": "", + "length": 7, + "start": 1947 + } + } + ], + "classification": 0, + "col_offset": 21, + "end_col_offset": 41, + "end_lineno": 102, + "lineno": 102, + "src": { + "jump_code": "", + "length": 20, + "start": 1934 + } + } + ], + "classification": 1, + "col_offset": 21, + "end_col_offset": 41, + "end_lineno": 102, + "lineno": 102, + "src": { + "jump_code": "", + "length": 20, + "start": 1934 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 24, + "end_lineno": 103, + "lineno": 103, + "src": { + "jump_code": "", + "length": 7, + "start": 1985 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 103, + "lineno": 103, + "src": { + "jump_code": "", + "length": 11, + "start": 1972 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 27, + "end_col_offset": 31, + "end_lineno": 103, + "lineno": 103, + "src": { + "jump_code": "", + "length": 4, + "start": 1995 + } + } + ], + "classification": 0, + "col_offset": 27, + "end_col_offset": 43, + "end_lineno": 103, + "lineno": 103, + "src": { + "jump_code": "", + "length": 16, + "start": 1995 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 43, + "end_lineno": 103, + "lineno": 103, + "src": { + "jump_code": "", + "length": 39, + "start": 1972 + } + }, + { + "ast_type": "If", + "children": [ + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 15, + "end_col_offset": 16, + "end_lineno": 105, + "lineno": 105, + "src": { + "jump_code": "", + "length": 1, + "start": 2052 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 105, + "lineno": 105, + "src": { + "jump_code": "", + "length": 8, + "start": 2045 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 7, + "end_col_offset": 18, + "end_lineno": 104, + "lineno": 104, + "src": { + "jump_code": "", + "length": 11, + "start": 2019 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 23, + "end_lineno": 104, + "lineno": 104, + "src": { + "jump_code": "", + "length": 16, + "start": 2019 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 22, + "end_col_offset": 23, + "end_lineno": 104, + "lineno": 104, + "src": { + "jump_code": "", + "length": 1, + "start": 2034 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 23, + "end_lineno": 104, + "lineno": 104, + "src": { + "jump_code": "", + "length": 16, + "start": 2019 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 16, + "end_lineno": 105, + "lineno": 104, + "src": { + "jump_code": "", + "length": 37, + "start": 2016 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "BinOp", + "children": [ + { + "ast_type": "BinOp", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 22, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 11, + "start": 2218 + } + }, + { + "ast_type": "Mult", + "children": [], + "classification": 0, + "col_offset": 11, + "end_col_offset": 51, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 40, + "start": 2218 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 46, + "end_col_offset": 50, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 4, + "start": 2253 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 25, + "end_col_offset": 29, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 4, + "start": 2232 + } + } + ], + "classification": 0, + "col_offset": 25, + "end_col_offset": 35, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 10, + "start": 2232 + } + } + ], + "classification": 0, + "col_offset": 25, + "end_col_offset": 45, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 20, + "start": 2232 + } + } + ], + "classification": 0, + "col_offset": 25, + "end_col_offset": 51, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 26, + "start": 2232 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 51, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 40, + "start": 2218 + } + }, + { + "ast_type": "Div", + "children": [], + "classification": 0, + "col_offset": 11, + "end_col_offset": 65, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 54, + "start": 2218 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 54, + "end_col_offset": 65, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 11, + "start": 2261 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 65, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 54, + "start": 2218 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 65, + "end_lineno": 109, + "lineno": 109, + "src": { + "jump_code": "", + "length": 61, + "start": 2211 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 100, + "lineno": 100, + "src": { + "jump_code": "", + "length": 4, + "start": 1898 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 101, + "lineno": 101, + "src": { + "jump_code": "", + "length": 8, + "start": 1904 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 46, + "end_col_offset": 53, + "end_lineno": 102, + "lineno": 102, + "src": { + "jump_code": "", + "length": 7, + "start": 1959 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 65, + "end_lineno": 109, + "lineno": 102, + "name": "_convertToAssets", + "src": { + "jump_code": "", + "length": 359, + "start": 1913 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 33, + "end_col_offset": 40, + "end_lineno": 114, + "lineno": 114, + "src": { + "jump_code": "", + "length": 7, + "start": 2324 + } + } + ], + "classification": 0, + "col_offset": 20, + "end_col_offset": 40, + "end_lineno": 114, + "lineno": 114, + "src": { + "jump_code": "", + "length": 20, + "start": 2311 + } + } + ], + "classification": 1, + "col_offset": 20, + "end_col_offset": 40, + "end_lineno": 114, + "lineno": 114, + "src": { + "jump_code": "", + "length": 20, + "start": 2311 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 33, + "end_col_offset": 44, + "end_lineno": 115, + "lineno": 115, + "src": { + "jump_code": "", + "length": 11, + "start": 2378 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 115, + "lineno": 115, + "src": { + "jump_code": "", + "length": 4, + "start": 2356 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 32, + "end_lineno": 115, + "lineno": 115, + "src": { + "jump_code": "", + "length": 21, + "start": 2356 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 45, + "end_lineno": 115, + "lineno": 115, + "src": { + "jump_code": "", + "length": 34, + "start": 2356 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 45, + "end_lineno": 115, + "lineno": 115, + "src": { + "jump_code": "", + "length": 41, + "start": 2349 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 112, + "lineno": 112, + "src": { + "jump_code": "", + "length": 4, + "start": 2276 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 113, + "lineno": 113, + "src": { + "jump_code": "", + "length": 8, + "start": 2282 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 45, + "end_col_offset": 52, + "end_lineno": 114, + "lineno": 114, + "src": { + "jump_code": "", + "length": 7, + "start": 2336 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 45, + "end_lineno": 115, + "lineno": 114, + "name": "convertToAssets", + "src": { + "jump_code": "", + "length": 99, + "start": 2291 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 34, + "end_col_offset": 41, + "end_lineno": 120, + "lineno": 120, + "src": { + "jump_code": "", + "length": 7, + "start": 2443 + } + } + ], + "classification": 0, + "col_offset": 21, + "end_col_offset": 41, + "end_lineno": 120, + "lineno": 120, + "src": { + "jump_code": "", + "length": 20, + "start": 2430 + } + } + ], + "classification": 1, + "col_offset": 21, + "end_col_offset": 41, + "end_lineno": 120, + "lineno": 120, + "src": { + "jump_code": "", + "length": 20, + "start": 2430 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 24, + "end_lineno": 121, + "lineno": 121, + "src": { + "jump_code": "", + "length": 7, + "start": 2481 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 121, + "lineno": 121, + "src": { + "jump_code": "", + "length": 11, + "start": 2468 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 27, + "end_col_offset": 31, + "end_lineno": 121, + "lineno": 121, + "src": { + "jump_code": "", + "length": 4, + "start": 2491 + } + } + ], + "classification": 0, + "col_offset": 27, + "end_col_offset": 43, + "end_lineno": 121, + "lineno": 121, + "src": { + "jump_code": "", + "length": 16, + "start": 2491 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 43, + "end_lineno": 121, + "lineno": 121, + "src": { + "jump_code": "", + "length": 39, + "start": 2468 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 24, + "end_lineno": 122, + "lineno": 122, + "src": { + "jump_code": "", + "length": 7, + "start": 2525 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 15, + "end_lineno": 122, + "lineno": 122, + "src": { + "jump_code": "", + "length": 11, + "start": 2512 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 48, + "end_col_offset": 52, + "end_lineno": 122, + "lineno": 122, + "src": { + "jump_code": "", + "length": 4, + "start": 2556 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 27, + "end_col_offset": 31, + "end_lineno": 122, + "lineno": 122, + "src": { + "jump_code": "", + "length": 4, + "start": 2535 + } + } + ], + "classification": 0, + "col_offset": 27, + "end_col_offset": 37, + "end_lineno": 122, + "lineno": 122, + "src": { + "jump_code": "", + "length": 10, + "start": 2535 + } + } + ], + "classification": 0, + "col_offset": 27, + "end_col_offset": 47, + "end_lineno": 122, + "lineno": 122, + "src": { + "jump_code": "", + "length": 20, + "start": 2535 + } + } + ], + "classification": 0, + "col_offset": 27, + "end_col_offset": 53, + "end_lineno": 122, + "lineno": 122, + "src": { + "jump_code": "", + "length": 26, + "start": 2535 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 53, + "end_lineno": 122, + "lineno": 122, + "src": { + "jump_code": "", + "length": 49, + "start": 2512 + } + }, + { + "ast_type": "If", + "children": [ + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 15, + "end_col_offset": 26, + "end_lineno": 124, + "lineno": 124, + "src": { + "jump_code": "", + "length": 11, + "start": 2622 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 26, + "end_lineno": 124, + "lineno": 124, + "src": { + "jump_code": "", + "length": 18, + "start": 2615 + } + }, + { + "ast_type": "BoolOp", + "children": [ + { + "ast_type": "Or", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 43, + "end_lineno": 123, + "lineno": 123, + "src": { + "jump_code": "", + "length": 36, + "start": 2569 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 7, + "end_col_offset": 18, + "end_lineno": 123, + "lineno": 123, + "src": { + "jump_code": "", + "length": 11, + "start": 2569 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 23, + "end_lineno": 123, + "lineno": 123, + "src": { + "jump_code": "", + "length": 16, + "start": 2569 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 22, + "end_col_offset": 23, + "end_lineno": 123, + "lineno": 123, + "src": { + "jump_code": "", + "length": 1, + "start": 2584 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 23, + "end_lineno": 123, + "lineno": 123, + "src": { + "jump_code": "", + "length": 16, + "start": 2569 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 27, + "end_col_offset": 38, + "end_lineno": 123, + "lineno": 123, + "src": { + "jump_code": "", + "length": 11, + "start": 2589 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 27, + "end_col_offset": 43, + "end_lineno": 123, + "lineno": 123, + "src": { + "jump_code": "", + "length": 16, + "start": 2589 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 42, + "end_col_offset": 43, + "end_lineno": 123, + "lineno": 123, + "src": { + "jump_code": "", + "length": 1, + "start": 2604 + } + } + ], + "classification": 0, + "col_offset": 27, + "end_col_offset": 43, + "end_lineno": 123, + "lineno": 123, + "src": { + "jump_code": "", + "length": 16, + "start": 2589 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 43, + "end_lineno": 123, + "lineno": 123, + "src": { + "jump_code": "", + "length": 36, + "start": 2569 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 26, + "end_lineno": 124, + "lineno": 123, + "src": { + "jump_code": "", + "length": 67, + "start": 2566 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "BinOp", + "children": [ + { + "ast_type": "BinOp", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 22, + "end_lineno": 127, + "lineno": 127, + "src": { + "jump_code": "", + "length": 11, + "start": 2735 + } + }, + { + "ast_type": "Mult", + "children": [], + "classification": 0, + "col_offset": 11, + "end_col_offset": 36, + "end_lineno": 127, + "lineno": 127, + "src": { + "jump_code": "", + "length": 25, + "start": 2735 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 25, + "end_col_offset": 36, + "end_lineno": 127, + "lineno": 127, + "src": { + "jump_code": "", + "length": 11, + "start": 2749 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 36, + "end_lineno": 127, + "lineno": 127, + "src": { + "jump_code": "", + "length": 25, + "start": 2735 + } + }, + { + "ast_type": "Div", + "children": [], + "classification": 0, + "col_offset": 11, + "end_col_offset": 50, + "end_lineno": 127, + "lineno": 127, + "src": { + "jump_code": "", + "length": 39, + "start": 2735 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 39, + "end_col_offset": 50, + "end_lineno": 127, + "lineno": 127, + "src": { + "jump_code": "", + "length": 11, + "start": 2763 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 50, + "end_lineno": 127, + "lineno": 127, + "src": { + "jump_code": "", + "length": 39, + "start": 2735 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 50, + "end_lineno": 127, + "lineno": 127, + "src": { + "jump_code": "", + "length": 46, + "start": 2728 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 118, + "lineno": 118, + "src": { + "jump_code": "", + "length": 4, + "start": 2394 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 119, + "lineno": 119, + "src": { + "jump_code": "", + "length": 8, + "start": 2400 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 46, + "end_col_offset": 53, + "end_lineno": 120, + "lineno": 120, + "src": { + "jump_code": "", + "length": 7, + "start": 2455 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 50, + "end_lineno": 127, + "lineno": 120, + "name": "_convertToShares", + "src": { + "jump_code": "", + "length": 365, + "start": 2409 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 33, + "end_col_offset": 40, + "end_lineno": 132, + "lineno": 132, + "src": { + "jump_code": "", + "length": 7, + "start": 2826 + } + } + ], + "classification": 0, + "col_offset": 20, + "end_col_offset": 40, + "end_lineno": 132, + "lineno": 132, + "src": { + "jump_code": "", + "length": 20, + "start": 2813 + } + } + ], + "classification": 1, + "col_offset": 20, + "end_col_offset": 40, + "end_lineno": 132, + "lineno": 132, + "src": { + "jump_code": "", + "length": 20, + "start": 2813 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 33, + "end_col_offset": 44, + "end_lineno": 133, + "lineno": 133, + "src": { + "jump_code": "", + "length": 11, + "start": 2880 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 133, + "lineno": 133, + "src": { + "jump_code": "", + "length": 4, + "start": 2858 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 32, + "end_lineno": 133, + "lineno": 133, + "src": { + "jump_code": "", + "length": 21, + "start": 2858 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 45, + "end_lineno": 133, + "lineno": 133, + "src": { + "jump_code": "", + "length": 34, + "start": 2858 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 45, + "end_lineno": 133, + "lineno": 133, + "src": { + "jump_code": "", + "length": 41, + "start": 2851 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 130, + "lineno": 130, + "src": { + "jump_code": "", + "length": 4, + "start": 2778 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 131, + "lineno": 131, + "src": { + "jump_code": "", + "length": 8, + "start": 2784 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 45, + "end_col_offset": 52, + "end_lineno": 132, + "lineno": 132, + "src": { + "jump_code": "", + "length": 7, + "start": 2838 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 45, + "end_lineno": 133, + "lineno": 132, + "name": "convertToShares", + "src": { + "jump_code": "", + "length": 99, + "start": 2793 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 29, + "end_lineno": 138, + "lineno": 138, + "src": { + "jump_code": "", + "length": 7, + "start": 2933 + } + } + ], + "classification": 0, + "col_offset": 15, + "end_col_offset": 29, + "end_lineno": 138, + "lineno": 138, + "src": { + "jump_code": "", + "length": 14, + "start": 2926 + } + } + ], + "classification": 1, + "col_offset": 15, + "end_col_offset": 29, + "end_lineno": 138, + "lineno": 138, + "src": { + "jump_code": "", + "length": 14, + "start": 2926 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 22, + "end_lineno": 139, + "lineno": 139, + "src": { + "jump_code": "", + "length": 11, + "start": 2965 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 22, + "end_lineno": 139, + "lineno": 139, + "src": { + "jump_code": "", + "length": 18, + "start": 2958 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 136, + "lineno": 136, + "src": { + "jump_code": "", + "length": 4, + "start": 2896 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 137, + "lineno": 137, + "src": { + "jump_code": "", + "length": 8, + "start": 2902 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 34, + "end_col_offset": 41, + "end_lineno": 138, + "lineno": 138, + "src": { + "jump_code": "", + "length": 7, + "start": 2945 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 22, + "end_lineno": 139, + "lineno": 138, + "name": "maxDeposit", + "src": { + "jump_code": "", + "length": 65, + "start": 2911 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 27, + "end_col_offset": 34, + "end_lineno": 144, + "lineno": 144, + "src": { + "jump_code": "", + "length": 7, + "start": 3022 + } + } + ], + "classification": 0, + "col_offset": 19, + "end_col_offset": 34, + "end_lineno": 144, + "lineno": 144, + "src": { + "jump_code": "", + "length": 15, + "start": 3014 + } + } + ], + "classification": 1, + "col_offset": 19, + "end_col_offset": 34, + "end_lineno": 144, + "lineno": 144, + "src": { + "jump_code": "", + "length": 15, + "start": 3014 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 33, + "end_col_offset": 39, + "end_lineno": 145, + "lineno": 145, + "src": { + "jump_code": "", + "length": 6, + "start": 3076 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 145, + "lineno": 145, + "src": { + "jump_code": "", + "length": 4, + "start": 3054 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 32, + "end_lineno": 145, + "lineno": 145, + "src": { + "jump_code": "", + "length": 21, + "start": 3054 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 40, + "end_lineno": 145, + "lineno": 145, + "src": { + "jump_code": "", + "length": 29, + "start": 3054 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 40, + "end_lineno": 145, + "lineno": 145, + "src": { + "jump_code": "", + "length": 36, + "start": 3047 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 142, + "lineno": 142, + "src": { + "jump_code": "", + "length": 4, + "start": 2980 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 143, + "lineno": 143, + "src": { + "jump_code": "", + "length": 8, + "start": 2986 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 39, + "end_col_offset": 46, + "end_lineno": 144, + "lineno": 144, + "src": { + "jump_code": "", + "length": 7, + "start": 3034 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 40, + "end_lineno": 145, + "lineno": 144, + "name": "previewDeposit", + "src": { + "jump_code": "", + "length": 88, + "start": 2995 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 20, + "end_col_offset": 27, + "end_lineno": 149, + "lineno": 149, + "src": { + "jump_code": "", + "length": 7, + "start": 3116 + } + } + ], + "classification": 0, + "col_offset": 12, + "end_col_offset": 27, + "end_lineno": 149, + "lineno": 149, + "src": { + "jump_code": "", + "length": 15, + "start": 3108 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 39, + "end_col_offset": 46, + "end_lineno": 149, + "lineno": 149, + "src": { + "jump_code": "", + "length": 7, + "start": 3135 + } + } + ], + "classification": 0, + "col_offset": 29, + "end_col_offset": 46, + "end_lineno": 149, + "lineno": 149, + "src": { + "jump_code": "", + "length": 17, + "start": 3125 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 47, + "end_col_offset": 50, + "end_lineno": 149, + "lineno": 149, + "src": { + "jump_code": "", + "length": 3, + "start": 3143 + } + } + ], + "classification": 0, + "col_offset": 47, + "end_col_offset": 57, + "end_lineno": 149, + "lineno": 149, + "src": { + "jump_code": "", + "length": 10, + "start": 3143 + } + } + ], + "classification": 1, + "col_offset": 12, + "end_col_offset": 57, + "end_lineno": 149, + "lineno": 149, + "src": { + "jump_code": "", + "length": 45, + "start": 3108 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 150, + "lineno": 150, + "src": { + "jump_code": "", + "length": 7, + "start": 3179 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 150, + "lineno": 150, + "src": { + "jump_code": "", + "length": 6, + "start": 3171 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 50, + "end_lineno": 150, + "lineno": 150, + "src": { + "jump_code": "", + "length": 6, + "start": 3211 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 26, + "end_lineno": 150, + "lineno": 150, + "src": { + "jump_code": "", + "length": 4, + "start": 3189 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 43, + "end_lineno": 150, + "lineno": 150, + "src": { + "jump_code": "", + "length": 21, + "start": 3189 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 51, + "end_lineno": 150, + "lineno": 150, + "src": { + "jump_code": "", + "length": 29, + "start": 3189 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 51, + "end_lineno": 150, + "lineno": 150, + "src": { + "jump_code": "", + "length": 47, + "start": 3171 + } + }, + { + "ast_type": "Expr", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 28, + "end_col_offset": 31, + "end_lineno": 151, + "lineno": 151, + "src": { + "jump_code": "", + "length": 3, + "start": 3247 + } + } + ], + "classification": 0, + "col_offset": 28, + "end_col_offset": 38, + "end_lineno": 151, + "lineno": 151, + "src": { + "jump_code": "", + "length": 10, + "start": 3247 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 40, + "end_col_offset": 44, + "end_lineno": 151, + "lineno": 151, + "src": { + "jump_code": "", + "length": 4, + "start": 3259 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 46, + "end_col_offset": 52, + "end_lineno": 151, + "lineno": 151, + "src": { + "jump_code": "", + "length": 6, + "start": 3265 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 151, + "lineno": 151, + "src": { + "jump_code": "", + "length": 4, + "start": 3223 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 14, + "end_lineno": 151, + "lineno": 151, + "src": { + "jump_code": "", + "length": 10, + "start": 3223 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 27, + "end_lineno": 151, + "lineno": 151, + "src": { + "jump_code": "", + "length": 23, + "start": 3223 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 53, + "end_lineno": 151, + "lineno": 151, + "src": { + "jump_code": "", + "length": 49, + "start": 3223 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 53, + "end_lineno": 151, + "lineno": 151, + "src": { + "jump_code": "", + "length": 49, + "start": 3223 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Add", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 153, + "lineno": 153, + "src": { + "jump_code": "", + "length": 26, + "start": 3278 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 153, + "lineno": 153, + "src": { + "jump_code": "", + "length": 4, + "start": 3278 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 20, + "end_lineno": 153, + "lineno": 153, + "src": { + "jump_code": "", + "length": 16, + "start": 3278 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 30, + "end_lineno": 153, + "lineno": 153, + "src": { + "jump_code": "", + "length": 6, + "start": 3298 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 153, + "lineno": 153, + "src": { + "jump_code": "", + "length": 26, + "start": 3278 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Add", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 154, + "lineno": 154, + "src": { + "jump_code": "", + "length": 34, + "start": 3309 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 27, + "end_lineno": 154, + "lineno": 154, + "src": { + "jump_code": "", + "length": 8, + "start": 3324 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 154, + "lineno": 154, + "src": { + "jump_code": "", + "length": 24, + "start": 3309 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 154, + "lineno": 154, + "src": { + "jump_code": "", + "length": 4, + "start": 3309 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 154, + "lineno": 154, + "src": { + "jump_code": "", + "length": 14, + "start": 3309 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 154, + "lineno": 154, + "src": { + "jump_code": "", + "length": 24, + "start": 3309 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 32, + "end_col_offset": 38, + "end_lineno": 154, + "lineno": 154, + "src": { + "jump_code": "", + "length": 6, + "start": 3337 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 154, + "lineno": 154, + "src": { + "jump_code": "", + "length": 34, + "start": 3309 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 23, + "end_col_offset": 30, + "end_lineno": 155, + "lineno": 155, + "src": { + "jump_code": "", + "length": 7, + "start": 3367 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 22, + "end_lineno": 155, + "lineno": 155, + "src": { + "jump_code": "", + "length": 5, + "start": 3361 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 31, + "end_lineno": 155, + "lineno": 155, + "src": { + "jump_code": "", + "length": 14, + "start": 3361 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 33, + "end_col_offset": 41, + "end_lineno": 155, + "lineno": 155, + "src": { + "jump_code": "", + "length": 8, + "start": 3377 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 43, + "end_col_offset": 49, + "end_lineno": 155, + "lineno": 155, + "src": { + "jump_code": "", + "length": 6, + "start": 3387 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 155, + "lineno": 155, + "src": { + "jump_code": "", + "length": 8, + "start": 3352 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 50, + "end_lineno": 155, + "lineno": 155, + "src": { + "jump_code": "", + "length": 42, + "start": 3352 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 50, + "end_lineno": 155, + "lineno": 155, + "src": { + "jump_code": "", + "length": 46, + "start": 3348 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 16, + "end_col_offset": 19, + "end_lineno": 156, + "lineno": 156, + "src": { + "jump_code": "", + "length": 3, + "start": 3411 + } + } + ], + "classification": 0, + "col_offset": 16, + "end_col_offset": 26, + "end_lineno": 156, + "lineno": 156, + "src": { + "jump_code": "", + "length": 10, + "start": 3411 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 28, + "end_col_offset": 36, + "end_lineno": 156, + "lineno": 156, + "src": { + "jump_code": "", + "length": 8, + "start": 3423 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 38, + "end_col_offset": 44, + "end_lineno": 156, + "lineno": 156, + "src": { + "jump_code": "", + "length": 6, + "start": 3433 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 46, + "end_col_offset": 52, + "end_lineno": 156, + "lineno": 156, + "src": { + "jump_code": "", + "length": 6, + "start": 3441 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 15, + "end_lineno": 156, + "lineno": 156, + "src": { + "jump_code": "", + "length": 7, + "start": 3403 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 53, + "end_lineno": 156, + "lineno": 156, + "src": { + "jump_code": "", + "length": 45, + "start": 3403 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 53, + "end_lineno": 156, + "lineno": 156, + "src": { + "jump_code": "", + "length": 49, + "start": 3399 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 157, + "lineno": 157, + "src": { + "jump_code": "", + "length": 6, + "start": 3460 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 17, + "end_lineno": 157, + "lineno": 157, + "src": { + "jump_code": "", + "length": 13, + "start": 3453 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 148, + "lineno": 148, + "src": { + "jump_code": "", + "length": 8, + "start": 3087 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 62, + "end_col_offset": 69, + "end_lineno": 149, + "lineno": 149, + "src": { + "jump_code": "", + "length": 7, + "start": 3158 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 157, + "lineno": 149, + "name": "deposit", + "src": { + "jump_code": "", + "length": 370, + "start": 3096 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 26, + "end_lineno": 162, + "lineno": 162, + "src": { + "jump_code": "", + "length": 7, + "start": 3504 + } + } + ], + "classification": 0, + "col_offset": 12, + "end_col_offset": 26, + "end_lineno": 162, + "lineno": 162, + "src": { + "jump_code": "", + "length": 14, + "start": 3497 + } + } + ], + "classification": 1, + "col_offset": 12, + "end_col_offset": 26, + "end_lineno": 162, + "lineno": 162, + "src": { + "jump_code": "", + "length": 14, + "start": 3497 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 22, + "end_lineno": 163, + "lineno": 163, + "src": { + "jump_code": "", + "length": 11, + "start": 3536 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 22, + "end_lineno": 163, + "lineno": 163, + "src": { + "jump_code": "", + "length": 18, + "start": 3529 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 160, + "lineno": 160, + "src": { + "jump_code": "", + "length": 4, + "start": 3470 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 161, + "lineno": 161, + "src": { + "jump_code": "", + "length": 8, + "start": 3476 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 31, + "end_col_offset": 38, + "end_lineno": 162, + "lineno": 162, + "src": { + "jump_code": "", + "length": 7, + "start": 3516 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 22, + "end_lineno": 163, + "lineno": 162, + "name": "maxMint", + "src": { + "jump_code": "", + "length": 62, + "start": 3485 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 31, + "end_lineno": 168, + "lineno": 168, + "src": { + "jump_code": "", + "length": 7, + "start": 3590 + } + } + ], + "classification": 0, + "col_offset": 16, + "end_col_offset": 31, + "end_lineno": 168, + "lineno": 168, + "src": { + "jump_code": "", + "length": 15, + "start": 3582 + } + } + ], + "classification": 1, + "col_offset": 16, + "end_col_offset": 31, + "end_lineno": 168, + "lineno": 168, + "src": { + "jump_code": "", + "length": 15, + "start": 3582 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 169, + "lineno": 169, + "src": { + "jump_code": "", + "length": 7, + "start": 3623 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 169, + "lineno": 169, + "src": { + "jump_code": "", + "length": 6, + "start": 3615 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 50, + "end_lineno": 169, + "lineno": 169, + "src": { + "jump_code": "", + "length": 6, + "start": 3655 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 26, + "end_lineno": 169, + "lineno": 169, + "src": { + "jump_code": "", + "length": 4, + "start": 3633 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 43, + "end_lineno": 169, + "lineno": 169, + "src": { + "jump_code": "", + "length": 21, + "start": 3633 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 51, + "end_lineno": 169, + "lineno": 169, + "src": { + "jump_code": "", + "length": 29, + "start": 3633 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 51, + "end_lineno": 169, + "lineno": 169, + "src": { + "jump_code": "", + "length": 47, + "start": 3615 + } + }, + { + "ast_type": "If", + "children": [ + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 15, + "end_col_offset": 21, + "end_lineno": 173, + "lineno": 173, + "src": { + "jump_code": "", + "length": 6, + "start": 3814 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 21, + "end_lineno": 173, + "lineno": 173, + "src": { + "jump_code": "", + "length": 13, + "start": 3807 + } + }, + { + "ast_type": "BoolOp", + "children": [ + { + "ast_type": "And", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 54, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 47, + "start": 3750 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 7, + "end_col_offset": 13, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 6, + "start": 3750 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 18, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 11, + "start": 3750 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 17, + "end_col_offset": 18, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 1, + "start": 3760 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 18, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 11, + "start": 3750 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 48, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 4, + "start": 3787 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 23, + "end_col_offset": 27, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 4, + "start": 3766 + } + } + ], + "classification": 0, + "col_offset": 23, + "end_col_offset": 33, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 10, + "start": 3766 + } + } + ], + "classification": 0, + "col_offset": 23, + "end_col_offset": 43, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 20, + "start": 3766 + } + } + ], + "classification": 0, + "col_offset": 23, + "end_col_offset": 49, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 26, + "start": 3766 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 23, + "end_col_offset": 54, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 31, + "start": 3766 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 53, + "end_col_offset": 54, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 1, + "start": 3796 + } + } + ], + "classification": 0, + "col_offset": 23, + "end_col_offset": 54, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 31, + "start": 3766 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 54, + "end_lineno": 172, + "lineno": 172, + "src": { + "jump_code": "", + "length": 47, + "start": 3750 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 21, + "end_lineno": 173, + "lineno": 172, + "src": { + "jump_code": "", + "length": 73, + "start": 3747 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 175, + "lineno": 175, + "src": { + "jump_code": "", + "length": 6, + "start": 3884 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 17, + "end_lineno": 175, + "lineno": 175, + "src": { + "jump_code": "", + "length": 13, + "start": 3877 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 166, + "lineno": 166, + "src": { + "jump_code": "", + "length": 4, + "start": 3551 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 167, + "lineno": 167, + "src": { + "jump_code": "", + "length": 8, + "start": 3557 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 36, + "end_col_offset": 43, + "end_lineno": 168, + "lineno": 168, + "src": { + "jump_code": "", + "length": 7, + "start": 3602 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 175, + "lineno": 168, + "name": "previewMint", + "src": { + "jump_code": "", + "length": 324, + "start": 3566 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 24, + "end_lineno": 179, + "lineno": 179, + "src": { + "jump_code": "", + "length": 7, + "start": 3920 + } + } + ], + "classification": 0, + "col_offset": 9, + "end_col_offset": 24, + "end_lineno": 179, + "lineno": 179, + "src": { + "jump_code": "", + "length": 15, + "start": 3912 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 36, + "end_col_offset": 43, + "end_lineno": 179, + "lineno": 179, + "src": { + "jump_code": "", + "length": 7, + "start": 3939 + } + } + ], + "classification": 0, + "col_offset": 26, + "end_col_offset": 43, + "end_lineno": 179, + "lineno": 179, + "src": { + "jump_code": "", + "length": 17, + "start": 3929 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 47, + "end_lineno": 179, + "lineno": 179, + "src": { + "jump_code": "", + "length": 3, + "start": 3947 + } + } + ], + "classification": 0, + "col_offset": 44, + "end_col_offset": 54, + "end_lineno": 179, + "lineno": 179, + "src": { + "jump_code": "", + "length": 10, + "start": 3947 + } + } + ], + "classification": 1, + "col_offset": 9, + "end_col_offset": 54, + "end_lineno": 179, + "lineno": 179, + "src": { + "jump_code": "", + "length": 45, + "start": 3912 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 180, + "lineno": 180, + "src": { + "jump_code": "", + "length": 7, + "start": 3983 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 180, + "lineno": 180, + "src": { + "jump_code": "", + "length": 6, + "start": 3975 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 50, + "end_lineno": 180, + "lineno": 180, + "src": { + "jump_code": "", + "length": 6, + "start": 4015 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 26, + "end_lineno": 180, + "lineno": 180, + "src": { + "jump_code": "", + "length": 4, + "start": 3993 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 43, + "end_lineno": 180, + "lineno": 180, + "src": { + "jump_code": "", + "length": 21, + "start": 3993 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 51, + "end_lineno": 180, + "lineno": 180, + "src": { + "jump_code": "", + "length": 29, + "start": 3993 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 51, + "end_lineno": 180, + "lineno": 180, + "src": { + "jump_code": "", + "length": 47, + "start": 3975 + } + }, + { + "ast_type": "If", + "children": [ + { + "ast_type": "Assign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 14, + "end_lineno": 183, + "lineno": 183, + "src": { + "jump_code": "", + "length": 6, + "start": 4088 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 23, + "end_lineno": 183, + "lineno": 183, + "src": { + "jump_code": "", + "length": 6, + "start": 4097 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 23, + "end_lineno": 183, + "lineno": 183, + "src": { + "jump_code": "", + "length": 15, + "start": 4088 + } + }, + { + "ast_type": "BoolOp", + "children": [ + { + "ast_type": "And", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 54, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 47, + "start": 4031 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 7, + "end_col_offset": 13, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 6, + "start": 4031 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 18, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 11, + "start": 4031 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 17, + "end_col_offset": 18, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 1, + "start": 4041 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 18, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 11, + "start": 4031 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 48, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 4, + "start": 4068 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 23, + "end_col_offset": 27, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 4, + "start": 4047 + } + } + ], + "classification": 0, + "col_offset": 23, + "end_col_offset": 33, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 10, + "start": 4047 + } + } + ], + "classification": 0, + "col_offset": 23, + "end_col_offset": 43, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 20, + "start": 4047 + } + } + ], + "classification": 0, + "col_offset": 23, + "end_col_offset": 49, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 26, + "start": 4047 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 23, + "end_col_offset": 54, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 31, + "start": 4047 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 53, + "end_col_offset": 54, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 1, + "start": 4077 + } + } + ], + "classification": 0, + "col_offset": 23, + "end_col_offset": 54, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 31, + "start": 4047 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 54, + "end_lineno": 182, + "lineno": 182, + "src": { + "jump_code": "", + "length": 47, + "start": 4031 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 23, + "end_lineno": 183, + "lineno": 182, + "src": { + "jump_code": "", + "length": 75, + "start": 4028 + } + }, + { + "ast_type": "Expr", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 28, + "end_col_offset": 31, + "end_lineno": 185, + "lineno": 185, + "src": { + "jump_code": "", + "length": 3, + "start": 4184 + } + } + ], + "classification": 0, + "col_offset": 28, + "end_col_offset": 38, + "end_lineno": 185, + "lineno": 185, + "src": { + "jump_code": "", + "length": 10, + "start": 4184 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 40, + "end_col_offset": 44, + "end_lineno": 185, + "lineno": 185, + "src": { + "jump_code": "", + "length": 4, + "start": 4196 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 46, + "end_col_offset": 52, + "end_lineno": 185, + "lineno": 185, + "src": { + "jump_code": "", + "length": 6, + "start": 4202 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 185, + "lineno": 185, + "src": { + "jump_code": "", + "length": 4, + "start": 4160 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 14, + "end_lineno": 185, + "lineno": 185, + "src": { + "jump_code": "", + "length": 10, + "start": 4160 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 27, + "end_lineno": 185, + "lineno": 185, + "src": { + "jump_code": "", + "length": 23, + "start": 4160 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 53, + "end_lineno": 185, + "lineno": 185, + "src": { + "jump_code": "", + "length": 49, + "start": 4160 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 53, + "end_lineno": 185, + "lineno": 185, + "src": { + "jump_code": "", + "length": 49, + "start": 4160 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Add", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 187, + "lineno": 187, + "src": { + "jump_code": "", + "length": 26, + "start": 4215 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 187, + "lineno": 187, + "src": { + "jump_code": "", + "length": 4, + "start": 4215 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 20, + "end_lineno": 187, + "lineno": 187, + "src": { + "jump_code": "", + "length": 16, + "start": 4215 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 30, + "end_lineno": 187, + "lineno": 187, + "src": { + "jump_code": "", + "length": 6, + "start": 4235 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 187, + "lineno": 187, + "src": { + "jump_code": "", + "length": 26, + "start": 4215 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Add", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 188, + "lineno": 188, + "src": { + "jump_code": "", + "length": 34, + "start": 4246 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 27, + "end_lineno": 188, + "lineno": 188, + "src": { + "jump_code": "", + "length": 8, + "start": 4261 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 188, + "lineno": 188, + "src": { + "jump_code": "", + "length": 24, + "start": 4246 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 188, + "lineno": 188, + "src": { + "jump_code": "", + "length": 4, + "start": 4246 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 188, + "lineno": 188, + "src": { + "jump_code": "", + "length": 14, + "start": 4246 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 28, + "end_lineno": 188, + "lineno": 188, + "src": { + "jump_code": "", + "length": 24, + "start": 4246 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 32, + "end_col_offset": 38, + "end_lineno": 188, + "lineno": 188, + "src": { + "jump_code": "", + "length": 6, + "start": 4274 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 38, + "end_lineno": 188, + "lineno": 188, + "src": { + "jump_code": "", + "length": 34, + "start": 4246 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 23, + "end_col_offset": 30, + "end_lineno": 189, + "lineno": 189, + "src": { + "jump_code": "", + "length": 7, + "start": 4304 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 22, + "end_lineno": 189, + "lineno": 189, + "src": { + "jump_code": "", + "length": 5, + "start": 4298 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 31, + "end_lineno": 189, + "lineno": 189, + "src": { + "jump_code": "", + "length": 14, + "start": 4298 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 33, + "end_col_offset": 41, + "end_lineno": 189, + "lineno": 189, + "src": { + "jump_code": "", + "length": 8, + "start": 4314 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 43, + "end_col_offset": 49, + "end_lineno": 189, + "lineno": 189, + "src": { + "jump_code": "", + "length": 6, + "start": 4324 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 189, + "lineno": 189, + "src": { + "jump_code": "", + "length": 8, + "start": 4289 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 50, + "end_lineno": 189, + "lineno": 189, + "src": { + "jump_code": "", + "length": 42, + "start": 4289 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 50, + "end_lineno": 189, + "lineno": 189, + "src": { + "jump_code": "", + "length": 46, + "start": 4285 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 16, + "end_col_offset": 19, + "end_lineno": 190, + "lineno": 190, + "src": { + "jump_code": "", + "length": 3, + "start": 4348 + } + } + ], + "classification": 0, + "col_offset": 16, + "end_col_offset": 26, + "end_lineno": 190, + "lineno": 190, + "src": { + "jump_code": "", + "length": 10, + "start": 4348 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 28, + "end_col_offset": 36, + "end_lineno": 190, + "lineno": 190, + "src": { + "jump_code": "", + "length": 8, + "start": 4360 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 38, + "end_col_offset": 44, + "end_lineno": 190, + "lineno": 190, + "src": { + "jump_code": "", + "length": 6, + "start": 4370 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 46, + "end_col_offset": 52, + "end_lineno": 190, + "lineno": 190, + "src": { + "jump_code": "", + "length": 6, + "start": 4378 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 15, + "end_lineno": 190, + "lineno": 190, + "src": { + "jump_code": "", + "length": 7, + "start": 4340 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 53, + "end_lineno": 190, + "lineno": 190, + "src": { + "jump_code": "", + "length": 45, + "start": 4340 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 53, + "end_lineno": 190, + "lineno": 190, + "src": { + "jump_code": "", + "length": 49, + "start": 4336 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 191, + "lineno": 191, + "src": { + "jump_code": "", + "length": 6, + "start": 4397 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 17, + "end_lineno": 191, + "lineno": 191, + "src": { + "jump_code": "", + "length": 13, + "start": 4390 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 178, + "lineno": 178, + "src": { + "jump_code": "", + "length": 8, + "start": 3894 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 59, + "end_col_offset": 66, + "end_lineno": 179, + "lineno": 179, + "src": { + "jump_code": "", + "length": 7, + "start": 3962 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 191, + "lineno": 179, + "name": "mint", + "src": { + "jump_code": "", + "length": 500, + "start": 3903 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 23, + "end_col_offset": 30, + "end_lineno": 196, + "lineno": 196, + "src": { + "jump_code": "", + "length": 7, + "start": 4445 + } + } + ], + "classification": 0, + "col_offset": 16, + "end_col_offset": 30, + "end_lineno": 196, + "lineno": 196, + "src": { + "jump_code": "", + "length": 14, + "start": 4438 + } + } + ], + "classification": 1, + "col_offset": 16, + "end_col_offset": 30, + "end_lineno": 196, + "lineno": 196, + "src": { + "jump_code": "", + "length": 14, + "start": 4438 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 22, + "end_lineno": 197, + "lineno": 197, + "src": { + "jump_code": "", + "length": 11, + "start": 4477 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 22, + "end_lineno": 197, + "lineno": 197, + "src": { + "jump_code": "", + "length": 18, + "start": 4470 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 194, + "lineno": 194, + "src": { + "jump_code": "", + "length": 4, + "start": 4407 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 195, + "lineno": 195, + "src": { + "jump_code": "", + "length": 8, + "start": 4413 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 35, + "end_col_offset": 42, + "end_lineno": 196, + "lineno": 196, + "src": { + "jump_code": "", + "length": 7, + "start": 4457 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 22, + "end_lineno": 197, + "lineno": 196, + "name": "maxWithdraw", + "src": { + "jump_code": "", + "length": 66, + "start": 4422 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 28, + "end_col_offset": 35, + "end_lineno": 202, + "lineno": 202, + "src": { + "jump_code": "", + "length": 7, + "start": 4579 + } + } + ], + "classification": 0, + "col_offset": 20, + "end_col_offset": 35, + "end_lineno": 202, + "lineno": 202, + "src": { + "jump_code": "", + "length": 15, + "start": 4571 + } + } + ], + "classification": 1, + "col_offset": 20, + "end_col_offset": 35, + "end_lineno": 202, + "lineno": 202, + "src": { + "jump_code": "", + "length": 15, + "start": 4571 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 203, + "lineno": 203, + "src": { + "jump_code": "", + "length": 7, + "start": 4612 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 203, + "lineno": 203, + "src": { + "jump_code": "", + "length": 6, + "start": 4604 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 50, + "end_lineno": 203, + "lineno": 203, + "src": { + "jump_code": "", + "length": 6, + "start": 4644 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 26, + "end_lineno": 203, + "lineno": 203, + "src": { + "jump_code": "", + "length": 4, + "start": 4622 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 43, + "end_lineno": 203, + "lineno": 203, + "src": { + "jump_code": "", + "length": 21, + "start": 4622 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 51, + "end_lineno": 203, + "lineno": 203, + "src": { + "jump_code": "", + "length": 29, + "start": 4622 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 51, + "end_lineno": 203, + "lineno": 203, + "src": { + "jump_code": "", + "length": 47, + "start": 4604 + } + }, + { + "ast_type": "If", + "children": [ + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 15, + "end_col_offset": 16, + "end_lineno": 207, + "lineno": 207, + "src": { + "jump_code": "", + "length": 1, + "start": 4798 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 207, + "lineno": 207, + "src": { + "jump_code": "", + "length": 8, + "start": 4791 + } + }, + { + "ast_type": "BoolOp", + "children": [ + { + "ast_type": "And", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 49, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 42, + "start": 4739 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 7, + "end_col_offset": 13, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 6, + "start": 4739 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 23, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 16, + "start": 4739 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 23, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 6, + "start": 4749 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 23, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 16, + "start": 4739 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 28, + "end_col_offset": 32, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 4, + "start": 4760 + } + } + ], + "classification": 0, + "col_offset": 28, + "end_col_offset": 44, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 16, + "start": 4760 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 28, + "end_col_offset": 49, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 21, + "start": 4760 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 48, + "end_col_offset": 49, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 1, + "start": 4780 + } + } + ], + "classification": 0, + "col_offset": 28, + "end_col_offset": 49, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 21, + "start": 4760 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 49, + "end_lineno": 206, + "lineno": 206, + "src": { + "jump_code": "", + "length": 42, + "start": 4739 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 16, + "end_lineno": 207, + "lineno": 206, + "src": { + "jump_code": "", + "length": 63, + "start": 4736 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 209, + "lineno": 209, + "src": { + "jump_code": "", + "length": 6, + "start": 4839 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 17, + "end_lineno": 209, + "lineno": 209, + "src": { + "jump_code": "", + "length": 13, + "start": 4832 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 200, + "lineno": 200, + "src": { + "jump_code": "", + "length": 4, + "start": 4536 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 201, + "lineno": 201, + "src": { + "jump_code": "", + "length": 8, + "start": 4542 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 40, + "end_col_offset": 47, + "end_lineno": 202, + "lineno": 202, + "src": { + "jump_code": "", + "length": 7, + "start": 4591 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 209, + "lineno": 202, + "name": "previewWithdraw", + "src": { + "jump_code": "", + "length": 294, + "start": 4551 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 21, + "end_col_offset": 28, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 7, + "start": 4879 + } + } + ], + "classification": 0, + "col_offset": 13, + "end_col_offset": 28, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 15, + "start": 4871 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 40, + "end_col_offset": 47, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 7, + "start": 4898 + } + } + ], + "classification": 0, + "col_offset": 30, + "end_col_offset": 47, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 17, + "start": 4888 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 67, + "end_col_offset": 74, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 7, + "start": 4925 + } + } + ], + "classification": 0, + "col_offset": 60, + "end_col_offset": 74, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 14, + "start": 4918 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 48, + "end_col_offset": 51, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 3, + "start": 4906 + } + } + ], + "classification": 0, + "col_offset": 48, + "end_col_offset": 58, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 10, + "start": 4906 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 75, + "end_col_offset": 78, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 3, + "start": 4933 + } + } + ], + "classification": 0, + "col_offset": 75, + "end_col_offset": 85, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 10, + "start": 4933 + } + } + ], + "classification": 1, + "col_offset": 13, + "end_col_offset": 85, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 72, + "start": 4871 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 214, + "lineno": 214, + "src": { + "jump_code": "", + "length": 7, + "start": 4969 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 214, + "lineno": 214, + "src": { + "jump_code": "", + "length": 6, + "start": 4961 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 50, + "end_lineno": 214, + "lineno": 214, + "src": { + "jump_code": "", + "length": 6, + "start": 5001 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 26, + "end_lineno": 214, + "lineno": 214, + "src": { + "jump_code": "", + "length": 4, + "start": 4979 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 43, + "end_lineno": 214, + "lineno": 214, + "src": { + "jump_code": "", + "length": 21, + "start": 4979 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 51, + "end_lineno": 214, + "lineno": 214, + "src": { + "jump_code": "", + "length": 29, + "start": 4979 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 51, + "end_lineno": 214, + "lineno": 214, + "src": { + "jump_code": "", + "length": 47, + "start": 4961 + } + }, + { + "ast_type": "If", + "children": [ + { + "ast_type": "Raise", + "children": [], + "classification": 0, + "col_offset": 8, + "end_col_offset": 13, + "end_lineno": 218, + "lineno": 218, + "src": { + "jump_code": "", + "length": 5, + "start": 5148 + } + }, + { + "ast_type": "BoolOp", + "children": [ + { + "ast_type": "And", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 49, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 42, + "start": 5096 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 7, + "end_col_offset": 13, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 6, + "start": 5096 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 23, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 16, + "start": 5096 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 23, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 6, + "start": 5106 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 23, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 16, + "start": 5096 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 28, + "end_col_offset": 32, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 4, + "start": 5117 + } + } + ], + "classification": 0, + "col_offset": 28, + "end_col_offset": 44, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 16, + "start": 5117 + } + }, + { + "ast_type": "Eq", + "children": [], + "classification": 0, + "col_offset": 28, + "end_col_offset": 49, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 21, + "start": 5117 + } + }, + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 48, + "end_col_offset": 49, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 1, + "start": 5137 + } + } + ], + "classification": 0, + "col_offset": 28, + "end_col_offset": 49, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 21, + "start": 5117 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 49, + "end_lineno": 217, + "lineno": 217, + "src": { + "jump_code": "", + "length": 42, + "start": 5096 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 13, + "end_lineno": 218, + "lineno": 217, + "src": { + "jump_code": "", + "length": 60, + "start": 5093 + } + }, + { + "ast_type": "If", + "children": [ + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 8, + "end_col_offset": 51, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 43, + "start": 5212 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 30, + "end_col_offset": 33, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 3, + "start": 5234 + } + } + ], + "classification": 0, + "col_offset": 30, + "end_col_offset": 40, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 10, + "start": 5234 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 41, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 33, + "start": 5212 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 23, + "end_col_offset": 28, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 5, + "start": 5227 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 29, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 21, + "start": 5212 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 12, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 4, + "start": 5212 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 22, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 14, + "start": 5212 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 29, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 21, + "start": 5212 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 41, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 33, + "start": 5212 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 45, + "end_col_offset": 51, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 6, + "start": 5249 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 51, + "end_lineno": 221, + "lineno": 221, + "src": { + "jump_code": "", + "length": 43, + "start": 5212 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 7, + "end_col_offset": 12, + "end_lineno": 220, + "lineno": 220, + "src": { + "jump_code": "", + "length": 5, + "start": 5183 + } + }, + { + "ast_type": "NotEq", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 26, + "end_lineno": 220, + "lineno": 220, + "src": { + "jump_code": "", + "length": 19, + "start": 5183 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 16, + "end_col_offset": 19, + "end_lineno": 220, + "lineno": 220, + "src": { + "jump_code": "", + "length": 3, + "start": 5192 + } + } + ], + "classification": 0, + "col_offset": 16, + "end_col_offset": 26, + "end_lineno": 220, + "lineno": 220, + "src": { + "jump_code": "", + "length": 10, + "start": 5192 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 26, + "end_lineno": 220, + "lineno": 220, + "src": { + "jump_code": "", + "length": 19, + "start": 5183 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 51, + "end_lineno": 221, + "lineno": 220, + "src": { + "jump_code": "", + "length": 75, + "start": 5180 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 223, + "lineno": 223, + "src": { + "jump_code": "", + "length": 26, + "start": 5261 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 223, + "lineno": 223, + "src": { + "jump_code": "", + "length": 4, + "start": 5261 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 20, + "end_lineno": 223, + "lineno": 223, + "src": { + "jump_code": "", + "length": 16, + "start": 5261 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 30, + "end_lineno": 223, + "lineno": 223, + "src": { + "jump_code": "", + "length": 6, + "start": 5281 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 223, + "lineno": 223, + "src": { + "jump_code": "", + "length": 26, + "start": 5261 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 35, + "end_lineno": 224, + "lineno": 224, + "src": { + "jump_code": "", + "length": 31, + "start": 5292 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 24, + "end_lineno": 224, + "lineno": 224, + "src": { + "jump_code": "", + "length": 5, + "start": 5307 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 25, + "end_lineno": 224, + "lineno": 224, + "src": { + "jump_code": "", + "length": 21, + "start": 5292 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 224, + "lineno": 224, + "src": { + "jump_code": "", + "length": 4, + "start": 5292 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 224, + "lineno": 224, + "src": { + "jump_code": "", + "length": 14, + "start": 5292 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 25, + "end_lineno": 224, + "lineno": 224, + "src": { + "jump_code": "", + "length": 21, + "start": 5292 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 29, + "end_col_offset": 35, + "end_lineno": 224, + "lineno": 224, + "src": { + "jump_code": "", + "length": 6, + "start": 5317 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 35, + "end_lineno": 224, + "lineno": 224, + "src": { + "jump_code": "", + "length": 31, + "start": 5292 + } + }, + { + "ast_type": "Expr", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 32, + "end_lineno": 226, + "lineno": 226, + "src": { + "jump_code": "", + "length": 8, + "start": 5349 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 34, + "end_col_offset": 40, + "end_lineno": 226, + "lineno": 226, + "src": { + "jump_code": "", + "length": 6, + "start": 5359 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 226, + "lineno": 226, + "src": { + "jump_code": "", + "length": 4, + "start": 5329 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 14, + "end_lineno": 226, + "lineno": 226, + "src": { + "jump_code": "", + "length": 10, + "start": 5329 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 23, + "end_lineno": 226, + "lineno": 226, + "src": { + "jump_code": "", + "length": 19, + "start": 5329 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 41, + "end_lineno": 226, + "lineno": 226, + "src": { + "jump_code": "", + "length": 37, + "start": 5329 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 41, + "end_lineno": 226, + "lineno": 226, + "src": { + "jump_code": "", + "length": 37, + "start": 5329 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 22, + "end_lineno": 227, + "lineno": 227, + "src": { + "jump_code": "", + "length": 5, + "start": 5384 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 30, + "end_col_offset": 37, + "end_lineno": 227, + "lineno": 227, + "src": { + "jump_code": "", + "length": 7, + "start": 5397 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 29, + "end_lineno": 227, + "lineno": 227, + "src": { + "jump_code": "", + "length": 5, + "start": 5391 + } + } + ], + "classification": 0, + "col_offset": 24, + "end_col_offset": 38, + "end_lineno": 227, + "lineno": 227, + "src": { + "jump_code": "", + "length": 14, + "start": 5391 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 40, + "end_col_offset": 46, + "end_lineno": 227, + "lineno": 227, + "src": { + "jump_code": "", + "length": 6, + "start": 5407 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 227, + "lineno": 227, + "src": { + "jump_code": "", + "length": 8, + "start": 5375 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 47, + "end_lineno": 227, + "lineno": 227, + "src": { + "jump_code": "", + "length": 39, + "start": 5375 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 47, + "end_lineno": 227, + "lineno": 227, + "src": { + "jump_code": "", + "length": 43, + "start": 5371 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 20, + "end_lineno": 228, + "lineno": 228, + "src": { + "jump_code": "", + "length": 3, + "start": 5432 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 27, + "end_lineno": 228, + "lineno": 228, + "src": { + "jump_code": "", + "length": 10, + "start": 5432 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 29, + "end_col_offset": 37, + "end_lineno": 228, + "lineno": 228, + "src": { + "jump_code": "", + "length": 8, + "start": 5444 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 39, + "end_col_offset": 44, + "end_lineno": 228, + "lineno": 228, + "src": { + "jump_code": "", + "length": 5, + "start": 5454 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 46, + "end_col_offset": 52, + "end_lineno": 228, + "lineno": 228, + "src": { + "jump_code": "", + "length": 6, + "start": 5461 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 54, + "end_col_offset": 60, + "end_lineno": 228, + "lineno": 228, + "src": { + "jump_code": "", + "length": 6, + "start": 5469 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 228, + "lineno": 228, + "src": { + "jump_code": "", + "length": 8, + "start": 5423 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 61, + "end_lineno": 228, + "lineno": 228, + "src": { + "jump_code": "", + "length": 53, + "start": 5423 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 61, + "end_lineno": 228, + "lineno": 228, + "src": { + "jump_code": "", + "length": 57, + "start": 5419 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 229, + "lineno": 229, + "src": { + "jump_code": "", + "length": 6, + "start": 5488 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 17, + "end_lineno": 229, + "lineno": 229, + "src": { + "jump_code": "", + "length": 13, + "start": 5481 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 212, + "lineno": 212, + "src": { + "jump_code": "", + "length": 8, + "start": 4849 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 90, + "end_col_offset": 97, + "end_lineno": 213, + "lineno": 213, + "src": { + "jump_code": "", + "length": 7, + "start": 4948 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 229, + "lineno": 213, + "name": "withdraw", + "src": { + "jump_code": "", + "length": 636, + "start": 4858 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 21, + "end_col_offset": 28, + "end_lineno": 234, + "lineno": 234, + "src": { + "jump_code": "", + "length": 7, + "start": 5534 + } + } + ], + "classification": 0, + "col_offset": 14, + "end_col_offset": 28, + "end_lineno": 234, + "lineno": 234, + "src": { + "jump_code": "", + "length": 14, + "start": 5527 + } + } + ], + "classification": 1, + "col_offset": 14, + "end_col_offset": 28, + "end_lineno": 234, + "lineno": 234, + "src": { + "jump_code": "", + "length": 14, + "start": 5527 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 22, + "end_lineno": 235, + "lineno": 235, + "src": { + "jump_code": "", + "length": 11, + "start": 5566 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 22, + "end_lineno": 235, + "lineno": 235, + "src": { + "jump_code": "", + "length": 18, + "start": 5559 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 232, + "lineno": 232, + "src": { + "jump_code": "", + "length": 4, + "start": 5498 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 233, + "lineno": 233, + "src": { + "jump_code": "", + "length": 8, + "start": 5504 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 33, + "end_col_offset": 40, + "end_lineno": 234, + "lineno": 234, + "src": { + "jump_code": "", + "length": 7, + "start": 5546 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 22, + "end_lineno": 235, + "lineno": 234, + "name": "maxRedeem", + "src": { + "jump_code": "", + "length": 64, + "start": 5513 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 26, + "end_col_offset": 33, + "end_lineno": 240, + "lineno": 240, + "src": { + "jump_code": "", + "length": 7, + "start": 5656 + } + } + ], + "classification": 0, + "col_offset": 18, + "end_col_offset": 33, + "end_lineno": 240, + "lineno": 240, + "src": { + "jump_code": "", + "length": 15, + "start": 5648 + } + } + ], + "classification": 1, + "col_offset": 18, + "end_col_offset": 33, + "end_lineno": 240, + "lineno": 240, + "src": { + "jump_code": "", + "length": 15, + "start": 5648 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 33, + "end_col_offset": 39, + "end_lineno": 241, + "lineno": 241, + "src": { + "jump_code": "", + "length": 6, + "start": 5710 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 15, + "end_lineno": 241, + "lineno": 241, + "src": { + "jump_code": "", + "length": 4, + "start": 5688 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 32, + "end_lineno": 241, + "lineno": 241, + "src": { + "jump_code": "", + "length": 21, + "start": 5688 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 40, + "end_lineno": 241, + "lineno": 241, + "src": { + "jump_code": "", + "length": 29, + "start": 5688 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 40, + "end_lineno": 241, + "lineno": 241, + "src": { + "jump_code": "", + "length": 36, + "start": 5681 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 5, + "end_lineno": 238, + "lineno": 238, + "src": { + "jump_code": "", + "length": 4, + "start": 5615 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 239, + "lineno": 239, + "src": { + "jump_code": "", + "length": 8, + "start": 5621 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 38, + "end_col_offset": 45, + "end_lineno": 240, + "lineno": 240, + "src": { + "jump_code": "", + "length": 7, + "start": 5668 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 40, + "end_lineno": 241, + "lineno": 240, + "name": "previewRedeem", + "src": { + "jump_code": "", + "length": 87, + "start": 5630 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 26, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 7, + "start": 5749 + } + } + ], + "classification": 0, + "col_offset": 11, + "end_col_offset": 26, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 15, + "start": 5741 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 38, + "end_col_offset": 45, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 7, + "start": 5768 + } + } + ], + "classification": 0, + "col_offset": 28, + "end_col_offset": 45, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 17, + "start": 5758 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 65, + "end_col_offset": 72, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 7, + "start": 5795 + } + } + ], + "classification": 0, + "col_offset": 58, + "end_col_offset": 72, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 14, + "start": 5788 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 46, + "end_col_offset": 49, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 3, + "start": 5776 + } + } + ], + "classification": 0, + "col_offset": 46, + "end_col_offset": 56, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 10, + "start": 5776 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 73, + "end_col_offset": 76, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 3, + "start": 5803 + } + } + ], + "classification": 0, + "col_offset": 73, + "end_col_offset": 83, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 10, + "start": 5803 + } + } + ], + "classification": 1, + "col_offset": 11, + "end_col_offset": 83, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 72, + "start": 5741 + } + }, + { + "ast_type": "If", + "children": [ + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 8, + "end_col_offset": 51, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 43, + "start": 5863 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 30, + "end_col_offset": 33, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 3, + "start": 5885 + } + } + ], + "classification": 0, + "col_offset": 30, + "end_col_offset": 40, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 10, + "start": 5885 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 41, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 33, + "start": 5863 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 23, + "end_col_offset": 28, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 5, + "start": 5878 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 29, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 21, + "start": 5863 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 12, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 4, + "start": 5863 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 22, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 14, + "start": 5863 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 29, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 21, + "start": 5863 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 41, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 33, + "start": 5863 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 45, + "end_col_offset": 51, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 6, + "start": 5900 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 51, + "end_lineno": 247, + "lineno": 247, + "src": { + "jump_code": "", + "length": 43, + "start": 5863 + } + }, + { + "ast_type": "Compare", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 7, + "end_col_offset": 12, + "end_lineno": 246, + "lineno": 246, + "src": { + "jump_code": "", + "length": 5, + "start": 5834 + } + }, + { + "ast_type": "NotEq", + "children": [], + "classification": 0, + "col_offset": 7, + "end_col_offset": 26, + "end_lineno": 246, + "lineno": 246, + "src": { + "jump_code": "", + "length": 19, + "start": 5834 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 16, + "end_col_offset": 19, + "end_lineno": 246, + "lineno": 246, + "src": { + "jump_code": "", + "length": 3, + "start": 5843 + } + } + ], + "classification": 0, + "col_offset": 16, + "end_col_offset": 26, + "end_lineno": 246, + "lineno": 246, + "src": { + "jump_code": "", + "length": 10, + "start": 5843 + } + } + ], + "classification": 0, + "col_offset": 7, + "end_col_offset": 26, + "end_lineno": 246, + "lineno": 246, + "src": { + "jump_code": "", + "length": 19, + "start": 5834 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 51, + "end_lineno": 247, + "lineno": 246, + "src": { + "jump_code": "", + "length": 75, + "start": 5831 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 249, + "lineno": 249, + "src": { + "jump_code": "", + "length": 7, + "start": 5920 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 249, + "lineno": 249, + "src": { + "jump_code": "", + "length": 6, + "start": 5912 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 50, + "end_lineno": 249, + "lineno": 249, + "src": { + "jump_code": "", + "length": 6, + "start": 5952 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 26, + "end_lineno": 249, + "lineno": 249, + "src": { + "jump_code": "", + "length": 4, + "start": 5930 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 43, + "end_lineno": 249, + "lineno": 249, + "src": { + "jump_code": "", + "length": 21, + "start": 5930 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 51, + "end_lineno": 249, + "lineno": 249, + "src": { + "jump_code": "", + "length": 29, + "start": 5930 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 51, + "end_lineno": 249, + "lineno": 249, + "src": { + "jump_code": "", + "length": 47, + "start": 5912 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 250, + "lineno": 250, + "src": { + "jump_code": "", + "length": 26, + "start": 5964 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 250, + "lineno": 250, + "src": { + "jump_code": "", + "length": 4, + "start": 5964 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 20, + "end_lineno": 250, + "lineno": 250, + "src": { + "jump_code": "", + "length": 16, + "start": 5964 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 30, + "end_lineno": 250, + "lineno": 250, + "src": { + "jump_code": "", + "length": 6, + "start": 5984 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 30, + "end_lineno": 250, + "lineno": 250, + "src": { + "jump_code": "", + "length": 26, + "start": 5964 + } + }, + { + "ast_type": "AugAssign", + "children": [ + { + "ast_type": "Sub", + "children": [], + "classification": 0, + "col_offset": 4, + "end_col_offset": 35, + "end_lineno": 251, + "lineno": 251, + "src": { + "jump_code": "", + "length": 31, + "start": 5995 + } + }, + { + "ast_type": "Subscript", + "children": [ + { + "ast_type": "Index", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 24, + "end_lineno": 251, + "lineno": 251, + "src": { + "jump_code": "", + "length": 5, + "start": 6010 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 25, + "end_lineno": 251, + "lineno": 251, + "src": { + "jump_code": "", + "length": 21, + "start": 5995 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 251, + "lineno": 251, + "src": { + "jump_code": "", + "length": 4, + "start": 5995 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 18, + "end_lineno": 251, + "lineno": 251, + "src": { + "jump_code": "", + "length": 14, + "start": 5995 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 25, + "end_lineno": 251, + "lineno": 251, + "src": { + "jump_code": "", + "length": 21, + "start": 5995 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 29, + "end_col_offset": 35, + "end_lineno": 251, + "lineno": 251, + "src": { + "jump_code": "", + "length": 6, + "start": 6020 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 35, + "end_lineno": 251, + "lineno": 251, + "src": { + "jump_code": "", + "length": 31, + "start": 5995 + } + }, + { + "ast_type": "Expr", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 32, + "end_lineno": 253, + "lineno": 253, + "src": { + "jump_code": "", + "length": 8, + "start": 6052 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 34, + "end_col_offset": 40, + "end_lineno": 253, + "lineno": 253, + "src": { + "jump_code": "", + "length": 6, + "start": 6062 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 253, + "lineno": 253, + "src": { + "jump_code": "", + "length": 4, + "start": 6032 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 14, + "end_lineno": 253, + "lineno": 253, + "src": { + "jump_code": "", + "length": 10, + "start": 6032 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 23, + "end_lineno": 253, + "lineno": 253, + "src": { + "jump_code": "", + "length": 19, + "start": 6032 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 41, + "end_lineno": 253, + "lineno": 253, + "src": { + "jump_code": "", + "length": 37, + "start": 6032 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 41, + "end_lineno": 253, + "lineno": 253, + "src": { + "jump_code": "", + "length": 37, + "start": 6032 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 22, + "end_lineno": 254, + "lineno": 254, + "src": { + "jump_code": "", + "length": 5, + "start": 6087 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 30, + "end_col_offset": 37, + "end_lineno": 254, + "lineno": 254, + "src": { + "jump_code": "", + "length": 7, + "start": 6100 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 29, + "end_lineno": 254, + "lineno": 254, + "src": { + "jump_code": "", + "length": 5, + "start": 6094 + } + } + ], + "classification": 0, + "col_offset": 24, + "end_col_offset": 38, + "end_lineno": 254, + "lineno": 254, + "src": { + "jump_code": "", + "length": 14, + "start": 6094 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 40, + "end_col_offset": 46, + "end_lineno": 254, + "lineno": 254, + "src": { + "jump_code": "", + "length": 6, + "start": 6110 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 254, + "lineno": 254, + "src": { + "jump_code": "", + "length": 8, + "start": 6078 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 47, + "end_lineno": 254, + "lineno": 254, + "src": { + "jump_code": "", + "length": 39, + "start": 6078 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 47, + "end_lineno": 254, + "lineno": 254, + "src": { + "jump_code": "", + "length": 43, + "start": 6074 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 17, + "end_col_offset": 20, + "end_lineno": 255, + "lineno": 255, + "src": { + "jump_code": "", + "length": 3, + "start": 6135 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 27, + "end_lineno": 255, + "lineno": 255, + "src": { + "jump_code": "", + "length": 10, + "start": 6135 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 29, + "end_col_offset": 37, + "end_lineno": 255, + "lineno": 255, + "src": { + "jump_code": "", + "length": 8, + "start": 6147 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 39, + "end_col_offset": 44, + "end_lineno": 255, + "lineno": 255, + "src": { + "jump_code": "", + "length": 5, + "start": 6157 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 46, + "end_col_offset": 52, + "end_lineno": 255, + "lineno": 255, + "src": { + "jump_code": "", + "length": 6, + "start": 6164 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 54, + "end_col_offset": 60, + "end_lineno": 255, + "lineno": 255, + "src": { + "jump_code": "", + "length": 6, + "start": 6172 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 16, + "end_lineno": 255, + "lineno": 255, + "src": { + "jump_code": "", + "length": 8, + "start": 6126 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 61, + "end_lineno": 255, + "lineno": 255, + "src": { + "jump_code": "", + "length": 53, + "start": 6126 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 61, + "end_lineno": 255, + "lineno": 255, + "src": { + "jump_code": "", + "length": 57, + "start": 6122 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 256, + "lineno": 256, + "src": { + "jump_code": "", + "length": 6, + "start": 6191 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 17, + "end_lineno": 256, + "lineno": 256, + "src": { + "jump_code": "", + "length": 13, + "start": 6184 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 244, + "lineno": 244, + "src": { + "jump_code": "", + "length": 8, + "start": 5721 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 88, + "end_col_offset": 95, + "end_lineno": 245, + "lineno": 245, + "src": { + "jump_code": "", + "length": 7, + "start": 5818 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 256, + "lineno": 245, + "name": "redeem", + "src": { + "jump_code": "", + "length": 467, + "start": 5730 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 31, + "end_col_offset": 38, + "end_lineno": 260, + "lineno": 260, + "src": { + "jump_code": "", + "length": 7, + "start": 6241 + } + } + ], + "classification": 0, + "col_offset": 23, + "end_col_offset": 38, + "end_lineno": 260, + "lineno": 260, + "src": { + "jump_code": "", + "length": 15, + "start": 6233 + } + } + ], + "classification": 1, + "col_offset": 23, + "end_col_offset": 38, + "end_lineno": 260, + "lineno": 260, + "src": { + "jump_code": "", + "length": 15, + "start": 6233 + } + }, + { + "ast_type": "Expr", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 24, + "end_col_offset": 27, + "end_lineno": 262, + "lineno": 262, + "src": { + "jump_code": "", + "length": 3, + "start": 6345 + } + } + ], + "classification": 0, + "col_offset": 24, + "end_col_offset": 34, + "end_lineno": 262, + "lineno": 262, + "src": { + "jump_code": "", + "length": 10, + "start": 6345 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 36, + "end_col_offset": 42, + "end_lineno": 262, + "lineno": 262, + "src": { + "jump_code": "", + "length": 6, + "start": 6357 + } + }, + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Attribute", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 8, + "end_lineno": 262, + "lineno": 262, + "src": { + "jump_code": "", + "length": 4, + "start": 6325 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 14, + "end_lineno": 262, + "lineno": 262, + "src": { + "jump_code": "", + "length": 10, + "start": 6325 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 23, + "end_lineno": 262, + "lineno": 262, + "src": { + "jump_code": "", + "length": 19, + "start": 6325 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 43, + "end_lineno": 262, + "lineno": 262, + "src": { + "jump_code": "", + "length": 39, + "start": 6325 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 43, + "end_lineno": 262, + "lineno": 262, + "src": { + "jump_code": "", + "length": 39, + "start": 6325 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 259, + "lineno": 259, + "src": { + "jump_code": "", + "length": 8, + "start": 6201 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 43, + "end_lineno": 262, + "lineno": 260, + "name": "DEBUG_steal_tokens", + "src": { + "jump_code": "", + "length": 154, + "start": 6210 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 43, + "end_lineno": 262, + "lineno": 1, + "name": "VyperVault.vy", + "src": { + "jump_code": "", + "length": 6364 + } + }, + "contractName": "VyperVault", + "deploymentBytecode": { + "bytecode": "0x6020610f505f395f518060a01c610f4c5760405234610f4c57604051600355610f1b61003061000039610f1b610000f36003361161000c57610e03565b5f3560e01c34610f0a576318160ddd811861002c575f5460405260206040f35b6370a0823181186100655760243610610f0a576004358060a01c610f0a5760405260016040516020525f5260405f205460605260206060f35b63dd62ed3e81186100bb5760443610610f0a576004358060a01c610f0a576040526024358060a01c610f0a5760605260026040516020525f5260405f20806060516020525f5260405f2090505460805260206080f35b6338d52e0f81186100d25760035460405260206040f35b6306fdde03811861015157602080608052600a6040527f54657374205661756c740000000000000000000000000000000000000000000060605260408160800181516020830160208301815181525050808252508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506080f35b6395d89b4181186101d05760208060805260056040527f765445535400000000000000000000000000000000000000000000000000000060605260408160800181516020830160208301815181525050808252508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506080f35b63313ce56781186101e657601260405260206040f35b63a9059cbb81186102885760443610610f0a576004358060a01c610f0a576040526001336020525f5260405f208054602435808203828111610f0a579050905081555060016040516020525f5260405f208054602435808201828110610f0a5790509050815550604051337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60243560605260206060a3600160605260206060f35b63095ea7b381186103035760443610610f0a576004358060a01c610f0a576040526024356002336020525f5260405f20806040516020525f5260405f20905055604051337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560243560605260206060a3600160605260206060f35b6323b872dd81186103e85760643610610f0a576004358060a01c610f0a576040526024358060a01c610f0a5760605260026040516020525f5260405f2080336020525f5260405f2090508054604435808203828111610f0a579050905081555060016040516020525f5260405f208054604435808203828111610f0a579050905081555060016060516020525f5260405f208054604435808201828110610f0a57905090508155506060516040517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60443560805260206080a3600160805260206080f35b6301e1d11481186104295760206003546370a0823160405230606052602060406024605c845afa61041b573d5f5f3e3d5ffd5b60203d10610f0a5760409050f35b6307a2d13a81186104525760243610610f0a57602060043560405261044e60c0610e07565b60c0f35b63c6e6f592811861047b5760243610610f0a57602060043560405261047760e0610e7c565b60e0f35b63402d267d81186104c65760243610610f0a576004358060a01c610f0a576040527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60605260206060f35b63ef8b30f781186104ef5760243610610f0a5760206004356040526104eb60e0610e7c565b60e0f35b63b6b55f25811861050b5760243610610f0a573360e05261052d565b636e553f6581186106495760443610610f0a576024358060a01c610f0a5760e0525b60043560405261053e610120610e7c565b61012051610100526003546323b872dd6101205233610140523061016052600435610180526020610120606461013c5f855af161057d573d5f5f3e3d5ffd5b60203d10610f0a57610120518060011c610f0a576101a0526101a050505f5461010051808201828110610f0a57905090505f55600160e0516020525f5260405f20805461010051808201828110610f0a579050905081555060e0515f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef61010051610120526020610120a360e051337fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d76004356101205261010051610140526040610120a36020610100f35b63c63d75b681186106945760243610610f0a576004358060a01c610f0a576040527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60605260206060f35b63b3d7f6b9811861071d5760243610610f0a576004356040526106b760e0610e07565b60e05160c05260c0516106fe576003546370a0823160e0523061010052602060e0602460fc845afa6106eb573d5f5f3e3d5ffd5b60203d10610f0a5760e090505115610700565b5f5b156107165760043561012052602061012061071b565b602060c05bf35b63a0712d6881186107395760243610610f0a573360c05261075b565b6394bf804d81186108c45760443610610f0a576024358060a01c610f0a5760c0525b60043560405261076c610100610e07565b6101005160e05260e0516107b8576003546370a082316101005230610120526020610100602461011c845afa6107a4573d5f5f3e3d5ffd5b60203d10610f0a57610100905051156107ba565b5f5b156107c65760043560e0525b6003546323b872dd610100523361012052306101405260e051610160526020610100606461011c5f855af16107fd573d5f5f3e3d5ffd5b60203d10610f0a57610100518060011c610f0a576101805261018050505f54600435808201828110610f0a57905090505f55600160c0516020525f5260405f208054600435808201828110610f0a579050905081555060c0515f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef600435610100526020610100a360c051337fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d760e05161010052600435610120526040610100a3602060e0f35b63ce96cb77811861090f5760243610610f0a576004358060a01c610f0a576040527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60605260206060f35b630a28a477811861096a5760243610610f0a57600435604052610933610100610e7c565b6101005160e05260043560e0511861094d575f541561094f565b5f5b15610963575f610100526020610100610968565b602060e05bf35b632e1a7d4d811861098b5760243610610f0a573360e05233610100526109e6565b62f714ce81186109b55760443610610f0a576024358060a01c610f0a5760e05233610100526109e6565b63b460af948118610b625760643610610f0a576024358060a01c610f0a5760e0526044358060a01c610f0a57610100525b6004356040526109f7610140610e7c565b61014051610120526004356101205118610a13575f5415610a15565b5f5b15610a1e575f5ffd5b336101005114610a5c576002610100516020525f5260405f2080336020525f5260405f209050805461012051808203828111610f0a57905090508155505b5f5461012051808203828111610f0a57905090505f556001610100516020525f5260405f20805461012051808203828111610f0a579050905081555060035463a9059cbb6101405260e05161016052600435610180526020610140604461015c5f855af1610acc573d5f5f3e3d5ffd5b60203d10610f0a57610140518060011c610f0a576101a0526101a050505f610100517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef61012051610140526020610140a36101005160e051337ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db6004356101405261012051610160526040610140a46020610120f35b63d905777e8118610bad5760243610610f0a576004358060a01c610f0a576040527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60605260206060f35b634cdad5068118610bd65760243610610f0a576020600435604052610bd260c0610e07565b60c0f35b63db006a758118610bf65760243610610f0a573360c0523360e052610c50565b637bde82f28118610c205760443610610f0a576024358060a01c610f0a5760c0523360e052610c50565b63ba0876528118610da55760643610610f0a576024358060a01c610f0a5760c0526044358060a01c610f0a5760e0525b3360e05114610c8b57600260e0516020525f5260405f2080336020525f5260405f2090508054600435808203828111610f0a57905090508155505b600435604052610c9c610120610e07565b61012051610100525f54600435808203828111610f0a57905090505f55600160e0516020525f5260405f208054600435808203828111610f0a579050905081555060035463a9059cbb6101205260c0516101405261010051610160526020610120604461013c5f855af1610d12573d5f5f3e3d5ffd5b60203d10610f0a57610120518060011c610f0a576101805261018050505f60e0517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef600435610120526020610120a360e05160c051337ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db6101005161012052600435610140526040610120a46020610100f35b63f1dc5be48118610e015760243610610f0a5760035463a9059cbb60405233606052600435608052602060406044605c5f855af1610de5573d5f5f3e3d5ffd5b60203d10610f0a576040518060011c610f0a5760a05260a05050005b505b5f5ffd5b5f54606052606051610e1c575f815250610e7a565b6040516003546370a082316080523060a052602060806024609c845afa610e45573d5f5f3e3d5ffd5b60203d10610f0a576080905051808202811583838304141715610f0a57905090506060518015610f0a57808204905090508152505b565b5f546060526003546370a0823160a0523060c052602060a0602460bc845afa610ea7573d5f5f3e3d5ffd5b60203d10610f0a5760a0905051608052608051610ec5576001610eca565b606051155b15610eda57604051815250610f08565b604051606051808202811583838304141715610f0a57905090506080518015610f0a57808204905090508152505b565b5f80fda165767970657283000309000b005b5f80fd" + }, + "dev_messages": {}, + "devdoc": {}, + "pcmap": { + "100": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "1000": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "1001": { + "location": [ + 96, + 0, + 97, + 37 + ] + }, + "1006": { + "location": [ + 96, + 0, + 97, + 37 + ] + }, + "1007": { + "location": [ + 96, + 0, + 97, + 37 + ] + }, + "1008": { + "location": [ + 96, + 0, + 97, + 37 + ] + }, + "101": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "1011": { + "location": [ + 96, + 0, + 97, + 37 + ] + }, + "1012": { + "location": [ + 96, + 0, + 97, + 37 + ] + }, + "1014": { + "location": [ + 97, + 11, + 97, + 21 + ] + }, + "1016": { + "location": [ + 97, + 11, + 97, + 37 + ] + }, + "102": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1025": { + "location": [ + 97, + 32, + 97, + 36 + ] + }, + "1037": { + "location": [ + 97, + 11, + 97, + 37 + ] + }, + "1050": { + "dev": "dev: EXTERNAL_CALL_FAILED", + "location": null + }, + "1056": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "1059": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "1062": { + "location": [ + 97, + 11, + 97, + 37 + ] + }, + "1063": { + "location": [ + 97, + 11, + 97, + 37 + ] + }, + "1064": { + "location": [ + 96, + 0, + 97, + 37 + ] + }, + "1065": { + "location": [ + 96, + 0, + 97, + 37 + ] + }, + "1066": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "107": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1071": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "1072": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "1073": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "1076": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "1077": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "1079": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "108": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1081": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "1084": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "1085": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "1087": { + "location": [ + 115, + 33, + 115, + 44 + ] + }, + "109": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1092": { + "location": [ + 115, + 11, + 115, + 45 + ] + }, + "1096": { + "location": [ + 115, + 11, + 115, + 45 + ] + }, + "1098": { + "location": [ + 115, + 11, + 115, + 45 + ] + }, + "1101": { + "location": [ + 115, + 11, + 115, + 45 + ] + }, + "1102": { + "location": [ + 115, + 11, + 115, + 45 + ] + }, + "1103": { + "location": [ + 115, + 11, + 115, + 45 + ] + }, + "1105": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "1106": { + "location": [ + 114, + 0, + 115, + 45 + ] + }, + "1107": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1112": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1113": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1114": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1117": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1118": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "112": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1120": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1122": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1125": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1126": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1128": { + "location": [ + 133, + 33, + 133, + 44 + ] + }, + "113": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1133": { + "location": [ + 133, + 11, + 133, + 45 + ] + }, + "1137": { + "location": [ + 133, + 11, + 133, + 45 + ] + }, + "1139": { + "location": [ + 133, + 11, + 133, + 45 + ] + }, + "1142": { + "location": [ + 133, + 11, + 133, + 45 + ] + }, + "1143": { + "location": [ + 133, + 11, + 133, + 45 + ] + }, + "1144": { + "location": [ + 133, + 11, + 133, + 45 + ] + }, + "1146": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1147": { + "location": [ + 132, + 0, + 133, + 45 + ] + }, + "1148": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "115": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1153": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "1154": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "1155": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "1158": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "1159": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "1161": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "1163": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "1166": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "117": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1174": { + "dev": "dev: Index out of range", + "location": null + }, + "1177": { + "dev": "dev: Index out of range", + "location": null + }, + "1178": { + "location": [ + 138, + 15, + 138, + 29 + ] + }, + "1180": { + "location": [ + 138, + 15, + 138, + 29 + ] + }, + "1181": { + "location": [ + 139, + 11, + 139, + 22 + ] + }, + "120": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1219": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "1221": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "1222": { + "location": [ + 138, + 0, + 139, + 22 + ] + }, + "1223": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1228": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1229": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1230": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1233": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1234": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1236": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1238": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1241": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1242": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1244": { + "location": [ + 145, + 33, + 145, + 39 + ] + }, + "1249": { + "location": [ + 145, + 11, + 145, + 40 + ] + }, + "1253": { + "location": [ + 145, + 11, + 145, + 40 + ] + }, + "1255": { + "location": [ + 145, + 11, + 145, + 40 + ] + }, + "1258": { + "location": [ + 145, + 11, + 145, + 40 + ] + }, + "1259": { + "location": [ + 145, + 11, + 145, + 40 + ] + }, + "1260": { + "location": [ + 145, + 11, + 145, + 40 + ] + }, + "1262": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1263": { + "location": [ + 144, + 0, + 145, + 40 + ] + }, + "1264": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1269": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1270": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1271": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1274": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1275": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1277": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1279": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "128": { + "dev": "dev: Index out of range", + "location": null + }, + "1282": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1283": { + "location": [ + 149, + 47, + 149, + 57 + ] + }, + "1284": { + "location": [ + 149, + 29, + 149, + 46 + ] + }, + "1286": { + "location": [ + 149, + 29, + 149, + 46 + ] + }, + "1287": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1290": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1291": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1292": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1297": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1298": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1299": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1302": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1303": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1305": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1307": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "131": { + "dev": "dev: Index out of range", + "location": null + }, + "1310": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1318": { + "dev": "dev: Index out of range", + "location": null + }, + "1321": { + "dev": "dev: Index out of range", + "location": null + }, + "1322": { + "location": [ + 149, + 29, + 149, + 46 + ] + }, + "1324": { + "location": [ + 149, + 29, + 149, + 46 + ] + }, + "1325": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1326": { + "location": [ + 156, + 38, + 156, + 44 + ] + }, + "1331": { + "location": [ + 150, + 22, + 150, + 51 + ] + }, + "1335": { + "location": [ + 150, + 22, + 150, + 51 + ] + }, + "1338": { + "location": [ + 150, + 22, + 150, + 51 + ] + }, + "1341": { + "location": [ + 150, + 22, + 150, + 51 + ] + }, + "1342": { + "location": [ + 150, + 22, + 150, + 51 + ] + }, + "1343": { + "location": [ + 150, + 22, + 150, + 51 + ] + }, + "1346": { + "location": [ + 150, + 4, + 150, + 51 + ] + }, + "1347": { + "location": [ + 150, + 4, + 150, + 51 + ] + }, + "1350": { + "location": [ + 150, + 4, + 150, + 51 + ] + }, + "1351": { + "location": [ + 151, + 4, + 151, + 14 + ] + }, + "1353": { + "location": [ + 151, + 4, + 151, + 53 + ] + }, + "1363": { + "location": [ + 151, + 28, + 151, + 38 + ] + }, + "1368": { + "location": [ + 151, + 40, + 151, + 44 + ] + }, + "1373": { + "location": [ + 156, + 38, + 156, + 44 + ] + }, + "1391": { + "location": [ + 151, + 4, + 151, + 53 + ] + }, + "1404": { + "dev": "dev: EXTERNAL_CALL_FAILED", + "location": null + }, + "1410": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "1413": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "142": { + "dev": "dev: Index out of range", + "location": null + }, + "1422": { + "dev": "dev: Index out of range", + "location": null + }, + "1425": { + "dev": "dev: Index out of range", + "location": null + }, + "1433": { + "location": [ + 151, + 4, + 151, + 53 + ] + }, + "1434": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1435": { + "location": [ + 153, + 4, + 153, + 20 + ] + }, + "1437": { + "location": [ + 153, + 24, + 153, + 30 + ] + }, + "1447": { + "dev": "dev: SAFEADD", + "location": null + }, + "145": { + "dev": "dev: Index out of range", + "location": null + }, + "1450": { + "dev": "dev: SAFEADD", + "location": null + }, + "1453": { + "location": [ + 153, + 4, + 153, + 30 + ] + }, + "1454": { + "location": [ + 153, + 4, + 153, + 30 + ] + }, + "1455": { + "location": [ + 153, + 4, + 153, + 20 + ] + }, + "1457": { + "location": [ + 154, + 4, + 154, + 18 + ] + }, + "1459": { + "location": [ + 154, + 19, + 154, + 27 + ] + }, + "1462": { + "location": [ + 154, + 4, + 154, + 28 + ] + }, + "1464": { + "location": [ + 154, + 4, + 154, + 28 + ] + }, + "1465": { + "location": [ + 154, + 4, + 154, + 28 + ] + }, + "1466": { + "location": [ + 154, + 4, + 154, + 28 + ] + }, + "1467": { + "location": [ + 154, + 4, + 154, + 28 + ] + }, + "1469": { + "location": [ + 154, + 4, + 154, + 28 + ] + }, + "1470": { + "location": [ + 154, + 4, + 154, + 28 + ] + }, + "1473": { + "location": [ + 154, + 32, + 154, + 38 + ] + }, + "1483": { + "dev": "dev: SAFEADD", + "location": null + }, + "1486": { + "dev": "dev: SAFEADD", + "location": null + }, + "1489": { + "location": [ + 154, + 4, + 154, + 38 + ] + }, + "1490": { + "location": [ + 154, + 4, + 154, + 38 + ] + }, + "1493": { + "location": [ + 154, + 4, + 154, + 38 + ] + }, + "1494": { + "location": [ + 155, + 33, + 155, + 41 + ] + }, + "1496": { + "location": [ + 155, + 4, + 155, + 50 + ] + }, + "1497": { + "location": [ + 155, + 4, + 155, + 50 + ] + }, + "1498": { + "location": [ + 155, + 4, + 155, + 50 + ] + }, + "1531": { + "location": [ + 155, + 43, + 155, + 49 + ] + }, + "1541": { + "location": [ + 155, + 4, + 155, + 50 + ] + }, + "1544": { + "location": [ + 155, + 4, + 155, + 50 + ] + }, + "1545": { + "location": [ + 156, + 28, + 156, + 36 + ] + }, + "1547": { + "location": [ + 156, + 4, + 156, + 53 + ] + }, + "1548": { + "location": [ + 156, + 16, + 156, + 26 + ] + }, + "1549": { + "location": [ + 156, + 4, + 156, + 53 + ] + }, + "1582": { + "location": [ + 156, + 38, + 156, + 44 + ] + }, + "1589": { + "location": [ + 156, + 46, + 156, + 52 + ] + }, + "1599": { + "location": [ + 156, + 4, + 156, + 53 + ] + }, + "1602": { + "location": [ + 156, + 4, + 156, + 53 + ] + }, + "1603": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1605": { + "location": [ + 157, + 11, + 157, + 17 + ] + }, + "1608": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1609": { + "location": [ + 149, + 0, + 157, + 17 + ] + }, + "1610": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1615": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1616": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1617": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1620": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1621": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1623": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1625": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1628": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1636": { + "dev": "dev: Index out of range", + "location": null + }, + "1639": { + "dev": "dev: Index out of range", + "location": null + }, + "1640": { + "location": [ + 162, + 12, + 162, + 26 + ] + }, + "1642": { + "location": [ + 162, + 12, + 162, + 26 + ] + }, + "1643": { + "location": [ + 163, + 11, + 163, + 22 + ] + }, + "1681": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1683": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1684": { + "location": [ + 162, + 0, + 163, + 22 + ] + }, + "1685": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1690": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1691": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1692": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1695": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1696": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1698": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1700": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1703": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1704": { + "location": [ + 173, + 15, + 173, + 21 + ] + }, + "1709": { + "location": [ + 169, + 22, + 169, + 51 + ] + }, + "1713": { + "location": [ + 169, + 22, + 169, + 51 + ] + }, + "1715": { + "location": [ + 169, + 22, + 169, + 51 + ] + }, + "1718": { + "location": [ + 169, + 22, + 169, + 51 + ] + }, + "1719": { + "location": [ + 169, + 22, + 169, + 51 + ] + }, + "1720": { + "location": [ + 169, + 22, + 169, + 51 + ] + }, + "1722": { + "location": [ + 169, + 4, + 169, + 51 + ] + }, + "1723": { + "location": [ + 169, + 4, + 169, + 51 + ] + }, + "1725": { + "location": [ + 169, + 4, + 169, + 51 + ] + }, + "1726": { + "location": [ + 172, + 7, + 172, + 13 + ] + }, + "1728": { + "location": [ + 172, + 7, + 172, + 18 + ] + }, + "1729": { + "location": [ + 172, + 7, + 172, + 54 + ] + }, + "1732": { + "location": [ + 172, + 7, + 172, + 54 + ] + }, + "1733": { + "location": [ + 172, + 23, + 172, + 33 + ] + }, + "1735": { + "location": [ + 172, + 23, + 172, + 49 + ] + }, + "1744": { + "location": [ + 172, + 44, + 172, + 48 + ] + }, + "1757": { + "location": [ + 172, + 23, + 172, + 49 + ] + }, + "1770": { + "dev": "dev: EXTERNAL_CALL_FAILED", + "location": null + }, + "1776": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "1779": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "1782": { + "location": [ + 172, + 23, + 172, + 49 + ] + }, + "1783": { + "location": [ + 172, + 23, + 172, + 49 + ] + }, + "1784": { + "location": [ + 172, + 23, + 172, + 54 + ] + }, + "1785": { + "location": [ + 172, + 23, + 172, + 54 + ] + }, + "1786": { + "location": [ + 172, + 7, + 172, + 54 + ] + }, + "1789": { + "location": [ + 172, + 7, + 172, + 54 + ] + }, + "1790": { + "location": [ + 172, + 7, + 172, + 54 + ] + }, + "1791": { + "location": [ + 172, + 7, + 172, + 54 + ] + }, + "1792": { + "location": [ + 172, + 7, + 172, + 54 + ] + }, + "1793": { + "location": [ + 172, + 4, + 173, + 21 + ] + }, + "1794": { + "location": [ + 172, + 4, + 173, + 21 + ] + }, + "1797": { + "location": [ + 172, + 4, + 173, + 21 + ] + }, + "1798": { + "location": [ + 173, + 15, + 173, + 21 + ] + }, + "1814": { + "location": [ + 172, + 4, + 173, + 21 + ] + }, + "1815": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1817": { + "location": [ + 175, + 11, + 175, + 17 + ] + }, + "1819": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1820": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1821": { + "location": [ + 168, + 0, + 175, + 17 + ] + }, + "1822": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1827": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1828": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1829": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1832": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1833": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1835": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1837": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "184": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1840": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1841": { + "location": [ + 179, + 44, + 179, + 54 + ] + }, + "1842": { + "location": [ + 179, + 26, + 179, + 43 + ] + }, + "1844": { + "location": [ + 179, + 26, + 179, + 43 + ] + }, + "1845": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1848": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1849": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1850": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1855": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1856": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1857": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "186": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1860": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1861": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1863": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1865": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1868": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "187": { + "location": [ + 13, + 18, + 13, + 61 + ] + }, + "1876": { + "dev": "dev: Index out of range", + "location": null + }, + "1879": { + "dev": "dev: Index out of range", + "location": null + }, + "188": { + "location": [ + 31, + 14, + 31, + 19 + ] + }, + "1880": { + "location": [ + 179, + 26, + 179, + 43 + ] + }, + "1882": { + "location": [ + 179, + 26, + 179, + 43 + ] + }, + "1883": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "1884": { + "location": [ + 190, + 46, + 190, + 52 + ] + }, + "1889": { + "location": [ + 180, + 22, + 180, + 51 + ] + }, + "1893": { + "location": [ + 180, + 22, + 180, + 51 + ] + }, + "1896": { + "location": [ + 180, + 22, + 180, + 51 + ] + }, + "1899": { + "location": [ + 180, + 22, + 180, + 51 + ] + }, + "19": { + "dev": "dev: NONPAYABLE_CHECK", + "location": null + }, + "1900": { + "location": [ + 180, + 22, + 180, + 51 + ] + }, + "1901": { + "location": [ + 180, + 22, + 180, + 51 + ] + }, + "1904": { + "location": [ + 180, + 4, + 180, + 51 + ] + }, + "1905": { + "location": [ + 180, + 4, + 180, + 51 + ] + }, + "1907": { + "location": [ + 180, + 4, + 180, + 51 + ] + }, + "1908": { + "location": [ + 182, + 7, + 182, + 13 + ] + }, + "1910": { + "location": [ + 182, + 7, + 182, + 18 + ] + }, + "1911": { + "location": [ + 182, + 7, + 182, + 54 + ] + }, + "1914": { + "location": [ + 182, + 7, + 182, + 54 + ] + }, + "1915": { + "location": [ + 182, + 23, + 182, + 33 + ] + }, + "1917": { + "location": [ + 182, + 23, + 182, + 49 + ] + }, + "1927": { + "location": [ + 182, + 44, + 182, + 48 + ] + }, + "193": { + "location": [ + 31, + 14, + 31, + 19 + ] + }, + "194": { + "location": [ + 31, + 14, + 31, + 19 + ] + }, + "1942": { + "location": [ + 182, + 23, + 182, + 49 + ] + }, + "195": { + "location": [ + 31, + 14, + 31, + 19 + ] + }, + "1955": { + "dev": "dev: EXTERNAL_CALL_FAILED", + "location": null + }, + "1961": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "1964": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "1968": { + "location": [ + 182, + 23, + 182, + 49 + ] + }, + "1969": { + "location": [ + 182, + 23, + 182, + 49 + ] + }, + "1970": { + "location": [ + 182, + 23, + 182, + 54 + ] + }, + "1971": { + "location": [ + 182, + 23, + 182, + 54 + ] + }, + "1972": { + "location": [ + 182, + 7, + 182, + 54 + ] + }, + "1975": { + "location": [ + 182, + 7, + 182, + 54 + ] + }, + "1976": { + "location": [ + 182, + 7, + 182, + 54 + ] + }, + "1977": { + "location": [ + 182, + 7, + 182, + 54 + ] + }, + "1978": { + "location": [ + 182, + 7, + 182, + 54 + ] + }, + "1979": { + "location": [ + 182, + 4, + 183, + 23 + ] + }, + "198": { + "location": [ + 31, + 14, + 31, + 19 + ] + }, + "1980": { + "location": [ + 182, + 4, + 183, + 23 + ] + }, + "1983": { + "location": [ + 182, + 4, + 183, + 23 + ] + }, + "1984": { + "location": [ + 190, + 46, + 190, + 52 + ] + }, + "1987": { + "location": [ + 183, + 8, + 183, + 14 + ] + }, + "1990": { + "location": [ + 182, + 4, + 183, + 23 + ] + }, + "1991": { + "location": [ + 185, + 4, + 185, + 14 + ] + }, + "1993": { + "location": [ + 185, + 4, + 185, + 53 + ] + }, + "2003": { + "location": [ + 185, + 28, + 185, + 38 + ] + }, + "2008": { + "location": [ + 185, + 40, + 185, + 44 + ] + }, + "2013": { + "location": [ + 185, + 46, + 185, + 52 + ] + }, + "2031": { + "location": [ + 185, + 4, + 185, + 53 + ] + }, + "2044": { + "dev": "dev: EXTERNAL_CALL_FAILED", + "location": null + }, + "2050": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "2053": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "2062": { + "dev": "dev: Index out of range", + "location": null + }, + "2065": { + "dev": "dev: Index out of range", + "location": null + }, + "207": { + "location": [ + 31, + 14, + 31, + 19 + ] + }, + "2073": { + "location": [ + 185, + 4, + 185, + 53 + ] + }, + "2074": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "2075": { + "location": [ + 187, + 4, + 187, + 20 + ] + }, + "2076": { + "location": [ + 187, + 4, + 187, + 30 + ] + }, + "2077": { + "location": [ + 190, + 46, + 190, + 52 + ] + }, + "2081": { + "location": [ + 187, + 4, + 187, + 30 + ] + }, + "2083": { + "location": [ + 187, + 4, + 187, + 30 + ] + }, + "2086": { + "dev": "dev: SAFEADD", + "location": null + }, + "2089": { + "dev": "dev: SAFEADD", + "location": null + }, + "209": { + "location": [ + 31, + 14, + 31, + 19 + ] + }, + "2090": { + "location": [ + 187, + 4, + 187, + 30 + ] + }, + "2091": { + "location": [ + 187, + 4, + 187, + 30 + ] + }, + "2092": { + "location": [ + 187, + 4, + 187, + 30 + ] + }, + "2093": { + "location": [ + 187, + 4, + 187, + 30 + ] + }, + "2094": { + "location": [ + 187, + 4, + 187, + 20 + ] + }, + "2095": { + "location": [ + 187, + 4, + 187, + 30 + ] + }, + "2096": { + "location": [ + 188, + 4, + 188, + 18 + ] + }, + "2098": { + "location": [ + 188, + 19, + 188, + 27 + ] + }, + "210": { + "location": [ + 31, + 14, + 31, + 19 + ] + }, + "2101": { + "location": [ + 188, + 4, + 188, + 28 + ] + }, + "2103": { + "location": [ + 188, + 4, + 188, + 28 + ] + }, + "2104": { + "location": [ + 188, + 4, + 188, + 28 + ] + }, + "2105": { + "location": [ + 188, + 4, + 188, + 28 + ] + }, + "2106": { + "location": [ + 188, + 4, + 188, + 28 + ] + }, + "2108": { + "location": [ + 188, + 4, + 188, + 28 + ] + }, + "2109": { + "location": [ + 188, + 4, + 188, + 28 + ] + }, + "211": { + "location": [ + 54, + 0, + 55, + 15 + ] + }, + "2110": { + "location": [ + 188, + 4, + 188, + 38 + ] + }, + "2111": { + "location": [ + 188, + 4, + 188, + 38 + ] + }, + "2112": { + "location": [ + 190, + 46, + 190, + 52 + ] + }, + "2116": { + "location": [ + 188, + 4, + 188, + 38 + ] + }, + "2118": { + "location": [ + 188, + 4, + 188, + 38 + ] + }, + "2121": { + "dev": "dev: SAFEADD", + "location": null + }, + "2124": { + "dev": "dev: SAFEADD", + "location": null + }, + "2125": { + "location": [ + 188, + 4, + 188, + 38 + ] + }, + "2126": { + "location": [ + 188, + 4, + 188, + 38 + ] + }, + "2127": { + "location": [ + 188, + 4, + 188, + 38 + ] + }, + "2128": { + "location": [ + 188, + 4, + 188, + 38 + ] + }, + "2129": { + "location": [ + 188, + 4, + 188, + 38 + ] + }, + "2131": { + "location": [ + 188, + 4, + 188, + 38 + ] + }, + "2132": { + "location": [ + 189, + 33, + 189, + 41 + ] + }, + "2134": { + "location": [ + 189, + 4, + 189, + 50 + ] + }, + "2135": { + "location": [ + 189, + 4, + 189, + 50 + ] + }, + "2136": { + "location": [ + 189, + 4, + 189, + 50 + ] + }, + "216": { + "location": [ + 54, + 0, + 55, + 15 + ] + }, + "2169": { + "location": [ + 190, + 46, + 190, + 52 + ] + }, + "217": { + "location": [ + 54, + 0, + 55, + 15 + ] + }, + "2178": { + "location": [ + 189, + 4, + 189, + 50 + ] + }, + "218": { + "location": [ + 54, + 0, + 55, + 15 + ] + }, + "2181": { + "location": [ + 189, + 4, + 189, + 50 + ] + }, + "2182": { + "location": [ + 190, + 28, + 190, + 36 + ] + }, + "2184": { + "location": [ + 190, + 4, + 190, + 53 + ] + }, + "2185": { + "location": [ + 190, + 16, + 190, + 26 + ] + }, + "2186": { + "location": [ + 190, + 4, + 190, + 53 + ] + }, + "22": { + "dev": "dev: NONPAYABLE_CHECK", + "location": null + }, + "221": { + "location": [ + 54, + 0, + 55, + 15 + ] + }, + "2219": { + "location": [ + 190, + 38, + 190, + 44 + ] + }, + "2226": { + "location": [ + 190, + 46, + 190, + 52 + ] + }, + "2235": { + "location": [ + 190, + 4, + 190, + 53 + ] + }, + "2238": { + "location": [ + 190, + 4, + 190, + 53 + ] + }, + "2239": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "2241": { + "location": [ + 191, + 11, + 191, + 17 + ] + }, + "2243": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "2244": { + "location": [ + 179, + 0, + 191, + 17 + ] + }, + "2245": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2250": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2251": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2252": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2255": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2256": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2258": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2260": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2263": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2271": { + "dev": "dev: Index out of range", + "location": null + }, + "2274": { + "dev": "dev: Index out of range", + "location": null + }, + "2275": { + "location": [ + 196, + 16, + 196, + 30 + ] + }, + "2277": { + "location": [ + 196, + 16, + 196, + 30 + ] + }, + "2278": { + "location": [ + 197, + 11, + 197, + 22 + ] + }, + "23": { + "location": [ + 11, + 20, + 11, + 27 + ] + }, + "2316": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2318": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "2319": { + "location": [ + 196, + 0, + 197, + 22 + ] + }, + "232": { + "location": [ + 55, + 11, + 55, + 15 + ] + }, + "2320": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2325": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2326": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2327": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2330": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2331": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2333": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2335": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2338": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2339": { + "location": [ + 206, + 17, + 206, + 23 + ] + }, + "2344": { + "location": [ + 203, + 22, + 203, + 51 + ] + }, + "2348": { + "location": [ + 203, + 22, + 203, + 51 + ] + }, + "2351": { + "location": [ + 203, + 22, + 203, + 51 + ] + }, + "2354": { + "location": [ + 203, + 22, + 203, + 51 + ] + }, + "2355": { + "location": [ + 203, + 22, + 203, + 51 + ] + }, + "2356": { + "location": [ + 203, + 22, + 203, + 51 + ] + }, + "2359": { + "location": [ + 203, + 4, + 203, + 51 + ] + }, + "2360": { + "location": [ + 203, + 4, + 203, + 51 + ] + }, + "2362": { + "location": [ + 203, + 4, + 203, + 51 + ] + }, + "2363": { + "location": [ + 206, + 17, + 206, + 23 + ] + }, + "2365": { + "location": [ + 206, + 7, + 206, + 23 + ] + }, + "2366": { + "location": [ + 206, + 7, + 206, + 13 + ] + }, + "2368": { + "location": [ + 206, + 7, + 206, + 23 + ] + }, + "2369": { + "location": [ + 206, + 7, + 206, + 23 + ] + }, + "2370": { + "location": [ + 206, + 7, + 206, + 49 + ] + }, + "2373": { + "location": [ + 206, + 7, + 206, + 49 + ] + }, + "2374": { + "location": [ + 206, + 28, + 206, + 44 + ] + }, + "2375": { + "location": [ + 206, + 28, + 206, + 49 + ] + }, + "2376": { + "location": [ + 206, + 28, + 206, + 49 + ] + }, + "2377": { + "location": [ + 206, + 7, + 206, + 49 + ] + }, + "2380": { + "location": [ + 206, + 7, + 206, + 49 + ] + }, + "2381": { + "location": [ + 206, + 7, + 206, + 49 + ] + }, + "2382": { + "location": [ + 206, + 7, + 206, + 49 + ] + }, + "2383": { + "location": [ + 206, + 7, + 206, + 49 + ] + }, + "2384": { + "location": [ + 206, + 4, + 207, + 16 + ] + }, + "2385": { + "location": [ + 206, + 4, + 207, + 16 + ] + }, + "2388": { + "location": [ + 206, + 4, + 207, + 16 + ] + }, + "2389": { + "location": [ + 207, + 15, + 207, + 16 + ] + }, + "2403": { + "location": [ + 206, + 4, + 207, + 16 + ] + }, + "2404": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2406": { + "location": [ + 209, + 11, + 209, + 17 + ] + }, + "2408": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2409": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2410": { + "location": [ + 202, + 0, + 209, + 17 + ] + }, + "2411": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2416": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2417": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2418": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2421": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2422": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2424": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2426": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2429": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2430": { + "location": [ + 213, + 48, + 213, + 58 + ] + }, + "2431": { + "location": [ + 213, + 30, + 213, + 47 + ] + }, + "2433": { + "location": [ + 213, + 30, + 213, + 47 + ] + }, + "2434": { + "location": [ + 213, + 75, + 213, + 85 + ] + }, + "2435": { + "location": [ + 213, + 60, + 213, + 74 + ] + }, + "2438": { + "location": [ + 213, + 60, + 213, + 74 + ] + }, + "2439": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2442": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2443": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2444": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2448": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2449": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2450": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2453": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2454": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2456": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2458": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2461": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2469": { + "dev": "dev: Index out of range", + "location": null + }, + "2472": { + "dev": "dev: Index out of range", + "location": null + }, + "2473": { + "location": [ + 213, + 30, + 213, + 47 + ] + }, + "2475": { + "location": [ + 213, + 30, + 213, + 47 + ] + }, + "2476": { + "location": [ + 213, + 75, + 213, + 85 + ] + }, + "2477": { + "location": [ + 213, + 60, + 213, + 74 + ] + }, + "2480": { + "location": [ + 213, + 60, + 213, + 74 + ] + }, + "2481": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2484": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2485": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2486": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2491": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2492": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2493": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2496": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2497": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2499": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2501": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2504": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2512": { + "dev": "dev: Index out of range", + "location": null + }, + "2515": { + "dev": "dev: Index out of range", + "location": null + }, + "2516": { + "location": [ + 213, + 30, + 213, + 47 + ] + }, + "2518": { + "location": [ + 213, + 30, + 213, + 47 + ] + }, + "2526": { + "dev": "dev: Index out of range", + "location": null + }, + "2529": { + "dev": "dev: Index out of range", + "location": null + }, + "2530": { + "location": [ + 213, + 60, + 213, + 74 + ] + }, + "2533": { + "location": [ + 213, + 60, + 213, + 74 + ] + }, + "2534": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2535": { + "location": [ + 228, + 46, + 228, + 52 + ] + }, + "2540": { + "location": [ + 214, + 22, + 214, + 51 + ] + }, + "2544": { + "location": [ + 214, + 22, + 214, + 51 + ] + }, + "2547": { + "location": [ + 214, + 22, + 214, + 51 + ] + }, + "2550": { + "location": [ + 214, + 22, + 214, + 51 + ] + }, + "2551": { + "location": [ + 214, + 22, + 214, + 51 + ] + }, + "2552": { + "location": [ + 214, + 22, + 214, + 51 + ] + }, + "2555": { + "location": [ + 214, + 4, + 214, + 51 + ] + }, + "2556": { + "location": [ + 214, + 4, + 214, + 51 + ] + }, + "2559": { + "location": [ + 214, + 4, + 214, + 51 + ] + }, + "2560": { + "location": [ + 228, + 46, + 228, + 52 + ] + }, + "2562": { + "location": [ + 217, + 7, + 217, + 23 + ] + }, + "2563": { + "location": [ + 217, + 7, + 217, + 13 + ] + }, + "2566": { + "location": [ + 217, + 7, + 217, + 23 + ] + }, + "2567": { + "location": [ + 217, + 7, + 217, + 23 + ] + }, + "2568": { + "location": [ + 217, + 7, + 217, + 49 + ] + }, + "2571": { + "location": [ + 217, + 7, + 217, + 49 + ] + }, + "2572": { + "location": [ + 217, + 28, + 217, + 44 + ] + }, + "2573": { + "location": [ + 217, + 28, + 217, + 49 + ] + }, + "2574": { + "location": [ + 217, + 28, + 217, + 49 + ] + }, + "2575": { + "location": [ + 217, + 7, + 217, + 49 + ] + }, + "2578": { + "location": [ + 217, + 7, + 217, + 49 + ] + }, + "2579": { + "location": [ + 217, + 7, + 217, + 49 + ] + }, + "2580": { + "location": [ + 217, + 7, + 217, + 49 + ] + }, + "2581": { + "location": [ + 217, + 7, + 217, + 49 + ] + }, + "2582": { + "location": [ + 217, + 4, + 218, + 13 + ] + }, + "2583": { + "location": [ + 217, + 4, + 218, + 13 + ] + }, + "2586": { + "location": [ + 217, + 4, + 218, + 13 + ] + }, + "2589": { + "dev": "dev: USER_RAISE", + "location": [ + 218, + 8, + 218, + 13 + ] + }, + "2590": { + "location": [ + 217, + 4, + 218, + 13 + ] + }, + "2591": { + "location": [ + 220, + 16, + 220, + 26 + ] + }, + "2592": { + "location": [ + 220, + 7, + 220, + 12 + ] + }, + "2597": { + "location": [ + 220, + 4, + 221, + 51 + ] + }, + "2600": { + "location": [ + 220, + 4, + 221, + 51 + ] + }, + "2601": { + "location": [ + 221, + 8, + 221, + 22 + ] + }, + "2603": { + "location": [ + 221, + 23, + 221, + 28 + ] + }, + "2607": { + "location": [ + 221, + 8, + 221, + 29 + ] + }, + "2609": { + "location": [ + 221, + 8, + 221, + 29 + ] + }, + "2610": { + "location": [ + 221, + 8, + 221, + 29 + ] + }, + "2611": { + "location": [ + 221, + 8, + 221, + 29 + ] + }, + "2612": { + "location": [ + 221, + 8, + 221, + 29 + ] + }, + "2614": { + "location": [ + 221, + 8, + 221, + 29 + ] + }, + "2615": { + "location": [ + 221, + 8, + 221, + 29 + ] + }, + "2617": { + "location": [ + 221, + 30, + 221, + 40 + ] + }, + "2627": { + "location": [ + 221, + 8, + 221, + 41 + ] + }, + "2628": { + "location": [ + 221, + 8, + 221, + 41 + ] + }, + "2631": { + "location": [ + 221, + 45, + 221, + 51 + ] + }, + "2641": { + "dev": "dev: Integer underflow", + "location": null + }, + "2644": { + "dev": "dev: Integer underflow", + "location": null + }, + "2647": { + "location": [ + 221, + 8, + 221, + 51 + ] + }, + "2648": { + "location": [ + 221, + 8, + 221, + 51 + ] + }, + "2651": { + "location": [ + 221, + 8, + 221, + 51 + ] + }, + "2652": { + "location": [ + 220, + 4, + 221, + 51 + ] + }, + "2653": { + "location": [ + 223, + 4, + 223, + 20 + ] + }, + "2655": { + "location": [ + 223, + 24, + 223, + 30 + ] + }, + "2665": { + "dev": "dev: Integer underflow", + "location": null + }, + "2668": { + "dev": "dev: Integer underflow", + "location": null + }, + "2671": { + "location": [ + 223, + 4, + 223, + 30 + ] + }, + "2672": { + "location": [ + 223, + 4, + 223, + 30 + ] + }, + "2673": { + "location": [ + 223, + 4, + 223, + 20 + ] + }, + "2675": { + "location": [ + 224, + 4, + 224, + 18 + ] + }, + "2677": { + "location": [ + 224, + 19, + 224, + 24 + ] + }, + "268": { + "location": [ + 55, + 11, + 55, + 15 + ] + }, + "2681": { + "location": [ + 224, + 4, + 224, + 25 + ] + }, + "2683": { + "location": [ + 224, + 4, + 224, + 25 + ] + }, + "2684": { + "location": [ + 224, + 4, + 224, + 25 + ] + }, + "2685": { + "location": [ + 224, + 4, + 224, + 25 + ] + }, + "2686": { + "location": [ + 224, + 4, + 224, + 25 + ] + }, + "2688": { + "location": [ + 224, + 4, + 224, + 25 + ] + }, + "2689": { + "location": [ + 224, + 4, + 224, + 25 + ] + }, + "269": { + "location": [ + 55, + 11, + 55, + 15 + ] + }, + "2692": { + "location": [ + 224, + 29, + 224, + 35 + ] + }, + "2702": { + "dev": "dev: Integer underflow", + "location": null + }, + "2705": { + "dev": "dev: Integer underflow", + "location": null + }, + "2708": { + "location": [ + 224, + 4, + 224, + 35 + ] + }, + "2709": { + "location": [ + 224, + 4, + 224, + 35 + ] + }, + "2712": { + "location": [ + 224, + 4, + 224, + 35 + ] + }, + "2713": { + "location": [ + 226, + 4, + 226, + 14 + ] + }, + "2715": { + "location": [ + 226, + 4, + 226, + 41 + ] + }, + "2725": { + "location": [ + 226, + 24, + 226, + 32 + ] + }, + "2732": { + "location": [ + 228, + 46, + 228, + 52 + ] + }, + "2750": { + "location": [ + 226, + 4, + 226, + 41 + ] + }, + "2763": { + "dev": "dev: EXTERNAL_CALL_FAILED", + "location": null + }, + "2769": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "2772": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "2781": { + "dev": "dev: Index out of range", + "location": null + }, + "2784": { + "dev": "dev: Index out of range", + "location": null + }, + "2792": { + "location": [ + 226, + 4, + 226, + 41 + ] + }, + "2793": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2794": { + "location": [ + 227, + 4, + 227, + 47 + ] + }, + "2795": { + "location": [ + 227, + 17, + 227, + 22 + ] + }, + "2798": { + "location": [ + 227, + 4, + 227, + 47 + ] + }, + "2799": { + "location": [ + 227, + 4, + 227, + 47 + ] + }, + "28": { + "location": [ + 11, + 20, + 11, + 27 + ] + }, + "2832": { + "location": [ + 227, + 40, + 227, + 46 + ] + }, + "2842": { + "location": [ + 227, + 4, + 227, + 47 + ] + }, + "2845": { + "location": [ + 227, + 4, + 227, + 47 + ] + }, + "2846": { + "location": [ + 228, + 39, + 228, + 44 + ] + }, + "2849": { + "location": [ + 228, + 4, + 228, + 61 + ] + }, + "2850": { + "location": [ + 228, + 29, + 228, + 37 + ] + }, + "2852": { + "location": [ + 228, + 4, + 228, + 61 + ] + }, + "2853": { + "location": [ + 228, + 17, + 228, + 27 + ] + }, + "2854": { + "location": [ + 228, + 4, + 228, + 61 + ] + }, + "2887": { + "location": [ + 228, + 46, + 228, + 52 + ] + }, + "2894": { + "location": [ + 228, + 54, + 228, + 60 + ] + }, + "29": { + "location": [ + 11, + 20, + 11, + 27 + ] + }, + "2904": { + "location": [ + 228, + 4, + 228, + 61 + ] + }, + "2907": { + "location": [ + 228, + 4, + 228, + 61 + ] + }, + "2908": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2910": { + "location": [ + 229, + 11, + 229, + 17 + ] + }, + "2913": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2914": { + "location": [ + 213, + 0, + 229, + 17 + ] + }, + "2915": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2920": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2921": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2922": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2925": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2926": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2928": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2930": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2933": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2941": { + "dev": "dev: Index out of range", + "location": null + }, + "2944": { + "dev": "dev: Index out of range", + "location": null + }, + "2945": { + "location": [ + 234, + 14, + 234, + 28 + ] + }, + "2947": { + "location": [ + 234, + 14, + 234, + 28 + ] + }, + "2948": { + "location": [ + 235, + 11, + 235, + 22 + ] + }, + "2986": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2988": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2989": { + "location": [ + 234, + 0, + 235, + 22 + ] + }, + "2990": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "2995": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "2996": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "2997": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "30": { + "location": [ + 11, + 20, + 11, + 27 + ] + }, + "3000": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "3001": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "3003": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "3005": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "3008": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "3009": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "3011": { + "location": [ + 241, + 33, + 241, + 39 + ] + }, + "3016": { + "location": [ + 241, + 11, + 241, + 40 + ] + }, + "3020": { + "location": [ + 241, + 11, + 241, + 40 + ] + }, + "3022": { + "location": [ + 241, + 11, + 241, + 40 + ] + }, + "3025": { + "location": [ + 241, + 11, + 241, + 40 + ] + }, + "3026": { + "location": [ + 241, + 11, + 241, + 40 + ] + }, + "3027": { + "location": [ + 241, + 11, + 241, + 40 + ] + }, + "3029": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "3030": { + "location": [ + 240, + 0, + 241, + 40 + ] + }, + "3031": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3036": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3037": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3038": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3041": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3042": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3044": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3046": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3049": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3050": { + "location": [ + 245, + 46, + 245, + 56 + ] + }, + "3051": { + "location": [ + 245, + 28, + 245, + 45 + ] + }, + "3053": { + "location": [ + 245, + 28, + 245, + 45 + ] + }, + "3054": { + "location": [ + 245, + 73, + 245, + 83 + ] + }, + "3055": { + "location": [ + 245, + 58, + 245, + 72 + ] + }, + "3057": { + "location": [ + 245, + 58, + 245, + 72 + ] + }, + "3058": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3061": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3062": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3063": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3068": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3069": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3070": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3073": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3074": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3076": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3078": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3081": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3089": { + "dev": "dev: Index out of range", + "location": null + }, + "3092": { + "dev": "dev: Index out of range", + "location": null + }, + "3093": { + "location": [ + 245, + 28, + 245, + 45 + ] + }, + "3095": { + "location": [ + 245, + 28, + 245, + 45 + ] + }, + "3096": { + "location": [ + 245, + 73, + 245, + 83 + ] + }, + "3097": { + "location": [ + 245, + 58, + 245, + 72 + ] + }, + "3099": { + "location": [ + 245, + 58, + 245, + 72 + ] + }, + "3100": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3103": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3104": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3105": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3110": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3111": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3112": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3115": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3116": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3118": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3120": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3123": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3131": { + "dev": "dev: Index out of range", + "location": null + }, + "3134": { + "dev": "dev: Index out of range", + "location": null + }, + "3135": { + "location": [ + 245, + 28, + 245, + 45 + ] + }, + "3137": { + "location": [ + 245, + 28, + 245, + 45 + ] + }, + "3145": { + "dev": "dev: Index out of range", + "location": null + }, + "3148": { + "dev": "dev: Index out of range", + "location": null + }, + "3149": { + "location": [ + 245, + 58, + 245, + 72 + ] + }, + "3151": { + "location": [ + 245, + 58, + 245, + 72 + ] + }, + "3152": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3153": { + "location": [ + 246, + 16, + 246, + 26 + ] + }, + "3154": { + "location": [ + 246, + 7, + 246, + 12 + ] + }, + "3158": { + "location": [ + 246, + 4, + 247, + 51 + ] + }, + "3161": { + "location": [ + 246, + 4, + 247, + 51 + ] + }, + "3162": { + "location": [ + 247, + 8, + 247, + 22 + ] + }, + "3164": { + "location": [ + 247, + 23, + 247, + 28 + ] + }, + "3167": { + "location": [ + 247, + 8, + 247, + 29 + ] + }, + "3169": { + "location": [ + 247, + 8, + 247, + 29 + ] + }, + "3170": { + "location": [ + 247, + 8, + 247, + 29 + ] + }, + "3171": { + "location": [ + 247, + 8, + 247, + 29 + ] + }, + "3172": { + "location": [ + 247, + 8, + 247, + 29 + ] + }, + "3174": { + "location": [ + 247, + 8, + 247, + 29 + ] + }, + "3175": { + "location": [ + 247, + 8, + 247, + 29 + ] + }, + "3177": { + "location": [ + 247, + 30, + 247, + 40 + ] + }, + "3187": { + "location": [ + 247, + 8, + 247, + 41 + ] + }, + "3188": { + "location": [ + 247, + 8, + 247, + 41 + ] + }, + "3189": { + "location": [ + 247, + 8, + 247, + 51 + ] + }, + "3190": { + "location": [ + 247, + 8, + 247, + 51 + ] + }, + "3191": { + "location": [ + 255, + 54, + 255, + 60 + ] + }, + "3195": { + "location": [ + 247, + 8, + 247, + 51 + ] + }, + "3197": { + "location": [ + 247, + 8, + 247, + 51 + ] + }, + "3200": { + "dev": "dev: Integer underflow", + "location": null + }, + "3203": { + "dev": "dev: Integer underflow", + "location": null + }, + "3204": { + "location": [ + 247, + 8, + 247, + 51 + ] + }, + "3205": { + "location": [ + 247, + 8, + 247, + 51 + ] + }, + "3206": { + "location": [ + 247, + 8, + 247, + 51 + ] + }, + "3207": { + "location": [ + 247, + 8, + 247, + 51 + ] + }, + "3208": { + "location": [ + 247, + 8, + 247, + 51 + ] + }, + "3210": { + "location": [ + 247, + 8, + 247, + 51 + ] + }, + "3211": { + "location": [ + 246, + 4, + 247, + 51 + ] + }, + "3212": { + "location": [ + 255, + 54, + 255, + 60 + ] + }, + "3217": { + "location": [ + 249, + 22, + 249, + 51 + ] + }, + "3221": { + "location": [ + 249, + 22, + 249, + 51 + ] + }, + "3224": { + "location": [ + 249, + 22, + 249, + 51 + ] + }, + "3227": { + "location": [ + 249, + 22, + 249, + 51 + ] + }, + "3228": { + "location": [ + 249, + 22, + 249, + 51 + ] + }, + "3229": { + "location": [ + 249, + 22, + 249, + 51 + ] + }, + "3232": { + "location": [ + 249, + 4, + 249, + 51 + ] + }, + "3233": { + "location": [ + 249, + 4, + 249, + 51 + ] + }, + "3236": { + "location": [ + 249, + 4, + 249, + 51 + ] + }, + "3237": { + "location": [ + 250, + 4, + 250, + 20 + ] + }, + "3238": { + "location": [ + 250, + 4, + 250, + 30 + ] + }, + "3239": { + "location": [ + 255, + 54, + 255, + 60 + ] + }, + "3243": { + "location": [ + 250, + 4, + 250, + 30 + ] + }, + "3245": { + "location": [ + 250, + 4, + 250, + 30 + ] + }, + "3248": { + "dev": "dev: Integer underflow", + "location": null + }, + "3251": { + "dev": "dev: Integer underflow", + "location": null + }, + "3252": { + "location": [ + 250, + 4, + 250, + 30 + ] + }, + "3253": { + "location": [ + 250, + 4, + 250, + 30 + ] + }, + "3254": { + "location": [ + 250, + 4, + 250, + 30 + ] + }, + "3255": { + "location": [ + 250, + 4, + 250, + 30 + ] + }, + "3256": { + "location": [ + 250, + 4, + 250, + 20 + ] + }, + "3257": { + "location": [ + 250, + 4, + 250, + 30 + ] + }, + "3258": { + "location": [ + 251, + 4, + 251, + 18 + ] + }, + "3260": { + "location": [ + 251, + 19, + 251, + 24 + ] + }, + "3263": { + "location": [ + 251, + 4, + 251, + 25 + ] + }, + "3265": { + "location": [ + 251, + 4, + 251, + 25 + ] + }, + "3266": { + "location": [ + 251, + 4, + 251, + 25 + ] + }, + "3267": { + "location": [ + 251, + 4, + 251, + 25 + ] + }, + "3268": { + "location": [ + 251, + 4, + 251, + 25 + ] + }, + "3270": { + "location": [ + 251, + 4, + 251, + 25 + ] + }, + "3271": { + "location": [ + 251, + 4, + 251, + 25 + ] + }, + "3272": { + "location": [ + 251, + 4, + 251, + 35 + ] + }, + "3273": { + "location": [ + 251, + 4, + 251, + 35 + ] + }, + "3274": { + "location": [ + 255, + 54, + 255, + 60 + ] + }, + "3278": { + "location": [ + 251, + 4, + 251, + 35 + ] + }, + "3280": { + "location": [ + 251, + 4, + 251, + 35 + ] + }, + "3283": { + "dev": "dev: Integer underflow", + "location": null + }, + "3286": { + "dev": "dev: Integer underflow", + "location": null + }, + "3287": { + "location": [ + 251, + 4, + 251, + 35 + ] + }, + "3288": { + "location": [ + 251, + 4, + 251, + 35 + ] + }, + "3289": { + "location": [ + 251, + 4, + 251, + 35 + ] + }, + "3290": { + "location": [ + 251, + 4, + 251, + 35 + ] + }, + "3291": { + "location": [ + 251, + 4, + 251, + 35 + ] + }, + "3293": { + "location": [ + 251, + 4, + 251, + 35 + ] + }, + "3294": { + "location": [ + 253, + 4, + 253, + 14 + ] + }, + "3296": { + "location": [ + 253, + 4, + 253, + 41 + ] + }, + "33": { + "location": [ + 11, + 20, + 11, + 27 + ] + }, + "3306": { + "location": [ + 253, + 24, + 253, + 32 + ] + }, + "3313": { + "location": [ + 253, + 34, + 253, + 40 + ] + }, + "3332": { + "location": [ + 253, + 4, + 253, + 41 + ] + }, + "334": { + "location": [ + 54, + 0, + 55, + 15 + ] + }, + "3345": { + "dev": "dev: EXTERNAL_CALL_FAILED", + "location": null + }, + "3351": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "3354": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "336": { + "location": [ + 54, + 0, + 55, + 15 + ] + }, + "3363": { + "dev": "dev: Index out of range", + "location": null + }, + "3366": { + "dev": "dev: Index out of range", + "location": null + }, + "337": { + "location": [ + 54, + 0, + 55, + 15 + ] + }, + "3374": { + "location": [ + 253, + 4, + 253, + 41 + ] + }, + "3375": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3376": { + "location": [ + 254, + 4, + 254, + 47 + ] + }, + "3377": { + "location": [ + 254, + 17, + 254, + 22 + ] + }, + "3379": { + "location": [ + 254, + 4, + 254, + 47 + ] + }, + "338": { + "location": [ + 60, + 0, + 61, + 17 + ] + }, + "3380": { + "location": [ + 254, + 4, + 254, + 47 + ] + }, + "3413": { + "location": [ + 255, + 54, + 255, + 60 + ] + }, + "3422": { + "location": [ + 254, + 4, + 254, + 47 + ] + }, + "3425": { + "location": [ + 254, + 4, + 254, + 47 + ] + }, + "3426": { + "location": [ + 255, + 39, + 255, + 44 + ] + }, + "3428": { + "location": [ + 255, + 4, + 255, + 61 + ] + }, + "3429": { + "location": [ + 255, + 29, + 255, + 37 + ] + }, + "343": { + "location": [ + 60, + 0, + 61, + 17 + ] + }, + "3431": { + "location": [ + 255, + 4, + 255, + 61 + ] + }, + "3432": { + "location": [ + 255, + 17, + 255, + 27 + ] + }, + "3433": { + "location": [ + 255, + 4, + 255, + 61 + ] + }, + "344": { + "location": [ + 60, + 0, + 61, + 17 + ] + }, + "345": { + "location": [ + 60, + 0, + 61, + 17 + ] + }, + "3466": { + "location": [ + 255, + 46, + 255, + 52 + ] + }, + "3474": { + "location": [ + 255, + 54, + 255, + 60 + ] + }, + "348": { + "location": [ + 60, + 0, + 61, + 17 + ] + }, + "3483": { + "location": [ + 255, + 4, + 255, + 61 + ] + }, + "3486": { + "location": [ + 255, + 4, + 255, + 61 + ] + }, + "3487": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3489": { + "location": [ + 256, + 11, + 256, + 17 + ] + }, + "3492": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3493": { + "location": [ + 245, + 0, + 256, + 17 + ] + }, + "3494": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3499": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3500": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3501": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3504": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3505": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3507": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3509": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3512": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3513": { + "location": [ + 262, + 4, + 262, + 14 + ] + }, + "3515": { + "location": [ + 262, + 4, + 262, + 43 + ] + }, + "3524": { + "location": [ + 262, + 24, + 262, + 34 + ] + }, + "3528": { + "location": [ + 262, + 36, + 262, + 42 + ] + }, + "3543": { + "location": [ + 262, + 4, + 262, + 43 + ] + }, + "3556": { + "dev": "dev: EXTERNAL_CALL_FAILED", + "location": null + }, + "3562": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "3565": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "3573": { + "dev": "dev: Index out of range", + "location": null + }, + "3576": { + "dev": "dev: Index out of range", + "location": null + }, + "3582": { + "location": [ + 262, + 4, + 262, + 43 + ] + }, + "3583": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3584": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "3585": { + "location": [ + 260, + 0, + 262, + 43 + ] + }, + "359": { + "location": [ + 61, + 11, + 61, + 17 + ] + }, + "3590": { + "dev": "dev: FALLBACK_FUNCTION", + "location": null + }, + "3591": { + "location": [ + 102, + 0, + 109, + 65 + ] + }, + "3592": { + "location": [ + 103, + 27, + 103, + 43 + ] + }, + "3596": { + "location": [ + 103, + 4, + 103, + 43 + ] + }, + "3597": { + "location": [ + 104, + 7, + 104, + 18 + ] + }, + "3599": { + "location": [ + 104, + 7, + 104, + 23 + ] + }, + "3600": { + "location": [ + 104, + 4, + 105, + 16 + ] + }, + "3603": { + "location": [ + 104, + 4, + 105, + 16 + ] + }, + "3604": { + "location": [ + 105, + 15, + 105, + 16 + ] + }, + "3606": { + "location": [ + 105, + 8, + 105, + 16 + ] + }, + "3607": { + "location": [ + 105, + 8, + 105, + 16 + ] + }, + "3608": { + "location": [ + 105, + 8, + 105, + 16 + ] + }, + "3611": { + "location": [ + 105, + 8, + 105, + 16 + ] + }, + "3612": { + "location": [ + 104, + 4, + 105, + 16 + ] + }, + "3613": { + "location": [ + 109, + 11, + 109, + 22 + ] + }, + "3615": { + "location": [ + 109, + 11, + 109, + 51 + ] + }, + "3616": { + "location": [ + 109, + 25, + 109, + 35 + ] + }, + "3618": { + "location": [ + 109, + 25, + 109, + 51 + ] + }, + "3627": { + "location": [ + 109, + 46, + 109, + 50 + ] + }, + "3639": { + "location": [ + 109, + 25, + 109, + 51 + ] + }, + "3652": { + "dev": "dev: EXTERNAL_CALL_FAILED", + "location": null + }, + "3658": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "3661": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "3664": { + "location": [ + 109, + 25, + 109, + 51 + ] + }, + "3665": { + "location": [ + 109, + 25, + 109, + 51 + ] + }, + "3668": { + "location": [ + 109, + 11, + 109, + 51 + ] + }, + "3672": { + "location": [ + 109, + 11, + 109, + 51 + ] + }, + "3674": { + "dev": "dev: VALIDATE_UINT256", + "location": null + }, + "3678": { + "dev": "dev: Integer overflow", + "location": null + }, + "3679": { + "dev": "dev: Integer overflow", + "location": null + }, + "3682": { + "dev": "dev: Integer overflow", + "location": null + }, + "3683": { + "location": [ + 109, + 11, + 109, + 51 + ] + }, + "3684": { + "location": [ + 109, + 11, + 109, + 51 + ] + }, + "3685": { + "location": [ + 109, + 11, + 109, + 51 + ] + }, + "3686": { + "location": [ + 109, + 11, + 109, + 51 + ] + }, + "3687": { + "location": [ + 109, + 54, + 109, + 65 + ] + }, + "3692": { + "dev": "dev: CLAMP_GT_0", + "location": null + }, + "3695": { + "dev": "dev: CLAMP_GT_0", + "location": null + }, + "3697": { + "location": [ + 109, + 11, + 109, + 65 + ] + }, + "3699": { + "location": [ + 109, + 11, + 109, + 65 + ] + }, + "3700": { + "location": [ + 109, + 11, + 109, + 65 + ] + }, + "3701": { + "location": [ + 109, + 11, + 109, + 65 + ] + }, + "3702": { + "location": [ + 109, + 11, + 109, + 65 + ] + }, + "3704": { + "location": [ + 109, + 4, + 109, + 65 + ] + }, + "3705": { + "location": [ + 109, + 4, + 109, + 65 + ] + }, + "3706": { + "location": [ + 102, + 0, + 109, + 65 + ] + }, + "3708": { + "location": [ + 120, + 0, + 127, + 50 + ] + }, + "3709": { + "location": [ + 121, + 27, + 121, + 43 + ] + }, + "3713": { + "location": [ + 121, + 4, + 121, + 43 + ] + }, + "3714": { + "location": [ + 122, + 27, + 122, + 37 + ] + }, + "3716": { + "location": [ + 122, + 27, + 122, + 53 + ] + }, + "3725": { + "location": [ + 122, + 48, + 122, + 52 + ] + }, + "3737": { + "location": [ + 122, + 27, + 122, + 53 + ] + }, + "3750": { + "dev": "dev: EXTERNAL_CALL_FAILED", + "location": null + }, + "3756": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "3759": { + "dev": "dev: RETURNDATASIZE_TOO_SMALL", + "location": null + }, + "3762": { + "location": [ + 122, + 27, + 122, + 53 + ] + }, + "3763": { + "location": [ + 122, + 27, + 122, + 53 + ] + }, + "3764": { + "location": [ + 122, + 4, + 122, + 53 + ] + }, + "3765": { + "location": [ + 122, + 4, + 122, + 53 + ] + }, + "3767": { + "location": [ + 122, + 4, + 122, + 53 + ] + }, + "3768": { + "location": [ + 123, + 7, + 123, + 18 + ] + }, + "3770": { + "location": [ + 123, + 7, + 123, + 23 + ] + }, + "3771": { + "location": [ + 123, + 7, + 123, + 43 + ] + }, + "3774": { + "location": [ + 123, + 7, + 123, + 43 + ] + }, + "3775": { + "location": [ + 123, + 7, + 123, + 43 + ] + }, + "3777": { + "location": [ + 123, + 7, + 123, + 43 + ] + }, + "3780": { + "location": [ + 123, + 7, + 123, + 43 + ] + }, + "3781": { + "location": [ + 123, + 7, + 123, + 43 + ] + }, + "3782": { + "location": [ + 123, + 27, + 123, + 38 + ] + }, + "3784": { + "location": [ + 123, + 27, + 123, + 43 + ] + }, + "3785": { + "location": [ + 123, + 27, + 123, + 43 + ] + }, + "3786": { + "location": [ + 123, + 7, + 123, + 43 + ] + }, + "3787": { + "location": [ + 123, + 4, + 124, + 26 + ] + }, + "3788": { + "location": [ + 123, + 4, + 124, + 26 + ] + }, + "3791": { + "location": [ + 123, + 4, + 124, + 26 + ] + }, + "3792": { + "location": [ + 124, + 15, + 124, + 26 + ] + }, + "3796": { + "location": [ + 124, + 8, + 124, + 26 + ] + }, + "3797": { + "location": [ + 124, + 8, + 124, + 26 + ] + }, + "3798": { + "location": [ + 124, + 8, + 124, + 26 + ] + }, + "3801": { + "location": [ + 124, + 8, + 124, + 26 + ] + }, + "3802": { + "location": [ + 123, + 4, + 124, + 26 + ] + }, + "3803": { + "location": [ + 127, + 11, + 127, + 22 + ] + }, + "3806": { + "location": [ + 127, + 25, + 127, + 36 + ] + }, + "3816": { + "dev": "dev: VALIDATE_UINT256", + "location": null + }, + "3820": { + "dev": "dev: Integer overflow", + "location": null + }, + "3821": { + "dev": "dev: Integer overflow", + "location": null + }, + "3824": { + "dev": "dev: Integer overflow", + "location": null + }, + "3827": { + "location": [ + 127, + 11, + 127, + 36 + ] + }, + "3828": { + "location": [ + 127, + 11, + 127, + 36 + ] + }, + "3829": { + "location": [ + 127, + 39, + 127, + 50 + ] + }, + "3834": { + "dev": "dev: CLAMP_GT_0", + "location": null + }, + "3837": { + "dev": "dev: CLAMP_GT_0", + "location": null + }, + "3839": { + "location": [ + 127, + 11, + 127, + 50 + ] + }, + "3841": { + "location": [ + 127, + 11, + 127, + 50 + ] + }, + "3842": { + "location": [ + 127, + 11, + 127, + 50 + ] + }, + "3843": { + "location": [ + 127, + 11, + 127, + 50 + ] + }, + "3844": { + "location": [ + 127, + 11, + 127, + 50 + ] + }, + "3846": { + "location": [ + 127, + 4, + 127, + 50 + ] + }, + "3847": { + "location": [ + 127, + 4, + 127, + 50 + ] + }, + "3848": { + "location": [ + 120, + 0, + 127, + 50 + ] + }, + "395": { + "location": [ + 61, + 11, + 61, + 17 + ] + }, + "396": { + "location": [ + 61, + 11, + 61, + 17 + ] + }, + "41": { + "location": [ + 11, + 20, + 11, + 27 + ] + }, + "43": { + "location": [ + 11, + 20, + 11, + 27 + ] + }, + "44": { + "location": [ + 11, + 20, + 11, + 27 + ] + }, + "45": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "461": { + "location": [ + 60, + 0, + 61, + 17 + ] + }, + "463": { + "location": [ + 60, + 0, + 61, + 17 + ] + }, + "464": { + "location": [ + 60, + 0, + 61, + 17 + ] + }, + "465": { + "location": [ + 66, + 0, + 67, + 19 + ] + }, + "470": { + "location": [ + 66, + 0, + 67, + 19 + ] + }, + "471": { + "location": [ + 66, + 0, + 67, + 19 + ] + }, + "472": { + "location": [ + 66, + 0, + 67, + 19 + ] + }, + "475": { + "location": [ + 66, + 0, + 67, + 19 + ] + }, + "476": { + "location": [ + 67, + 11, + 67, + 19 + ] + }, + "483": { + "location": [ + 66, + 0, + 67, + 19 + ] + }, + "485": { + "location": [ + 66, + 0, + 67, + 19 + ] + }, + "486": { + "location": [ + 66, + 0, + 67, + 19 + ] + }, + "487": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "492": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "493": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "494": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "497": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "498": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "50": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "500": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "502": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "505": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "51": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "513": { + "dev": "dev: Index out of range", + "location": null + }, + "516": { + "dev": "dev: Index out of range", + "location": null + }, + "517": { + "location": [ + 71, + 13, + 71, + 30 + ] + }, + "519": { + "location": [ + 71, + 13, + 71, + 30 + ] + }, + "52": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "520": { + "location": [ + 72, + 4, + 72, + 18 + ] + }, + "522": { + "location": [ + 72, + 19, + 72, + 29 + ] + }, + "523": { + "location": [ + 72, + 4, + 72, + 30 + ] + }, + "525": { + "location": [ + 72, + 4, + 72, + 30 + ] + }, + "526": { + "location": [ + 72, + 4, + 72, + 30 + ] + }, + "527": { + "location": [ + 72, + 4, + 72, + 30 + ] + }, + "528": { + "location": [ + 72, + 4, + 72, + 30 + ] + }, + "530": { + "location": [ + 72, + 4, + 72, + 30 + ] + }, + "531": { + "location": [ + 72, + 4, + 72, + 30 + ] + }, + "532": { + "location": [ + 72, + 4, + 72, + 40 + ] + }, + "533": { + "location": [ + 72, + 4, + 72, + 40 + ] + }, + "534": { + "location": [ + 74, + 39, + 74, + 45 + ] + }, + "538": { + "location": [ + 72, + 4, + 72, + 40 + ] + }, + "540": { + "location": [ + 72, + 4, + 72, + 40 + ] + }, + "543": { + "dev": "dev: Integer underflow", + "location": null + }, + "546": { + "dev": "dev: Integer underflow", + "location": null + }, + "547": { + "location": [ + 72, + 4, + 72, + 40 + ] + }, + "548": { + "location": [ + 72, + 4, + 72, + 40 + ] + }, + "549": { + "location": [ + 72, + 4, + 72, + 40 + ] + }, + "55": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "550": { + "location": [ + 72, + 4, + 72, + 40 + ] + }, + "551": { + "location": [ + 72, + 4, + 72, + 40 + ] + }, + "553": { + "location": [ + 72, + 4, + 72, + 40 + ] + }, + "554": { + "location": [ + 73, + 4, + 73, + 18 + ] + }, + "556": { + "location": [ + 73, + 19, + 73, + 27 + ] + }, + "559": { + "location": [ + 73, + 4, + 73, + 28 + ] + }, + "56": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "561": { + "location": [ + 73, + 4, + 73, + 28 + ] + }, + "562": { + "location": [ + 73, + 4, + 73, + 28 + ] + }, + "563": { + "location": [ + 73, + 4, + 73, + 28 + ] + }, + "564": { + "location": [ + 73, + 4, + 73, + 28 + ] + }, + "566": { + "location": [ + 73, + 4, + 73, + 28 + ] + }, + "567": { + "location": [ + 73, + 4, + 73, + 28 + ] + }, + "568": { + "location": [ + 73, + 4, + 73, + 38 + ] + }, + "569": { + "location": [ + 73, + 4, + 73, + 38 + ] + }, + "570": { + "location": [ + 74, + 39, + 74, + 45 + ] + }, + "574": { + "location": [ + 73, + 4, + 73, + 38 + ] + }, + "576": { + "location": [ + 73, + 4, + 73, + 38 + ] + }, + "579": { + "dev": "dev: SAFEADD", + "location": null + }, + "58": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "582": { + "dev": "dev: SAFEADD", + "location": null + }, + "583": { + "location": [ + 73, + 4, + 73, + 38 + ] + }, + "584": { + "location": [ + 73, + 4, + 73, + 38 + ] + }, + "585": { + "location": [ + 73, + 4, + 73, + 38 + ] + }, + "586": { + "location": [ + 73, + 4, + 73, + 38 + ] + }, + "587": { + "location": [ + 73, + 4, + 73, + 38 + ] + }, + "589": { + "location": [ + 73, + 4, + 73, + 38 + ] + }, + "590": { + "location": [ + 74, + 29, + 74, + 37 + ] + }, + "592": { + "location": [ + 74, + 4, + 74, + 46 + ] + }, + "593": { + "location": [ + 74, + 17, + 74, + 27 + ] + }, + "594": { + "location": [ + 74, + 4, + 74, + 46 + ] + }, + "60": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "627": { + "location": [ + 74, + 39, + 74, + 45 + ] + }, + "63": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "635": { + "location": [ + 74, + 4, + 74, + 46 + ] + }, + "637": { + "location": [ + 74, + 4, + 74, + 46 + ] + }, + "638": { + "location": [ + 75, + 11, + 75, + 15 + ] + }, + "645": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "647": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "648": { + "location": [ + 71, + 0, + 75, + 15 + ] + }, + "649": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "654": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "655": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "656": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "659": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "660": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "662": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "664": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "667": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "675": { + "dev": "dev: Index out of range", + "location": null + }, + "678": { + "dev": "dev: Index out of range", + "location": null + }, + "679": { + "location": [ + 79, + 12, + 79, + 28 + ] + }, + "681": { + "location": [ + 79, + 12, + 79, + 28 + ] + }, + "682": { + "location": [ + 81, + 38, + 81, + 44 + ] + }, + "685": { + "location": [ + 80, + 4, + 80, + 18 + ] + }, + "687": { + "location": [ + 80, + 19, + 80, + 29 + ] + }, + "688": { + "location": [ + 80, + 4, + 80, + 30 + ] + }, + "690": { + "location": [ + 80, + 4, + 80, + 30 + ] + }, + "691": { + "location": [ + 80, + 4, + 80, + 30 + ] + }, + "692": { + "location": [ + 80, + 4, + 80, + 30 + ] + }, + "693": { + "location": [ + 80, + 4, + 80, + 30 + ] + }, + "695": { + "location": [ + 80, + 4, + 80, + 30 + ] + }, + "696": { + "location": [ + 80, + 4, + 80, + 30 + ] + }, + "698": { + "location": [ + 80, + 31, + 80, + 38 + ] + }, + "71": { + "dev": "dev: Index out of range", + "location": null + }, + "710": { + "location": [ + 80, + 4, + 80, + 39 + ] + }, + "711": { + "location": [ + 80, + 4, + 80, + 39 + ] + }, + "713": { + "location": [ + 81, + 29, + 81, + 36 + ] + }, + "715": { + "location": [ + 81, + 4, + 81, + 45 + ] + }, + "716": { + "location": [ + 81, + 17, + 81, + 27 + ] + }, + "717": { + "location": [ + 81, + 4, + 81, + 45 + ] + }, + "74": { + "dev": "dev: Index out of range", + "location": null + }, + "750": { + "location": [ + 81, + 38, + 81, + 44 + ] + }, + "758": { + "location": [ + 81, + 4, + 81, + 45 + ] + }, + "760": { + "location": [ + 81, + 4, + 81, + 45 + ] + }, + "761": { + "location": [ + 82, + 11, + 82, + 15 + ] + }, + "768": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "770": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "771": { + "location": [ + 79, + 0, + 82, + 15 + ] + }, + "772": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "777": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "778": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "779": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "782": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "783": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "785": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "787": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "790": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "798": { + "dev": "dev: Index out of range", + "location": null + }, + "801": { + "dev": "dev: Index out of range", + "location": null + }, + "802": { + "location": [ + 86, + 17, + 86, + 32 + ] + }, + "804": { + "location": [ + 86, + 17, + 86, + 32 + ] + }, + "812": { + "dev": "dev: Index out of range", + "location": null + }, + "815": { + "dev": "dev: Index out of range", + "location": null + }, + "816": { + "location": [ + 86, + 34, + 86, + 51 + ] + }, + "818": { + "location": [ + 86, + 34, + 86, + 51 + ] + }, + "819": { + "location": [ + 87, + 4, + 87, + 18 + ] + }, + "821": { + "location": [ + 87, + 19, + 87, + 25 + ] + }, + "824": { + "location": [ + 87, + 4, + 87, + 26 + ] + }, + "826": { + "location": [ + 87, + 4, + 87, + 26 + ] + }, + "827": { + "location": [ + 87, + 4, + 87, + 26 + ] + }, + "828": { + "location": [ + 87, + 4, + 87, + 26 + ] + }, + "829": { + "location": [ + 87, + 4, + 87, + 26 + ] + }, + "831": { + "location": [ + 87, + 4, + 87, + 26 + ] + }, + "832": { + "location": [ + 87, + 4, + 87, + 26 + ] + }, + "834": { + "location": [ + 87, + 27, + 87, + 37 + ] + }, + "844": { + "location": [ + 87, + 4, + 87, + 38 + ] + }, + "845": { + "location": [ + 87, + 4, + 87, + 38 + ] + }, + "846": { + "location": [ + 87, + 4, + 87, + 48 + ] + }, + "847": { + "location": [ + 87, + 4, + 87, + 48 + ] + }, + "848": { + "location": [ + 90, + 35, + 90, + 41 + ] + }, + "852": { + "location": [ + 87, + 4, + 87, + 48 + ] + }, + "854": { + "location": [ + 87, + 4, + 87, + 48 + ] + }, + "857": { + "dev": "dev: Integer underflow", + "location": null + }, + "860": { + "dev": "dev: Integer underflow", + "location": null + }, + "861": { + "location": [ + 87, + 4, + 87, + 48 + ] + }, + "862": { + "location": [ + 87, + 4, + 87, + 48 + ] + }, + "863": { + "location": [ + 87, + 4, + 87, + 48 + ] + }, + "864": { + "location": [ + 87, + 4, + 87, + 48 + ] + }, + "865": { + "location": [ + 87, + 4, + 87, + 48 + ] + }, + "867": { + "location": [ + 87, + 4, + 87, + 48 + ] + }, + "868": { + "location": [ + 88, + 4, + 88, + 18 + ] + }, + "870": { + "location": [ + 88, + 19, + 88, + 25 + ] + }, + "873": { + "location": [ + 88, + 4, + 88, + 26 + ] + }, + "875": { + "location": [ + 88, + 4, + 88, + 26 + ] + }, + "876": { + "location": [ + 88, + 4, + 88, + 26 + ] + }, + "877": { + "location": [ + 88, + 4, + 88, + 26 + ] + }, + "878": { + "location": [ + 88, + 4, + 88, + 26 + ] + }, + "880": { + "location": [ + 88, + 4, + 88, + 26 + ] + }, + "881": { + "location": [ + 88, + 4, + 88, + 26 + ] + }, + "882": { + "location": [ + 88, + 4, + 88, + 36 + ] + }, + "883": { + "location": [ + 88, + 4, + 88, + 36 + ] + }, + "884": { + "location": [ + 90, + 35, + 90, + 41 + ] + }, + "888": { + "location": [ + 88, + 4, + 88, + 36 + ] + }, + "890": { + "location": [ + 88, + 4, + 88, + 36 + ] + }, + "893": { + "dev": "dev: Integer underflow", + "location": null + }, + "896": { + "dev": "dev: Integer underflow", + "location": null + }, + "897": { + "location": [ + 88, + 4, + 88, + 36 + ] + }, + "898": { + "location": [ + 88, + 4, + 88, + 36 + ] + }, + "899": { + "location": [ + 88, + 4, + 88, + 36 + ] + }, + "900": { + "location": [ + 88, + 4, + 88, + 36 + ] + }, + "901": { + "location": [ + 88, + 4, + 88, + 36 + ] + }, + "903": { + "location": [ + 88, + 4, + 88, + 36 + ] + }, + "904": { + "location": [ + 89, + 4, + 89, + 18 + ] + }, + "906": { + "location": [ + 89, + 19, + 89, + 27 + ] + }, + "909": { + "location": [ + 89, + 4, + 89, + 28 + ] + }, + "911": { + "location": [ + 89, + 4, + 89, + 28 + ] + }, + "912": { + "location": [ + 89, + 4, + 89, + 28 + ] + }, + "913": { + "location": [ + 89, + 4, + 89, + 28 + ] + }, + "914": { + "location": [ + 89, + 4, + 89, + 28 + ] + }, + "916": { + "location": [ + 89, + 4, + 89, + 28 + ] + }, + "917": { + "location": [ + 89, + 4, + 89, + 28 + ] + }, + "918": { + "location": [ + 89, + 4, + 89, + 38 + ] + }, + "919": { + "location": [ + 89, + 4, + 89, + 38 + ] + }, + "920": { + "location": [ + 90, + 35, + 90, + 41 + ] + }, + "924": { + "location": [ + 89, + 4, + 89, + 38 + ] + }, + "926": { + "location": [ + 89, + 4, + 89, + 38 + ] + }, + "929": { + "dev": "dev: SAFEADD", + "location": null + }, + "932": { + "dev": "dev: SAFEADD", + "location": null + }, + "933": { + "location": [ + 89, + 4, + 89, + 38 + ] + }, + "934": { + "location": [ + 89, + 4, + 89, + 38 + ] + }, + "935": { + "location": [ + 89, + 4, + 89, + 38 + ] + }, + "936": { + "location": [ + 89, + 4, + 89, + 38 + ] + }, + "937": { + "location": [ + 89, + 4, + 89, + 38 + ] + }, + "939": { + "location": [ + 89, + 4, + 89, + 38 + ] + }, + "940": { + "location": [ + 90, + 25, + 90, + 33 + ] + }, + "942": { + "location": [ + 90, + 4, + 90, + 42 + ] + }, + "943": { + "location": [ + 90, + 17, + 90, + 23 + ] + }, + "945": { + "location": [ + 90, + 4, + 90, + 42 + ] + }, + "946": { + "location": [ + 90, + 4, + 90, + 42 + ] + }, + "979": { + "location": [ + 90, + 35, + 90, + 41 + ] + }, + "98": { + "location": [ + 12, + 18, + 12, + 43 + ] + }, + "987": { + "location": [ + 90, + 4, + 90, + 42 + ] + }, + "989": { + "location": [ + 90, + 4, + 90, + 42 + ] + }, + "990": { + "location": [ + 91, + 11, + 91, + 15 + ] + }, + "997": { + "location": [ + 86, + 0, + 91, + 15 + ] + }, + "999": { + "location": [ + 86, + 0, + 91, + 15 + ] + } + }, + "runtimeBytecode": { + "bytecode": "0x6003361161000c57610e03565b5f3560e01c34610f0a576318160ddd811861002c575f5460405260206040f35b6370a0823181186100655760243610610f0a576004358060a01c610f0a5760405260016040516020525f5260405f205460605260206060f35b63dd62ed3e81186100bb5760443610610f0a576004358060a01c610f0a576040526024358060a01c610f0a5760605260026040516020525f5260405f20806060516020525f5260405f2090505460805260206080f35b6338d52e0f81186100d25760035460405260206040f35b6306fdde03811861015157602080608052600a6040527f54657374205661756c740000000000000000000000000000000000000000000060605260408160800181516020830160208301815181525050808252508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506080f35b6395d89b4181186101d05760208060805260056040527f765445535400000000000000000000000000000000000000000000000000000060605260408160800181516020830160208301815181525050808252508051806020830101601f825f03163682375050601f19601f8251602001011690509050810190506080f35b63313ce56781186101e657601260405260206040f35b63a9059cbb81186102885760443610610f0a576004358060a01c610f0a576040526001336020525f5260405f208054602435808203828111610f0a579050905081555060016040516020525f5260405f208054602435808201828110610f0a5790509050815550604051337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60243560605260206060a3600160605260206060f35b63095ea7b381186103035760443610610f0a576004358060a01c610f0a576040526024356002336020525f5260405f20806040516020525f5260405f20905055604051337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560243560605260206060a3600160605260206060f35b6323b872dd81186103e85760643610610f0a576004358060a01c610f0a576040526024358060a01c610f0a5760605260026040516020525f5260405f2080336020525f5260405f2090508054604435808203828111610f0a579050905081555060016040516020525f5260405f208054604435808203828111610f0a579050905081555060016060516020525f5260405f208054604435808201828110610f0a57905090508155506060516040517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60443560805260206080a3600160805260206080f35b6301e1d11481186104295760206003546370a0823160405230606052602060406024605c845afa61041b573d5f5f3e3d5ffd5b60203d10610f0a5760409050f35b6307a2d13a81186104525760243610610f0a57602060043560405261044e60c0610e07565b60c0f35b63c6e6f592811861047b5760243610610f0a57602060043560405261047760e0610e7c565b60e0f35b63402d267d81186104c65760243610610f0a576004358060a01c610f0a576040527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60605260206060f35b63ef8b30f781186104ef5760243610610f0a5760206004356040526104eb60e0610e7c565b60e0f35b63b6b55f25811861050b5760243610610f0a573360e05261052d565b636e553f6581186106495760443610610f0a576024358060a01c610f0a5760e0525b60043560405261053e610120610e7c565b61012051610100526003546323b872dd6101205233610140523061016052600435610180526020610120606461013c5f855af161057d573d5f5f3e3d5ffd5b60203d10610f0a57610120518060011c610f0a576101a0526101a050505f5461010051808201828110610f0a57905090505f55600160e0516020525f5260405f20805461010051808201828110610f0a579050905081555060e0515f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef61010051610120526020610120a360e051337fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d76004356101205261010051610140526040610120a36020610100f35b63c63d75b681186106945760243610610f0a576004358060a01c610f0a576040527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60605260206060f35b63b3d7f6b9811861071d5760243610610f0a576004356040526106b760e0610e07565b60e05160c05260c0516106fe576003546370a0823160e0523061010052602060e0602460fc845afa6106eb573d5f5f3e3d5ffd5b60203d10610f0a5760e090505115610700565b5f5b156107165760043561012052602061012061071b565b602060c05bf35b63a0712d6881186107395760243610610f0a573360c05261075b565b6394bf804d81186108c45760443610610f0a576024358060a01c610f0a5760c0525b60043560405261076c610100610e07565b6101005160e05260e0516107b8576003546370a082316101005230610120526020610100602461011c845afa6107a4573d5f5f3e3d5ffd5b60203d10610f0a57610100905051156107ba565b5f5b156107c65760043560e0525b6003546323b872dd610100523361012052306101405260e051610160526020610100606461011c5f855af16107fd573d5f5f3e3d5ffd5b60203d10610f0a57610100518060011c610f0a576101805261018050505f54600435808201828110610f0a57905090505f55600160c0516020525f5260405f208054600435808201828110610f0a579050905081555060c0515f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef600435610100526020610100a360c051337fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d760e05161010052600435610120526040610100a3602060e0f35b63ce96cb77811861090f5760243610610f0a576004358060a01c610f0a576040527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60605260206060f35b630a28a477811861096a5760243610610f0a57600435604052610933610100610e7c565b6101005160e05260043560e0511861094d575f541561094f565b5f5b15610963575f610100526020610100610968565b602060e05bf35b632e1a7d4d811861098b5760243610610f0a573360e05233610100526109e6565b62f714ce81186109b55760443610610f0a576024358060a01c610f0a5760e05233610100526109e6565b63b460af948118610b625760643610610f0a576024358060a01c610f0a5760e0526044358060a01c610f0a57610100525b6004356040526109f7610140610e7c565b61014051610120526004356101205118610a13575f5415610a15565b5f5b15610a1e575f5ffd5b336101005114610a5c576002610100516020525f5260405f2080336020525f5260405f209050805461012051808203828111610f0a57905090508155505b5f5461012051808203828111610f0a57905090505f556001610100516020525f5260405f20805461012051808203828111610f0a579050905081555060035463a9059cbb6101405260e05161016052600435610180526020610140604461015c5f855af1610acc573d5f5f3e3d5ffd5b60203d10610f0a57610140518060011c610f0a576101a0526101a050505f610100517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef61012051610140526020610140a36101005160e051337ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db6004356101405261012051610160526040610140a46020610120f35b63d905777e8118610bad5760243610610f0a576004358060a01c610f0a576040527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60605260206060f35b634cdad5068118610bd65760243610610f0a576020600435604052610bd260c0610e07565b60c0f35b63db006a758118610bf65760243610610f0a573360c0523360e052610c50565b637bde82f28118610c205760443610610f0a576024358060a01c610f0a5760c0523360e052610c50565b63ba0876528118610da55760643610610f0a576024358060a01c610f0a5760c0526044358060a01c610f0a5760e0525b3360e05114610c8b57600260e0516020525f5260405f2080336020525f5260405f2090508054600435808203828111610f0a57905090508155505b600435604052610c9c610120610e07565b61012051610100525f54600435808203828111610f0a57905090505f55600160e0516020525f5260405f208054600435808203828111610f0a579050905081555060035463a9059cbb6101205260c0516101405261010051610160526020610120604461013c5f855af1610d12573d5f5f3e3d5ffd5b60203d10610f0a57610120518060011c610f0a576101805261018050505f60e0517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef600435610120526020610120a360e05160c051337ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db6101005161012052600435610140526040610120a46020610100f35b63f1dc5be48118610e015760243610610f0a5760035463a9059cbb60405233606052600435608052602060406044605c5f855af1610de5573d5f5f3e3d5ffd5b60203d10610f0a576040518060011c610f0a5760a05260a05050005b505b5f5ffd5b5f54606052606051610e1c575f815250610e7a565b6040516003546370a082316080523060a052602060806024609c845afa610e45573d5f5f3e3d5ffd5b60203d10610f0a576080905051808202811583838304141715610f0a57905090506060518015610f0a57808204905090508152505b565b5f546060526003546370a0823160a0523060c052602060a0602460bc845afa610ea7573d5f5f3e3d5ffd5b60203d10610f0a5760a0905051608052608051610ec5576001610eca565b606051155b15610eda57604051815250610f08565b604051606051808202811583838304141715610f0a57905090506080518015610f0a57808204905090508152505b565b5f80fda165767970657283000309000b" + }, + "sourceId": "VyperVault.vy", + "sourcemap": "-1:-1:0:-;;;;:::-;;:::-;:::-;;;;;;;:::-;158:7;;;;:::-;-1:-1;;;;;158:7;;:::-;185:25;;;;:::-;;;-1:-1;185:25;:::-;-1:-1;;;;;;:::-;;;;;;;;;;;;;;;;;185:25;;:::-;230:43;;;;:::-;;;-1:-1;230:43;:::-;-1:-1;;;;;;:::-;;;;;;;;;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;230:43;;:::-;618:5;;;;:::-;-1:-1;;;;;618:5;;:::-;974:41;;;;:::-;-1:-1;;;;;;1011:4;-1:-1;;1011:4;;-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;974:41;;:::-;1034:44;;;;:::-;-1:-1;;;;;;1072:6;-1:-1;;1072:6;;-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1034:44;;:::-;1097;;;;:::-;1133:8;-1:-1;;;1097:44;;:::-;1154:200;;;;:::-;;;-1:-1;1154:200;:::-;-1:-1;;;;;;:::-;1167:17;;1216:14;1231:10;1216:26;;;;;;;:36;;1331:6;-1:-1;;1216:36;-1:-1;1216:36;-1:-1;;;:::-;1216:36;;;;;-1:-1;1216:36;1257:14;1272:8;-1:-1;1257:24;;;;;;;:34;;1331:6;-1:-1;;1257:34;-1:-1;1257:34;-1:-1;;;:::-;1257:34;;;;;-1:-1;1257:34;1321:8;1296:42;1309:10;1296:42;1331:6;-1:-1;;;;1296:42;;1350:4;-1:-1;;;1154:200;;:::-;1367:166;;;;:::-;;;-1:-1;1367:166;:::-;-1:-1;;;;;;:::-;1379:16;;1510:6;-1:-1;1427:14;1442:10;1427:26;;;;;;;-1:-1;1454:7;-1:-1;;;;;;;;1427:35;;-1:-1;1501:7;1476:41;1489:10;1476:41;1510:6;-1:-1;;;;1476:41;;1529:4;-1:-1;;;1367:166;;:::-;1546:262;;;;:::-;;;-1:-1;1546:262;:::-;-1:-1;;;;;;:::-;1563:15;;-1:-1;;;;;;:::-;1580:17;;1629:14;1644:6;-1:-1;1629:22;;;;;;;-1:-1;1652:10;-1:-1;;;;;;;1629:34;;:44;;1785:6;-1:-1;;1629:44;-1:-1;1629:44;-1:-1;;;:::-;1629:44;;;;;-1:-1;1629:44;1678:14;1693:6;-1:-1;1678:22;;;;;;;:32;;1785:6;-1:-1;;1678:32;-1:-1;1678:32;-1:-1;;;:::-;1678:32;;;;;-1:-1;1678:32;1715:14;1730:8;-1:-1;1715:24;;;;;;;:34;;1785:6;-1:-1;;1715:34;-1:-1;1715:34;-1:-1;;;:::-;1715:34;;;;;-1:-1;1715:34;1775:8;1754:38;1767:6;1754:38;;1785:6;-1:-1;;;;1754:38;;1804:4;-1:-1;;;1546:262;;:::-;1827:67;;;;:::-;;1868:10;:26;-1:-1;;;1889:4;-1:-1;;;;;;1868:26;-1:-1;;;:::-;;;;;;;;:::-;;;;;:::-;;1868:26;;1827:67;:::-;2291:99;;;;:::-;;;-1:-1;2291:99;:::-;;2378:11;-1:-1;;2356:34;-1:-1;2356:34;;:::i;:::-;;2291:99;:::-;2793;;;;:::-;;;-1:-1;2793:99;:::-;;2880:11;-1:-1;;2858:34;-1:-1;2858:34;;:::i;:::-;;2793:99;:::-;2911:65;;;;:::-;;;-1:-1;2911:65;:::-;-1:-1;;;;;;:::-;2926:14;;2965:11;-1:-1;;;2911:65;;:::-;2995:88;;;;:::-;;;-1:-1;2995:88;:::-;;3076:6;-1:-1;;3054:29;-1:-1;3054:29;;:::i;:::-;;2995:88;:::-;3096:370;;;;:::-;;;-1:-1;3096:370;:::-;3143:10;3125:17;;3096:370;:::-;:::-;;;;;:::-;;;-1:-1;3096:370;:::-;-1:-1;;;;;;:::-;3125:17;;3096:370::-;3433:6;-1:-1;;3189:29;-1:-1;3189:29;;:::i;:::-;;3171:47;;;3223:10;:49;-1:-1;;;3247:10;-1:-1;;3259:4;-1:-1;;3433:6;-1:-1;;;;;;;;3223:49;-1:-1;;;:::-;;;;;;;;:::-;;;;;:::-;;;;;;;:::-;;;;3223:49;3096:370;3278:16;-1:-1;3298:6;-1:-1;;;;;;;;:::-;;;3278:26;;:16;-1:-1;3309:14;3324:8;-1:-1;3309:24;;;;;;;-1:-1;;3337:6;-1:-1;;;;;;;;:::-;;;3309:34;;-1:-1;;3309:34;3377:8;3348:46;;;3387:6;-1:-1;;;;3348:46;;3423:8;3399:49;3411:10;3399:49;3433:6;-1:-1;;;3441:6;-1:-1;;;;3399:49;;3096:370;3460:6;3096:370;:::-;3485:62;;;;:::-;;;-1:-1;3485:62;:::-;-1:-1;;;;;;:::-;3497:14;;3536:11;-1:-1;;;3485:62;;:::-;3566:324;;;;:::-;;;-1:-1;3566:324;:::-;3814:6;-1:-1;;3633:29;-1:-1;3633:29;;:::i;:::-;;3615:47;;;3750:6;:11;:47;:::-;3766:10;:26;-1:-1;;;3787:4;-1:-1;;;;;;3766:26;-1:-1;;;:::-;;;;;;;;:::-;;;;;:::-;;3766:26;;:31;;3750:47;:::-;:::-;;:::-;3747:73;;:::-;3814:6;-1:-1;;;;;;:::-;3747:73::-;3566:324;3884:6;3566:324::-;;:::-;3903:500;;;;:::-;;;-1:-1;3903:500;:::-;3947:10;3929:17;;3903:500;:::-;:::-;;;;;:::-;;;-1:-1;3903:500;:::-;-1:-1;;;;;;:::-;3929:17;;3903:500::-;4378:6;-1:-1;;3993:29;-1:-1;3993:29;;:::i;:::-;;3975:47;;;4031:6;:11;:47;:::-;4047:10;:26;-1:-1;;;4068:4;-1:-1;;;;;;4047:26;-1:-1;;;:::-;;;;;;;;:::-;;;;;:::-;;4047:26;;:31;;4031:47;:::-;:::-;;:::-;4028:75;;:::-;4378:6;-1:-1;4088:6;-1:-1;4028:75::-;4160:10;:49;-1:-1;;;4184:10;-1:-1;;4196:4;-1:-1;;4202:6;-1:-1;;;;;;;;4160:49;-1:-1;;;:::-;;;;;;;;:::-;;;;;:::-;;;;;;;:::-;;;;4160:49;3903:500;4215:16;:26;4378:6;-1:-1;;4215:26;-1:-1;4215:26;-1:-1;;;:::-;4215:26;;;;:16;:26;4246:14;4261:8;-1:-1;4246:24;;;;;;;:34;;4378:6;-1:-1;;4246:34;-1:-1;4246:34;-1:-1;;;:::-;4246:34;;;;;-1:-1;4246:34;4314:8;4285:46;;;4378:6;-1:-1;;;;4285:46;;4360:8;4336:49;4348:10;4336:49;4370:6;-1:-1;;;4378:6;-1:-1;;;;4336:49;;3903:500;4397:6;3903:500;:::-;4422:66;;;;:::-;;;-1:-1;4422:66;:::-;-1:-1;;;;;;:::-;4438:14;;4477:11;-1:-1;;;4422:66;;:::-;4551:294;;;;:::-;;;-1:-1;4551:294;:::-;4749:6;-1:-1;;4622:29;-1:-1;4622:29;;:::i;:::-;;4604:47;;;4749:6;4739:16;:6;:16;;:42;:::-;4760:16;:21;;4739:42;:::-;:::-;;:::-;4736:63;;:::-;4798:1;-1:-1;;;;;:::-;4736:63::-;4551:294;4839:6;4551:294::-;;:::-;4858:636;;;;:::-;;;-1:-1;4858:636;:::-;4906:10;4888:17;;4933:10;4918:14;;4858:636;:::-;:::-;;;;;:::-;;;-1:-1;4858:636;:::-;-1:-1;;;;;;:::-;4888:17;;4933:10;4918:14;;4858:636;:::-;:::-;;;;;:::-;;;-1:-1;4858:636;:::-;-1:-1;;;;;;:::-;4888:17;;-1:-1;;;;;;:::-;4918:14;;4858:636::-;5461:6;-1:-1;;4979:29;-1:-1;4979:29;;:::i;:::-;;4961:47;;;5461:6;5096:16;:6;:16;;:42;:::-;5117:16;:21;;5096:42;:::-;:::-;;:::-;5093:60;;:::-;-1:-1;;5148:5;5093:60::-;5192:10;5183:5;-1:-1;;5180:75;:::-;5212:14;5227:5;-1:-1;5212:21;;;;;;;-1:-1;5234:10;-1:-1;;;;;;;5212:33;;-1:-1;;5249:6;-1:-1;;;;;;;;:::-;;;5212:43;;-1:-1;;5212:43;5180:75::-;5261:16;-1:-1;5281:6;-1:-1;;;;;;;;:::-;;;5261:26;;:16;-1:-1;5292:14;5307:5;-1:-1;5292:21;;;;;;;-1:-1;;5317:6;-1:-1;;;;;;;;:::-;;;5292:31;;-1:-1;;5292:31;5329:10;:37;-1:-1;;;5349:8;-1:-1;;;5461:6;-1:-1;;;;;;;;5329:37;-1:-1;;;:::-;;;;;;;;:::-;;;;;:::-;;;;;;;:::-;;;;5329:37;4858:636;5371:43;5384:5;5371:43;;5407:6;-1:-1;;;;5371:43;;5454:5;5419:57;5444:8;5419:57;5432:10;5419:57;5461:6;-1:-1;;;5469:6;-1:-1;;;;5419:57;;4858:636;5488:6;4858:636;:::-;5513:64;;;;:::-;;;-1:-1;5513:64;:::-;-1:-1;;;;;;:::-;5527:14;;5566:11;-1:-1;;;5513:64;;:::-;5630:87;;;;:::-;;;-1:-1;5630:87;:::-;;5710:6;-1:-1;;5688:29;-1:-1;5688:29;;:::i;:::-;;5630:87;:::-;5730:467;;;;:::-;;;-1:-1;5730:467;:::-;5776:10;5758:17;;5803:10;5788:14;;5730:467;:::-;:::-;;;;;:::-;;;-1:-1;5730:467;:::-;-1:-1;;;;;;:::-;5758:17;;5803:10;5788:14;;5730:467;:::-;:::-;;;;;:::-;;;-1:-1;5730:467;:::-;-1:-1;;;;;;:::-;5758:17;;-1:-1;;;;;;:::-;5788:14;;5730:467::-;5843:10;5834:5;-1:-1;;5831:75;:::-;5863:14;5878:5;-1:-1;5863:21;;;;;;;-1:-1;5885:10;-1:-1;;;;;;;5863:33;;:43;;6172:6;-1:-1;;5863:43;-1:-1;5863:43;-1:-1;;;:::-;5863:43;;;;;-1:-1;5863:43;5831:75::-;6172:6;-1:-1;;5930:29;-1:-1;5930:29;;:::i;:::-;;5912:47;;;5964:16;:26;6172:6;-1:-1;;5964:26;-1:-1;5964:26;-1:-1;;;:::-;5964:26;;;;:16;:26;5995:14;6010:5;-1:-1;5995:21;;;;;;;:31;;6172:6;-1:-1;;5995:31;-1:-1;5995:31;-1:-1;;;:::-;5995:31;;;;;-1:-1;5995:31;6032:10;:37;-1:-1;;;6052:8;-1:-1;;;6062:6;-1:-1;;;;;;;;6032:37;-1:-1;;;:::-;;;;;;;;:::-;;;;;:::-;;;;;;;:::-;;;;6032:37;5730:467;6074:43;6087:5;6074:43;;6172:6;-1:-1;;;;6074:43;;6157:5;6122:57;6147:8;6122:57;6135:10;6122:57;6164:6;-1:-1;;;6172:6;-1:-1;;;;6122:57;;5730:467;6191:6;5730:467;:::-;6210:154;;;;:::-;;;-1:-1;6210:154;:::-;6325:10;:39;-1:-1;;;6345:10;-1:-1;;6357:6;-1:-1;;;;;;;;6325:39;-1:-1;;;:::-;;;;;;;;:::-;;;;;:::-;;;;;;;:::-;;;;6325:39;6210:154;;:::-;-1:-1;:::-;;;;1913:359::-;1995:16;-1:-1;;1972:39;2019:11;:16;2016:37;:::-;2052:1;-1:-1;2045:8;;;:::o;2016:37::-;2218:11;:40;2232:10;:26;-1:-1;;;2253:4;-1:-1;;;;;;2232:26;-1:-1;;;:::-;;;;;;;;:::-;;;;;:::-;;2232:26;;-1:-1;;2218:40;-1:-1;;;2218:40;-1:-1;;;;;;;:::-;2218:40;;;;2261:11;-1:-1;;;;:::-;;2218:54;-1:-1;2218:54;;;;-1:-1;2211:61;;1913:359::-;-1:-1::-;2409:365::-;2491:16;-1:-1;;2468:39;2535:10;:26;-1:-1;;;2556:4;-1:-1;;;;;;2535:26;-1:-1;;;:::-;;;;;;;;:::-;;;;;:::-;;2535:26;;2512:49;;;2569:11;:16;:36;:::-;;;:::-;:::-;2589:11;:16;;2569:36::-;2566:67;;:::-;2622:11;-1:-1;;2615:18;;;:::o;2566:67::-;2735:11;-1:-1;2749:11;-1:-1;;;;;;;;;;;;;;:::-;;;2735:25;;2763:11;-1:-1;;;;:::-;;2735:39;-1:-1;2735:39;;;;-1:-1;2728:46;;2409:365::-;-1:-1::-;:::-;;;", + "userdoc": {} +} \ No newline at end of file diff --git a/tests/test_multisend.py b/tests/test_multisend.py new file mode 100644 index 0000000..32ece63 --- /dev/null +++ b/tests/test_multisend.py @@ -0,0 +1,31 @@ +import pytest +from ape import networks + +from ape_safe import MultiSend +from ape_safe.exceptions import SafeLogicError + + +def test_asset(vault, token): + assert vault.asset() == token + + +def test_default_operation(safe, token, vault): + with networks.ethereum.local.use_provider("foundry"): + ms = MultiSend() + ms.inject() + amount = token.balanceOf(safe) + ms.add(token.approve, vault, safe) + ms.add(vault.transfer, safe, amount) + receipt = ms(sender=safe) + assert receipt.txn_hash + + +def test_no_operation(safe, token, vault): + with networks.ethereum.local.use_provider("foundry"): + ms = MultiSend() + ms.inject() + amount = token.balanceOf(safe) + ms.add(token.approve, vault, safe) + ms.add(vault.transfer, safe, amount) + with pytest.raises(SafeLogicError, match="Safe transaction failed"): + ms(sender=safe, operation=0) From 662d6904d2d677a63d14304e8115e13791ab395b Mon Sep 17 00:00:00 2001 From: Dalena Date: Thu, 12 Oct 2023 13:28:02 -0500 Subject: [PATCH 043/134] chore: mockclient mypy --- ape_safe/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index ed02c81..dd0b842 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -359,7 +359,7 @@ def post_signature( class MockSafeClient(BaseSafeClient, ManagerAccessMixin): def __init__(self, contract: ContractInstance): self.contract = contract - self.transactions: Dict[SafeTxID, SafeApiTxData] = {} + self.transactions: Dict[SafeTx | SafeTxID, SafeApiTxData] = {} self.transactions_by_nonce: Dict[int, List[SafeTxID]] = {} @property From a9f2646026efc360d6b53331ec2e3f4ad1fbcb6a Mon Sep 17 00:00:00 2001 From: Dalena Date: Thu, 12 Oct 2023 15:41:48 -0500 Subject: [PATCH 044/134] chore: change to union --- ape_safe/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index dd0b842..0d414b1 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -359,7 +359,7 @@ def post_signature( class MockSafeClient(BaseSafeClient, ManagerAccessMixin): def __init__(self, contract: ContractInstance): self.contract = contract - self.transactions: Dict[SafeTx | SafeTxID, SafeApiTxData] = {} + self.transactions: Union[SafeTx, SafeTxID, SafeApiTxData] = {} self.transactions_by_nonce: Dict[int, List[SafeTxID]] = {} @property From a8c3985b17722163b5f1cb1ef06cfd1218092b48 Mon Sep 17 00:00:00 2001 From: Dalena Date: Thu, 12 Oct 2023 15:56:39 -0500 Subject: [PATCH 045/134] fix: remove extra parenthesis --- ape_safe/multisend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ape_safe/multisend.py b/ape_safe/multisend.py index 02a5376..182763b 100644 --- a/ape_safe/multisend.py +++ b/ape_safe/multisend.py @@ -214,7 +214,7 @@ def __call__(self, **txn_kwargs) -> ReceiptAPI: :class:`~ape.api.transactions.ReceiptAPI` """ self._validate_calls(**txn_kwargs) - if "operation" not in txn_kwargs and (not txn_kwargs.get("impersonate", False)): + if "operation" not in txn_kwargs and not txn_kwargs.get("impersonate", False): txn_kwargs["operation"] = 1 return self.handler(b"".join(self.encoded_calls), **txn_kwargs) @@ -228,7 +228,7 @@ def as_transaction(self, **txn_kwargs) -> TransactionAPI: self._validate_calls(**txn_kwargs) # NOTE: Will fail using `self.handler.as_transaction` because handler # expects to be called only via delegatecall - if "operation" not in txn_kwargs and (not txn_kwargs.get("impersonate", False)): + if "operation" not in txn_kwargs and not txn_kwargs.get("impersonate", False): txn_kwargs["operation"] = 1 return self.network_manager.ecosystem.create_transaction( receiver=self.handler.contract.address, From f5b1e4dc9339555af83df2b68232a2527a3a3b07 Mon Sep 17 00:00:00 2001 From: Dalena Date: Thu, 12 Oct 2023 15:58:00 -0500 Subject: [PATCH 046/134] fix: network fixture --- tests/conftest.py | 12 +++++++++--- tests/test_multisend.py | 30 ++++++++++++++---------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index af072d9..dcfc386 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,7 @@ from ape_safe.accounts import SafeAccount -projects_directory = Path(__file__).parent / "contracts" +contracts_directory = Path(__file__).parent / "contracts" @pytest.fixture(scope="session") @@ -102,11 +102,17 @@ def safe(safe_data_file): @pytest.fixture def token(deployer): - contract = ContractType.parse_file(projects_directory / "Token.json") + contract = ContractType.parse_file(contracts_directory / "Token.json") return deployer.deploy(ContractContainer(contract)) @pytest.fixture def vault(deployer, token): - vault = ContractContainer(ContractType.parse_file(projects_directory / "VyperVault.json")) + vault = ContractContainer(ContractType.parse_file(contracts_directory / "VyperVault.json")) return deployer.deploy(vault, token) + + +@pytest.fixture +def foundry(networks): + with networks.ethereum.local.use_provider("foundry") as provider: + yield provider \ No newline at end of file diff --git a/tests/test_multisend.py b/tests/test_multisend.py index 32ece63..d68881d 100644 --- a/tests/test_multisend.py +++ b/tests/test_multisend.py @@ -10,22 +10,20 @@ def test_asset(vault, token): def test_default_operation(safe, token, vault): - with networks.ethereum.local.use_provider("foundry"): - ms = MultiSend() - ms.inject() - amount = token.balanceOf(safe) - ms.add(token.approve, vault, safe) - ms.add(vault.transfer, safe, amount) - receipt = ms(sender=safe) - assert receipt.txn_hash + ms = MultiSend() + ms.inject() + amount = token.balanceOf(safe) + ms.add(token.approve, vault, safe) + ms.add(vault.transfer, safe, amount) + receipt = ms(sender=safe) + assert receipt.txn_hash def test_no_operation(safe, token, vault): - with networks.ethereum.local.use_provider("foundry"): - ms = MultiSend() - ms.inject() - amount = token.balanceOf(safe) - ms.add(token.approve, vault, safe) - ms.add(vault.transfer, safe, amount) - with pytest.raises(SafeLogicError, match="Safe transaction failed"): - ms(sender=safe, operation=0) + ms = MultiSend() + ms.inject() + amount = token.balanceOf(safe) + ms.add(token.approve, vault, safe) + ms.add(vault.transfer, safe, amount) + with pytest.raises(SafeLogicError, match="Safe transaction failed"): + ms(sender=safe, operation=0) From cb471355a6ef1a72ef23a86727e6f5d3b073f823 Mon Sep 17 00:00:00 2001 From: Dalena Date: Thu, 12 Oct 2023 17:05:51 -0500 Subject: [PATCH 047/134] fix: foundry and alchemy --- .github/workflows/test.yaml | 2 ++ ape_safe/client.py | 2 +- setup.py | 2 ++ tests/ape-config.yaml | 18 ++++++++++++++++++ tests/conftest.py | 2 +- tests/test_multisend.py | 1 - 6 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/ape-config.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5a3886e..fa1f76f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -93,3 +93,5 @@ jobs: - name: Run Tests run: pytest -n 0 -s --cov + env: + WEB3_ALCHEMY_PROJECT_ID: ${{ secrets.WEB3_ALCHEMY_PROJECT_ID }} diff --git a/ape_safe/client.py b/ape_safe/client.py index 0d414b1..cb6d718 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -359,7 +359,7 @@ def post_signature( class MockSafeClient(BaseSafeClient, ManagerAccessMixin): def __init__(self, contract: ContractInstance): self.contract = contract - self.transactions: Union[SafeTx, SafeTxID, SafeApiTxData] = {} + self.transactions: Dict[Union[SafeTx, SafeTxID], SafeApiTxData] = {} self.transactions_by_nonce: Dict[int, List[SafeTxID]] = {} @property diff --git a/setup.py b/setup.py index f116faa..712ae39 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,8 @@ include_package_data=True, install_requires=[ "eth-ape>=0.6.11,<0.7.0", + "ape-alchemy>=0.6.5,<0.7.0", + "ape-foundry>=0.6.17,<0.7.0", "eip712>=0.2.0,<0.3.0", "requests>=2.31.0,<3", "pydantic", # Use same version as eth-ape diff --git a/tests/ape-config.yaml b/tests/ape-config.yaml new file mode 100644 index 0000000..0db7509 --- /dev/null +++ b/tests/ape-config.yaml @@ -0,0 +1,18 @@ +ethereum: + mainnnet: + default_provider: alchemy + local: + default_provider: foundry + +foundry: + fork: + ethereum: + mainnet: + upstream_provider: alchemy + block_number: 15776634 + goerli: + upstream_provider: alchemy + block_number: 7849922 + sepolia: + upstream_provider: alchemy + block_number: 3091950 diff --git a/tests/conftest.py b/tests/conftest.py index dcfc386..a728080 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -115,4 +115,4 @@ def vault(deployer, token): @pytest.fixture def foundry(networks): with networks.ethereum.local.use_provider("foundry") as provider: - yield provider \ No newline at end of file + yield provider diff --git a/tests/test_multisend.py b/tests/test_multisend.py index d68881d..8c686f6 100644 --- a/tests/test_multisend.py +++ b/tests/test_multisend.py @@ -1,5 +1,4 @@ import pytest -from ape import networks from ape_safe import MultiSend from ape_safe.exceptions import SafeLogicError From 83f0c6e7176d6d20867e8cca550f771b93332325 Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 25 Oct 2023 12:43:32 -0500 Subject: [PATCH 048/134] fix: test dependencies location --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 712ae39..dbcb1f7 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,8 @@ "pytest-xdist", # multi-process runner "pytest-cov", # Coverage analyzer plugin "hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer + "ape-alchemy>=0.6.5,<0.7.0", + "ape-foundry>=0.6.17,<0.7.0", ], "lint": [ "black>=23.7.0,<24", # Auto-formatter and linter @@ -57,8 +59,6 @@ include_package_data=True, install_requires=[ "eth-ape>=0.6.11,<0.7.0", - "ape-alchemy>=0.6.5,<0.7.0", - "ape-foundry>=0.6.17,<0.7.0", "eip712>=0.2.0,<0.3.0", "requests>=2.31.0,<3", "pydantic", # Use same version as eth-ape From af65c12943b573abc1402caa98c0cdf519154a19 Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 25 Oct 2023 12:44:39 -0500 Subject: [PATCH 049/134] fix: remove get_args --- ape_safe/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index cb6d718..b1b943d 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -2,7 +2,7 @@ from datetime import datetime from enum import Enum from functools import reduce -from typing import Dict, Iterator, List, NewType, Optional, Set, Union, get_args +from typing import Dict, Iterator, List, NewType, Optional, Set, Union import requests from ape.contracts import ContractInstance @@ -332,10 +332,10 @@ def post_signature( signer: AddressType, signature: MessageSignature, ): - if isinstance(safe_tx_or_hash, get_args(SafeTx)): + if isinstance(safe_tx_or_hash, SafeTx): safe_tx = safe_tx_or_hash safe_tx_hash = hash_eip712_message(safe_tx).hex() - elif isinstance(safe_tx_or_hash, get_args(SafeTxID)): + else: safe_tx_hash = safe_tx_or_hash if not isinstance(safe_tx_hash, str): From 254bfea54ef46cbc618cd9bbab0b4f0a7630a709 Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 25 Oct 2023 12:51:52 -0500 Subject: [PATCH 050/134] fix: confirmations required for signing --- ape_safe/_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 5c6c662..1a84f21 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -139,7 +139,7 @@ def pending(cli_ctx, network, sign_with_local_signers, execute, alias): ) # Add signatures, if was requested to do so. - if sign_with_local_signers and len(confirmations) < safe.confirmations_required: + if sign_with_local_signers and len(confirmations) < safe.confirmations_required - 1: safe.add_signatures(safe_tx, confirmations) cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") From 84b63d13234962576910e408f7897d34dbcd0bdd Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 25 Oct 2023 14:11:45 -0500 Subject: [PATCH 051/134] fix: add network warning --- ape_safe/_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 1a84f21..6e5d395 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -47,7 +47,7 @@ def _list(cli_ctx, network): extras.append(f"version: '{account.version}'") except ChainError: # Not connected to the network where safe is deployed - pass + extras.append(f"version: (not connected)") extras_display = f" ({', '.join(extras)})" if extras else "" click.echo(f" {account.address}{extras_display}") @@ -81,7 +81,7 @@ def add(cli_ctx, network, address, alias): container = cli_ctx.account_manager.containers["safe"] container.save_account(alias, address) - cli_ctx.logger.success(f"Safe '{address}' ({alias}) added.") + cli_ctx.logger.success(f"Safe '{address}' ({alias}) added.") @cli.command(short_help="Stop tracking a locally-tracked Safe") From ef0bf21a3bc522515f3ff2a3e1c340a8f7a44ada Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 25 Oct 2023 14:47:57 -0500 Subject: [PATCH 052/134] fix: remove comment for url --- ape_safe/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index b1b943d..4703ae0 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -35,8 +35,7 @@ 137: "https://safe-transaction-polygon.safe.global", 250: "https://safe-txservice.fantom.network", 288: "https://safe-transaction.mainnet.boba.network", - # NOTE: Not supported yet - # 8453: "https://safe-transaction-base.safe.global", + 8453: "https://safe-transaction-base.safe.global", 42161: "https://safe-transaction-arbitrum.safe.global", 43114: "https://safe-transaction-avalanche.safe.global", 84531: "https://safe-transaction-base-testnet.safe.global", From 43c06d86e5fd185b772a6f043d8ada570823dae5 Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 25 Oct 2023 14:57:44 -0500 Subject: [PATCH 053/134] refactor: multisend fixture --- tests/conftest.py | 7 +++++++ tests/test_multisend.py | 20 ++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a728080..f1cfb13 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ from ethpm_types import ContractType from ape_safe.accounts import SafeAccount +from ape_safe import MultiSend contracts_directory = Path(__file__).parent / "contracts" @@ -116,3 +117,9 @@ def vault(deployer, token): def foundry(networks): with networks.ethereum.local.use_provider("foundry") as provider: yield provider + +@pytest.fixture +def multisend(): + ms = MultiSend() + ms.inject() + return ms \ No newline at end of file diff --git a/tests/test_multisend.py b/tests/test_multisend.py index 8c686f6..8f0249b 100644 --- a/tests/test_multisend.py +++ b/tests/test_multisend.py @@ -8,21 +8,17 @@ def test_asset(vault, token): assert vault.asset() == token -def test_default_operation(safe, token, vault): - ms = MultiSend() - ms.inject() +def test_default_operation(safe, token, vault, multisend): amount = token.balanceOf(safe) - ms.add(token.approve, vault, safe) - ms.add(vault.transfer, safe, amount) - receipt = ms(sender=safe) + multisend.add(token.approve, vault, safe) + multisend.add(vault.transfer, safe, amount) + receipt = multisend(sender=safe) assert receipt.txn_hash -def test_no_operation(safe, token, vault): - ms = MultiSend() - ms.inject() +def test_no_operation(safe, token, vault, multisend): amount = token.balanceOf(safe) - ms.add(token.approve, vault, safe) - ms.add(vault.transfer, safe, amount) + multisend.add(token.approve, vault, safe) + multisend.add(vault.transfer, safe, amount) with pytest.raises(SafeLogicError, match="Safe transaction failed"): - ms(sender=safe, operation=0) + multisend(sender=safe, operation=0) From 688d02a402395e8fa35984c27975363acdf540a5 Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 25 Oct 2023 14:58:54 -0500 Subject: [PATCH 054/134] fix: remove version pins --- setup.py | 4 ++-- tests/conftest.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index dbcb1f7..7d4b5dc 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ "pytest-xdist", # multi-process runner "pytest-cov", # Coverage analyzer plugin "hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer - "ape-alchemy>=0.6.5,<0.7.0", - "ape-foundry>=0.6.17,<0.7.0", + "ape-alchemy", + "ape-foundry", ], "lint": [ "black>=23.7.0,<24", # Auto-formatter and linter diff --git a/tests/conftest.py b/tests/conftest.py index f1cfb13..aac7886 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,8 +7,8 @@ from ape.utils import ZERO_ADDRESS from ethpm_types import ContractType -from ape_safe.accounts import SafeAccount from ape_safe import MultiSend +from ape_safe.accounts import SafeAccount contracts_directory = Path(__file__).parent / "contracts" @@ -118,8 +118,9 @@ def foundry(networks): with networks.ethereum.local.use_provider("foundry") as provider: yield provider + @pytest.fixture def multisend(): ms = MultiSend() ms.inject() - return ms \ No newline at end of file + return ms From d5a98fae314cc777a985c4269a9547ecee76d995 Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 25 Oct 2023 15:11:23 -0500 Subject: [PATCH 055/134] fix: access data using .get --- ape_safe/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 4703ae0..41391e5 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -274,14 +274,14 @@ def _all_transactions(self) -> Iterator[SafeApiTxData]: data = response.json() - for txn in data["results"]: + for txn in data.get("results"): if "isExecuted" in txn and txn["isExecuted"]: yield ExecutedTxData.parse_obj(txn) else: yield UnexecutedTxData.parse_obj(txn) - url = data["next"] + url = data.get("next") def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: url = ( @@ -294,8 +294,8 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati raise ClientResponseError(url, response) data = response.json() - yield from map(SafeTxConfirmation.parse_obj, data["results"]) - url = data["next"] + yield from map(SafeTxConfirmation.parse_obj, data.get("results")) + url = data.get("next") def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) From 4ae4fcc525ada671066827c7008aea773ff0dcf6 Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 25 Oct 2023 15:13:18 -0500 Subject: [PATCH 056/134] docs: fix syntax mistake --- ape_safe/accounts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index d36317d..f63d8f0 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -85,7 +85,7 @@ def load_account(self, alias: str) -> "SafeAccount": alias (str): The alias the Safe account is saved under. Returns: - ``:class:~ape_safe.accounts.SafeAccount``: The Safe account loaded. + :class:`~ape_safe.accounts.SafeAccount`: The Safe account loaded. """ account_path = self._get_path(alias) if not account_path.is_file(): @@ -240,7 +240,7 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) **safe_tx_kwargs: The safe transactions specifications, such as ``submitter``. Returns: - ``:class:~ape_safe.client.SafeTx``: The Safe Transaction to be used. + :class:`~ape_safe.client.SafeTx`: The Safe Transaction to be used. """ safe_tx = { "to": txn.receiver if txn else self.address, # Self-call, e.g. rejection From dd50fa099f668162322ea175c3dc8bb63975357a Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 25 Oct 2023 15:16:13 -0500 Subject: [PATCH 057/134] fix: update call comment --- ape_safe/accounts.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index f63d8f0..be5539f 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -389,8 +389,7 @@ def call( # type: ignore[override] impersonate: bool = False, **call_kwargs, ) -> ReceiptAPI: - # NOTE: The `or True` at the end is in case we get `submit=None`, - # meaning use the default. + # NOTE: This handles if given `submit=None'. default_submit = not impersonate submit = ( call_kwargs.pop("submit_transaction", call_kwargs.pop("submit", default_submit)) From d855479b3af086ff1643858a8084c09c967a640b Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 11:37:06 -0500 Subject: [PATCH 058/134] fix: issues with pending --- ape_safe/_cli.py | 100 +++++++++++++++++++++++++++++----------- ape_safe/client.py | 2 +- tests/test_multisend.py | 1 - 3 files changed, 74 insertions(+), 29 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 6e5d395..54ee3d7 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -1,8 +1,12 @@ +from typing import Optional + import click +from ape.api import AccountAPI from ape.cli import ( NetworkBoundCommand, ape_cli_context, existing_alias_argument, + get_user_selected_account, network_option, non_existing_alias_argument, ) @@ -47,7 +51,7 @@ def _list(cli_ctx, network): extras.append(f"version: '{account.version}'") except ChainError: # Not connected to the network where safe is deployed - extras.append(f"version: (not connected)") + extras.append("version: (not connected)") extras_display = f" ({', '.join(extras)})" if extras else "" click.echo(f" {account.address}{extras_display}") @@ -100,38 +104,66 @@ def remove(cli_ctx, alias): cli_ctx.logger.success(f"Safe '{address}' ({alias}) removed.") -def _execute_callback(ctx, param, val): - if isinstance(val, str): - if val in ctx.obj.account_manager.aliases: - return ctx.obj.account_manager.load(val) - elif val in ctx.obj.account_manager: - return ctx.obj.account_manager[val] - else: - raise BadOptionUsage( - "--execute", f"`--execute` value '{val}` not found in local accounts." - ) +# NOTE: The handling of the `--execute` flag in the `pending` CLI +# all happens here EXCEPT if a pending tx is executable and no +# value of `--execute` was provided. +def _handle_execute_cli_arg(ctx, param, val): + # Account alias - execute using this account. + if val in ctx.obj.account_manager.aliases: + return ctx.obj.account_manager.load(val) - # Additional handling may occur in command definition. - return val + # Account address - execute using this account. + elif val in ctx.obj.account_manager: + return ctx.obj.account_manager[val] + # Saying "yes, execute". Use first "local signer". + elif val.lower() in ("true", "t", "1"): + return True -@cli.command( - cls=NetworkBoundCommand, short_help="See pending transactions for a locally-tracked Safe" -) + # Saying "no, do not execute", even if we could. + elif val.lower() in ("false", "f", "0"): + return False + + elif val is None: + # Was not given any value. + # If it is determined in `pending` that a tx can execute, + # the user will get prompted. + # Avoid this by always doing `--execute false`. + return val + + raise BadOptionUsage( + "--execute", f"`--execute` value '{val}` not a boolean or account identifier." + ) + + +@cli.command(cls=NetworkBoundCommand) @ape_cli_context() @network_option() @click.option("sign_with_local_signers", "--sign", is_flag=True) -@click.option("--execute", is_flag=True, flag_value=True, default=False, callback=_execute_callback) +@click.option("--execute", callback=_handle_execute_cli_arg) @existing_alias_argument(account_type=SafeAccount) -def pending(cli_ctx, network, sign_with_local_signers, execute, alias): +def pending(cli_ctx, network, sign_with_local_signers, execute, alias) -> None: + """ + View pending transactions for a Safe + """ + _ = network # Needed for NetworkBoundCommand safe = cli_ctx.account_manager.load(alias) - submitter = execute - if submitter is True: + submitter: Optional[AccountAPI] = None + + if execute is True: if not safe.local_signers: - cli_ctx.abort("Cannot execute without a local signer.") + cli_ctx.abort("Cannot use `--execute TRUE` without a local signer.") + + submitter = get_user_selected_account(account_list=safe.local_signers) - submitter = safe.local_signers[0] + elif isinstance(execute, AccountAPI): + # The callback handler loaded the local account. + submitter = execute + + # NOTE: --execute is only None when not specified at all. + # In this case, for any found executable txns, the user will be prompted. + execute_cli_arg_specified = execute is not None for safe_tx, confirmations in safe.pending_transactions(): click.echo( @@ -143,12 +175,26 @@ def pending(cli_ctx, network, sign_with_local_signers, execute, alias): safe.add_signatures(safe_tx, confirmations) cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") - if not execute: + # NOTE: Lazily check signatures. + signatures = None + + if not execute_cli_arg_specified: + # Check if we _can_ execute and ask the user. signatures = safe.get_api_confirmations(safe_tx) - if len(signatures) >= safe.confirmations_required and click.confirm( - f"Submit Transaction {safe_tx.nonce}" - ): - submitter.call(safe.create_execute_transaction(safe_tx, signatures)) + do_execute = ( + len(safe.local_signers) > 0 + and len(signatures) >= safe.confirmations_required + and click.confirm(f"Submit Transaction {safe_tx.nonce}") + ) + if do_execute: + submitter = get_user_selected_account(account_list=safe.local_signers) + + if submitter: + # NOTE: Signatures may have gotten set above already. + signatures = signatures or safe.get_api_confirmations(safe_tx) + + exc_tx = safe.create_execute_transaction(safe_tx, signatures) + submitter.call(exc_tx) @cli.command(cls=NetworkBoundCommand, short_help="Reject one or more pending transactions") diff --git a/ape_safe/client.py b/ape_safe/client.py index 41391e5..115fbf7 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -331,7 +331,7 @@ def post_signature( signer: AddressType, signature: MessageSignature, ): - if isinstance(safe_tx_or_hash, SafeTx): + if isinstance(safe_tx_or_hash, (SafeTxV1, SafeTxV2)): safe_tx = safe_tx_or_hash safe_tx_hash = hash_eip712_message(safe_tx).hex() else: diff --git a/tests/test_multisend.py b/tests/test_multisend.py index 8f0249b..deefd2e 100644 --- a/tests/test_multisend.py +++ b/tests/test_multisend.py @@ -1,6 +1,5 @@ import pytest -from ape_safe import MultiSend from ape_safe.exceptions import SafeLogicError From d4ed17a4a6ce291454fc054332ac8cb6f10f4cdf Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 12:10:34 -0500 Subject: [PATCH 059/134] fix: cli fixes --- ape_safe/__init__.py | 4 +-- ape_safe/_cli.py | 61 ++++++++++++++++++++++++-------------------- ape_safe/accounts.py | 21 ++++++++++++++- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/ape_safe/__init__.py b/ape_safe/__init__.py index b19423f..2422253 100644 --- a/ape_safe/__init__.py +++ b/ape_safe/__init__.py @@ -1,12 +1,12 @@ from ape import plugins -from .accounts import AccountContainer, SafeAccount +from .accounts import SafeAccount, SafeContainer from .multisend import MultiSend @plugins.register(plugins.AccountPlugin) def account_types(): - return AccountContainer, SafeAccount + return SafeContainer, SafeAccount __all__ = [ diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 54ee3d7..9c68f08 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -3,6 +3,7 @@ import click from ape.api import AccountAPI from ape.cli import ( + ApeCliContextObject, NetworkBoundCommand, ape_cli_context, existing_alias_argument, @@ -10,14 +11,26 @@ network_option, non_existing_alias_argument, ) -from ape.exceptions import ChainError +from ape.exceptions import AccountsError, ChainError from ape.types import AddressType from click import BadArgumentUsage, BadOptionUsage -from ape_safe.accounts import SafeAccount +from ape_safe.accounts import SafeAccount, SafeContainer from ape_safe.client import ExecutedTxData +class SafeCliContext(ApeCliContextObject): + @property + def safes(self) -> SafeContainer: + if "safe" in self.account_manager.containers: + return self.account_manager.containers["safe"] + + raise AccountsError("Safe account container missing; plugin failure.") + + +safe_cli_ctx = ape_cli_context(obj_type=SafeCliContext) + + @click.group(short_help="Manage Safe accounts and view Safe API data") def cli(): """ @@ -27,12 +40,11 @@ def cli(): @cli.command(name="list", cls=NetworkBoundCommand, short_help="Show locally-tracked Safes") -@ape_cli_context() +@safe_cli_ctx @network_option() -def _list(cli_ctx, network): +def _list(cli_ctx: SafeCliContext, network): _ = network # Needed for NetworkBoundCommand - safes = cli_ctx.account_manager.get_accounts_by_type(type_=SafeAccount) - number_of_safes = len(safes) + number_of_safes = len(cli_ctx.safes) if number_of_safes == 0: cli_ctx.logger.warning("No Safes found.") @@ -42,7 +54,7 @@ def _list(cli_ctx, network): header += "s:" if number_of_safes > 1 else ":" click.echo(header) - for account in safes: + for account in cli_ctx.safes: extras = [] if account.alias: extras.append(f"alias: '{account.alias}'") @@ -58,11 +70,11 @@ def _list(cli_ctx, network): @cli.command(cls=NetworkBoundCommand, short_help="Add a Safe to locally tracked Safes") -@ape_cli_context() +@safe_cli_ctx @network_option() @click.argument("address", type=AddressType) @non_existing_alias_argument() -def add(cli_ctx, network, address, alias): +def add(cli_ctx: SafeCliContext, network, address, alias): _ = network # Needed for NetworkBoundCommand address = cli_ctx.conversion_manager.convert(address, AddressType) safe_contract = cli_ctx.chain_manager.contracts.instance_at(address) @@ -82,24 +94,20 @@ def add(cli_ctx, network, address, alias): ) if click.confirm("Add safe"): - container = cli_ctx.account_manager.containers["safe"] - container.save_account(alias, address) - + cli_ctx.safes.save_account(alias, address) cli_ctx.logger.success(f"Safe '{address}' ({alias}) added.") @cli.command(short_help="Stop tracking a locally-tracked Safe") -@ape_cli_context() +@safe_cli_ctx @existing_alias_argument() -def remove(cli_ctx, alias): - safe_container = cli_ctx.account_manager.containers["safe"] - - if alias not in safe_container.aliases: +def remove(cli_ctx: SafeCliContext, alias): + if alias not in cli_ctx.safes.aliases: raise BadArgumentUsage(f"There is no safe with the alias `{alias}`.") - address = safe_container.load_account(alias).address + address = cli_ctx.safes.load_account(alias).address if click.confirm(f"Remove safe {address} ({alias})"): - safe_container.delete_account(alias) + cli_ctx.safes.delete_account(alias) cli_ctx.logger.success(f"Safe '{address}' ({alias}) removed.") @@ -137,12 +145,12 @@ def _handle_execute_cli_arg(ctx, param, val): @cli.command(cls=NetworkBoundCommand) -@ape_cli_context() +@safe_cli_ctx @network_option() @click.option("sign_with_local_signers", "--sign", is_flag=True) @click.option("--execute", callback=_handle_execute_cli_arg) @existing_alias_argument(account_type=SafeAccount) -def pending(cli_ctx, network, sign_with_local_signers, execute, alias) -> None: +def pending(cli_ctx: SafeCliContext, network, sign_with_local_signers, execute, alias) -> None: """ View pending transactions for a Safe """ @@ -198,11 +206,11 @@ def pending(cli_ctx, network, sign_with_local_signers, execute, alias) -> None: @cli.command(cls=NetworkBoundCommand, short_help="Reject one or more pending transactions") +@safe_cli_ctx() @network_option() @existing_alias_argument(account_type=SafeAccount) @click.argument("txn-ids", type=int, nargs=-1) -@ape_cli_context() -def reject(cli_ctx, network, alias, txn_ids): +def reject(cli_ctx: SafeCliContext, network, alias, txn_ids): _ = network # Needed for NetworkBoundCommand safe = cli_ctx.account_manager.load(alias) pending_transactions = safe.client.get_transactions(starting_nonce=safe.next_nonce) @@ -222,14 +230,13 @@ def reject(cli_ctx, network, alias, txn_ids): cls=NetworkBoundCommand, short_help="View and filter all transactions for a given Safe using Safe API", ) -@ape_cli_context() +@safe_cli_ctx @network_option() @click.argument("address", type=AddressType) @click.option("--confirmed", is_flag=True, default=None) -def all_txns(cli_ctx, network, address, confirmed): +def all_txns(cli_ctx: SafeCliContext, network, address, confirmed): _ = network # Needed for NetworkBoundCommand - safe_container = cli_ctx.account_manager.containers["safe"] - client = safe_container._get_client(address) + client = cli_ctx.safes._get_client(address) for txn in client.get_transactions(confirmed=confirmed): if isinstance(txn, ExecutedTxData): diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index be5539f..4448ea3 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -37,7 +37,7 @@ from ape_safe.utils import order_by_signer -class AccountContainer(AccountContainerAPI): +class SafeContainer(AccountContainerAPI): @property def _account_files(self) -> Iterator[Path]: yield from self.data_folder.glob("*.json") @@ -47,6 +47,11 @@ def aliases(self) -> Iterator[str]: for p in self._account_files: yield p.stem + @property + def addresses(self) -> Iterator[str]: + for safe in self.accounts: + yield safe.address + @property def accounts(self) -> Iterator[AccountAPI]: for account_file in self._account_files: @@ -61,6 +66,20 @@ def __setitem__(self, alias: str, address: str): def __delitem__(self, alias: str): self.delete_account(alias) + def __iter__(self) -> Iterator["SafeAccount"]: + yield from self.accounts + + def __contains__(self, item: Union[str, "SafeAccount"]) -> bool: + if isinstance(item, str): + return item in self.aliases or item in self.addresses + + # Is account object + for account in self.accounts: + if account.address == item.address: + return True + + return False + def save_account(self, alias: str, address: str): """ Save a new Safe to your ape configuration. From 0d17dfef6aaca891c24ca8f428b5ebb3168468d6 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 13:25:28 -0500 Subject: [PATCH 060/134] feat: better forked net handling --- ape_safe/accounts.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 4448ea3..cf450e3 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -5,7 +5,7 @@ from ape.api import AccountAPI, AccountContainerAPI, ReceiptAPI, TransactionAPI from ape.api.address import BaseAddress -from ape.api.networks import LOCAL_NETWORK_NAME +from ape.api.networks import LOCAL_NETWORK_NAME, ForkedNetworkAPI from ape.contracts import ContractInstance from ape.logging import logger from ape.managers.accounts import AccountManager, TestAccountManager @@ -27,7 +27,6 @@ ) from ape_safe.exceptions import ( ApeSafeError, - ClientUnavailable, NoLocalSigners, NotASigner, NotEnoughSignatures, @@ -127,12 +126,11 @@ def _get_client(self, key: str) -> BaseSafeClient: safe = self.load_account(key) return safe.client - elif key in self: - account = self[key] # type: ignore[index] - if not isinstance(account, SafeAccount): - raise TypeError("Safe container returned non-safe account.") + elif key in self.addresses: + return self[cast(AddressType, key)].client - return account.client + elif key in self.aliases: + return self.load_account(key).client else: # Is not locally managed. @@ -169,7 +167,7 @@ def account_file(self) -> dict: @property def address(self) -> AddressType: - return self.network_manager.ethereum.decode_address(self.account_file["address"]) + return self.provider.network.ecosystem.decode_address(self.account_file["address"]) @cached_property def contract(self) -> ContractInstance: @@ -202,10 +200,26 @@ def fallback_handler(self) -> Optional[ContractInstance]: @cached_property def client(self) -> BaseSafeClient: chain_id = self.provider.chain_id - if chain_id not in self.account_file["deployed_chain_ids"]: - raise ClientUnavailable(f"Safe client not valid on chain '{chain_id}'.") - elif self.provider.network.name == LOCAL_NETWORK_NAME: + if self.provider.network.name == LOCAL_NETWORK_NAME: + return MockSafeClient(contract=self.contract) + + elif chain_id in self.account_file["deployed_chain_ids"]: + return SafeClient(address=self.address, chain_id=self.provider.chain_id) + + elif ( + self.provider.network.name.endswith("-fork") + and isinstance(self.provider.network, ForkedNetworkAPI) + and self.provider.network.upstream_chain_id in self.account_file["deployed_chain_ids"] + ): + return SafeClient( + address=self.address, chain_id=self.provider.network.upstream_chain_id + ) + + elif ( + self.provider.network.name == LOCAL_NETWORK_NAME + or self.provider.network.name.endswith("-fork") + ): return MockSafeClient(contract=self.contract) return SafeClient(address=self.address, chain_id=self.provider.chain_id) From b9a43b2832f6cfa2a927e88940599dd77922190c Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 13:33:53 -0500 Subject: [PATCH 061/134] fix: cli parens --- ape_safe/_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 9c68f08..e22ed1f 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -206,7 +206,7 @@ def pending(cli_ctx: SafeCliContext, network, sign_with_local_signers, execute, @cli.command(cls=NetworkBoundCommand, short_help="Reject one or more pending transactions") -@safe_cli_ctx() +@safe_cli_ctx @network_option() @existing_alias_argument(account_type=SafeAccount) @click.argument("txn-ids", type=int, nargs=-1) From 5a2197f565c0eddac1d4c5f801a8574980b21934 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 13:35:59 -0500 Subject: [PATCH 062/134] docs: add raises doc --- ape_safe/accounts.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index cf450e3..510b568 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -83,6 +83,10 @@ def save_account(self, alias: str, address: str): """ Save a new Safe to your ape configuration. + Raises: + :class:`~ape_safe.exceptions.ApeSafeError`: When the alias + already exists. + Args: alias (str): The alias to save the Safe under. address (str): The address of the Safe account. From 796f9178ec7a543007dbd03b79f2e07ab49284ef Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 13:36:44 -0500 Subject: [PATCH 063/134] docs: add raises doc --- ape_safe/accounts.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 510b568..33d9380 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -103,6 +103,10 @@ def load_account(self, alias: str) -> "SafeAccount": """ Load the Safe account. + Raises: + :class:`~ape_safe.exceptions.ApeSafeError`: When the alias does + not exist. + Args: alias (str): The alias the Safe account is saved under. From 51d07b6a6de43d210c45d92e47fff8303499c4e8 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 13:38:45 -0500 Subject: [PATCH 064/134] fix: handle when converted in addresses --- ape_safe/accounts.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 33d9380..d916c3c 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -141,8 +141,11 @@ def _get_client(self, key: str) -> BaseSafeClient: return self.load_account(key).client else: - # Is not locally managed. address = self.conversion_manager.convert(key, AddressType) + if address in self.addresses: + return self[cast(AddressType, key)].client + + # Is not locally managed. return SafeClient(address=address, chain_id=self.chain_manager.provider.chain_id) def _get_path(self, alias: str) -> Path: From b085060b1dbbb5155600ac0c7c12eab2256caabf Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 13:40:41 -0500 Subject: [PATCH 065/134] docs: say why needed --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7d4b5dc..fbba078 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ "pytest-xdist", # multi-process runner "pytest-cov", # Coverage analyzer plugin "hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer - "ape-alchemy", - "ape-foundry", + "ape-alchemy", # Needed for testing in a forked network + "ape-foundry", # Needed for forked-network features ], "lint": [ "black>=23.7.0,<24", # Auto-formatter and linter From ea5db701cf8fed4c2d5eb1ceeb4ef852c0469dd0 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 13:46:46 -0500 Subject: [PATCH 066/134] fix: utc now --- ape_safe/client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 115fbf7..6d326c5 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from datetime import datetime +from datetime import datetime, timezone from enum import Enum from functools import reduce from typing import Dict, Iterator, List, NewType, Optional, Set, Union @@ -97,8 +97,8 @@ class UnexecutedTxData(BaseModel): def from_safe_tx(cls, safe_tx: SafeTx, confirmations_required: int) -> "UnexecutedTxData": return cls( safe=safe_tx._verifyingContract_, - submissionDate=datetime.now(), - modified=datetime.now(), + submissionDate=datetime.now(timezone.utc), + modified=datetime.now(timezone.utc), confirmationsRequired=confirmations_required, safeTxHash=hash_eip712_message(safe_tx).hex(), **safe_tx._body_["message"], @@ -398,7 +398,7 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna safe_tx_data.confirmations.extend( SafeTxConfirmation( owner=signer, - submissionDate=datetime.now(), + submissionDate=datetime.now(timezone.utc), signature=sig.encode_rsv(), signatureType=SignatureType.EOA, ) @@ -420,7 +420,7 @@ def post_signature( self.transactions[safe_tx_or_hash].confirmations.append( SafeTxConfirmation( owner=signer, - submissionDate=datetime.now(), + submissionDate=datetime.now(timezone.utc), signature=signature.encode_rsv(), signatureType=SignatureType.EOA, ) From 5b7970443d004adc9775fdc8ec137c59e82c94c9 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 13:50:36 -0500 Subject: [PATCH 067/134] feat: snake case ify --- ape_safe/_cli.py | 2 +- ape_safe/client.py | 38 +++++++++++++++++++------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index e22ed1f..e694d05 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -244,5 +244,5 @@ def all_txns(cli_ctx: SafeCliContext, network, address, confirmed): click.echo(f"Txn {txn.nonce}: {success_str} @ {txn.executionDate}") else: click.echo( - f"Txn {txn.nonce}: pending ({len(txn.confirmations)}/{txn.confirmationsRequired})" + f"Txn {txn.nonce}: pending ({len(txn.confirmations)}/{txn.confirmations_required})" ) diff --git a/ape_safe/client.py b/ape_safe/client.py index 6d326c5..08167fa 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -11,7 +11,7 @@ from eip712.common import SafeTxV1, SafeTxV2 from eip712.messages import hash_eip712_message from eth_utils import keccak -from pydantic import BaseModel +from pydantic import BaseModel, Field from ape_safe.exceptions import ( ClientResponseError, @@ -79,16 +79,16 @@ class UnexecutedTxData(BaseModel): value: int data: Optional[HexBytes] = None operation: OperationType - gasToken: AddressType - safeTxGas: int - baseGas: int - gasPrice: int - refundReceiver: AddressType + gas_token: AddressType = Field(alias="gasToken") + safe_tx_gas: int = Field(alias="safeTxGas") + base_gas: int = Field(alias="baseGas") + gas_price: int = Field(alias="gasPrice") + refund_receiver: AddressType = Field(alias="refundReceiver") nonce: int - submissionDate: datetime + submission_date: datetime = Field(alias="submissionDate") modified: datetime - safeTxHash: SafeTxID - confirmationsRequired: int + safe_tx_hash: SafeTxID = Field(alias="safeTxHash") + confirmations_required: int = Field(alias="confirmationsRequired") confirmations: List[SafeTxConfirmation] = [] trusted: bool = True signatures: Optional[HexBytes] = None @@ -111,11 +111,11 @@ def base_tx_dict(self) -> Dict: "value": self.value, "data": self.data, "operation": self.operation, - "safeTxGas": self.safeTxGas, - "baseGas": self.baseGas, - "gasPrice": self.gasPrice, - "gasToken": self.gasToken, - "refundReceiver": self.refundReceiver, + "safeTxGas": self.safe_tx_gas, + "baseGas": self.base_gas, + "gasPrice": self.gas_price, + "gasToken": self.gas_token, + "refundReceiver": self.refund_receiver, "nonce": self.nonce, } @@ -184,7 +184,7 @@ def get_transactions( if txn.nonce < starting_nonce: break # NOTE: order is largest nonce to smallest, so safe to break here - is_confirmed = len(txn.confirmations) >= txn.confirmationsRequired + is_confirmed = len(txn.confirmations) >= txn.confirmations_required if confirmed is not None: if not confirmed and isinstance(txn, ExecutedTxData): @@ -196,7 +196,7 @@ def get_transactions( if txn.nonce < next_nonce and isinstance(txn, UnexecutedTxData): continue # NOTE: Skip orphaned transactions - if filter_by_ids and txn.safeTxHash not in filter_by_ids: + if filter_by_ids and txn.safe_tx_hash not in filter_by_ids: continue # NOTE: Skip transactions not in the filter if filter_by_missing_signers and filter_by_missing_signers.issubset( @@ -404,12 +404,12 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna ) for signer, sig in sigs.items() ) - self.transactions[safe_tx_data.safeTxHash] = safe_tx_data + self.transactions[safe_tx_data.safe_tx_hash] = safe_tx_data if safe_tx_data.nonce in self.transactions_by_nonce: - self.transactions_by_nonce[safe_tx_data.nonce].append(safe_tx_data.safeTxHash) + self.transactions_by_nonce[safe_tx_data.nonce].append(safe_tx_data.safe_tx_hash) else: - self.transactions_by_nonce[safe_tx_data.nonce] = [safe_tx_data.safeTxHash] + self.transactions_by_nonce[safe_tx_data.nonce] = [safe_tx_data.safe_tx_hash] def post_signature( self, From beb27191cb34f174611d1d7e708f9db6de41cda7 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 13:53:31 -0500 Subject: [PATCH 068/134] feat: better vm checks --- ape_safe/client.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ape_safe/client.py b/ape_safe/client.py index 08167fa..86e8c85 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -373,13 +373,23 @@ def safe_details(self) -> SafeDetails: threshold=self.contract.getThreshold(), owners=self.contract.getOwners(), masterCopy=self.contract.masterCopy(), - modules=self.contract.getModules() if hasattr(self.contract, "getModules") else [], + modules=self.modules, # TODO: Add fallback handler getter fallbackHandler=fallback_address, - guard=self.contract.getGuard() if hasattr(self.contract, "getGuard") else ZERO_ADDRESS, + guard=self.guard, version=self.contract.VERSION(), ) + @property + def guard(self) -> AddressType: + return ( + self.contract.getGuard() if "getGuard" in self.contract._view_methods_ else ZERO_ADDRESS + ) + + @property + def modules(self) -> List[AddressType]: + return self.contract.getModules() if "getModules" in self.contract._view_methods_ else [] + def get_next_nonce(self) -> int: return self.contract._view_methods_["nonce"]() From 16bafe757c2969c00d6b42a18b5f8fa07ca36765 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 13:55:01 -0500 Subject: [PATCH 069/134] refacor: switch to assert --- ape_safe/_cli.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index e694d05..93d05df 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -11,7 +11,7 @@ network_option, non_existing_alias_argument, ) -from ape.exceptions import AccountsError, ChainError +from ape.exceptions import ChainError from ape.types import AddressType from click import BadArgumentUsage, BadOptionUsage @@ -22,10 +22,9 @@ class SafeCliContext(ApeCliContextObject): @property def safes(self) -> SafeContainer: - if "safe" in self.account_manager.containers: - return self.account_manager.containers["safe"] - - raise AccountsError("Safe account container missing; plugin failure.") + # NOTE: Would only happen in local development of this plugin. + assert "safe" in self.account_manager.containers, "Are all API methods implemented?" + return self.account_manager.containers["safe"] safe_cli_ctx = ape_cli_context(obj_type=SafeCliContext) From ec243302850e30d37451ed11693a173bd39761c7 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 14:04:32 -0500 Subject: [PATCH 070/134] fix: regression issues --- ape_safe/_cli.py | 16 ++++++++-------- ape_safe/accounts.py | 11 ++++++++++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 93d05df..cfa5f20 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -116,7 +116,14 @@ def remove(cli_ctx: SafeCliContext, alias): # value of `--execute` was provided. def _handle_execute_cli_arg(ctx, param, val): # Account alias - execute using this account. - if val in ctx.obj.account_manager.aliases: + if val is None: + # Was not given any value. + # If it is determined in `pending` that a tx can execute, + # the user will get prompted. + # Avoid this by always doing `--execute false`. + return val + + elif val in ctx.obj.account_manager.aliases: return ctx.obj.account_manager.load(val) # Account address - execute using this account. @@ -131,13 +138,6 @@ def _handle_execute_cli_arg(ctx, param, val): elif val.lower() in ("false", "f", "0"): return False - elif val is None: - # Was not given any value. - # If it is determined in `pending` that a tx can execute, - # the user will get prompted. - # Avoid this by always doing `--execute false`. - return val - raise BadOptionUsage( "--execute", f"`--execute` value '{val}` not a boolean or account identifier." ) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index d916c3c..85427bd 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -7,6 +7,7 @@ from ape.api.address import BaseAddress from ape.api.networks import LOCAL_NETWORK_NAME, ForkedNetworkAPI from ape.contracts import ContractInstance +from ape.exceptions import ProviderNotConnectedError from ape.logging import logger from ape.managers.accounts import AccountManager, TestAccountManager from ape.types import AddressType, HexBytes, MessageSignature, SignableMessage @@ -69,6 +70,9 @@ def __iter__(self) -> Iterator["SafeAccount"]: yield from self.accounts def __contains__(self, item: Union[str, "SafeAccount"]) -> bool: + if item is None: + return False + if isinstance(item, str): return item in self.aliases or item in self.addresses @@ -178,7 +182,12 @@ def account_file(self) -> dict: @property def address(self) -> AddressType: - return self.provider.network.ecosystem.decode_address(self.account_file["address"]) + try: + ecosystem = self.provider.network.ecosystem + except ProviderNotConnectedError: + ecosystem = self.network_manager.ethereum + + return ecosystem.decode_address(self.account_file["address"]) @cached_property def contract(self) -> ContractInstance: From 940d0eb740a0f5b39ec09c398e8c0b1a6509923c Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 31 Oct 2023 17:14:33 -0500 Subject: [PATCH 071/134] refactor: use key --- ape_safe/_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index cfa5f20..46d636e 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -162,7 +162,7 @@ def pending(cli_ctx: SafeCliContext, network, sign_with_local_signers, execute, if not safe.local_signers: cli_ctx.abort("Cannot use `--execute TRUE` without a local signer.") - submitter = get_user_selected_account(account_list=safe.local_signers) + submitter = get_user_selected_account(account_type=safe.local_signers) elif isinstance(execute, AccountAPI): # The callback handler loaded the local account. @@ -194,7 +194,7 @@ def pending(cli_ctx: SafeCliContext, network, sign_with_local_signers, execute, and click.confirm(f"Submit Transaction {safe_tx.nonce}") ) if do_execute: - submitter = get_user_selected_account(account_list=safe.local_signers) + submitter = get_user_selected_account(account_type=safe.local_signers) if submitter: # NOTE: Signatures may have gotten set above already. From d28994200f34ed5fce8b14151f12ced2ffb73949 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 1 Nov 2023 08:15:54 -0500 Subject: [PATCH 072/134] chore: use doc str for short help --- ape_safe/_cli.py | 33 +++++++++++++++++++++++++-------- setup.py | 1 + 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 46d636e..52c9375 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -38,10 +38,14 @@ def cli(): """ -@cli.command(name="list", cls=NetworkBoundCommand, short_help="Show locally-tracked Safes") +@cli.command(name="list", cls=NetworkBoundCommand) @safe_cli_ctx @network_option() def _list(cli_ctx: SafeCliContext, network): + """ + Show locally-tracked Safes + """ + _ = network # Needed for NetworkBoundCommand number_of_safes = len(cli_ctx.safes) @@ -68,12 +72,16 @@ def _list(cli_ctx: SafeCliContext, network): click.echo(f" {account.address}{extras_display}") -@cli.command(cls=NetworkBoundCommand, short_help="Add a Safe to locally tracked Safes") +@cli.command(cls=NetworkBoundCommand) @safe_cli_ctx @network_option() @click.argument("address", type=AddressType) @non_existing_alias_argument() def add(cli_ctx: SafeCliContext, network, address, alias): + """ + Add a Safe to locally tracked Safes + """ + _ = network # Needed for NetworkBoundCommand address = cli_ctx.conversion_manager.convert(address, AddressType) safe_contract = cli_ctx.chain_manager.contracts.instance_at(address) @@ -97,10 +105,14 @@ def add(cli_ctx: SafeCliContext, network, address, alias): cli_ctx.logger.success(f"Safe '{address}' ({alias}) added.") -@cli.command(short_help="Stop tracking a locally-tracked Safe") +@cli.command() @safe_cli_ctx @existing_alias_argument() def remove(cli_ctx: SafeCliContext, alias): + """ + Stop tracking a locally-tracked Safe + """ + if alias not in cli_ctx.safes.aliases: raise BadArgumentUsage(f"There is no safe with the alias `{alias}`.") @@ -204,12 +216,16 @@ def pending(cli_ctx: SafeCliContext, network, sign_with_local_signers, execute, submitter.call(exc_tx) -@cli.command(cls=NetworkBoundCommand, short_help="Reject one or more pending transactions") +@cli.command(cls=NetworkBoundCommand) @safe_cli_ctx @network_option() @existing_alias_argument(account_type=SafeAccount) @click.argument("txn-ids", type=int, nargs=-1) def reject(cli_ctx: SafeCliContext, network, alias, txn_ids): + """ + Reject one or more pending transactions + """ + _ = network # Needed for NetworkBoundCommand safe = cli_ctx.account_manager.load(alias) pending_transactions = safe.client.get_transactions(starting_nonce=safe.next_nonce) @@ -225,15 +241,16 @@ def reject(cli_ctx: SafeCliContext, network, alias, txn_ids): safe.transfer(safe, "0 ether", nonce=txn_id, submit_transaction=False) -@cli.command( - cls=NetworkBoundCommand, - short_help="View and filter all transactions for a given Safe using Safe API", -) +@cli.command(cls=NetworkBoundCommand) @safe_cli_ctx @network_option() @click.argument("address", type=AddressType) @click.option("--confirmed", is_flag=True, default=None) def all_txns(cli_ctx: SafeCliContext, network, address, confirmed): + """ + View and filter all transactions for a given Safe using Safe API + """ + _ = network # Needed for NetworkBoundCommand client = cli_ctx.safes._get_client(address) diff --git a/setup.py b/setup.py index fbba078..bbea50f 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ "eth-ape>=0.6.11,<0.7.0", "eip712>=0.2.0,<0.3.0", "requests>=2.31.0,<3", + "click", # Use same version as eth-ape "pydantic", # Use same version as eth-ape "eth-utils", # Use same version as eth-ape ], From 7728910232616ac2ac95c40972fefa6e06cbad39 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 1 Nov 2023 08:16:50 -0500 Subject: [PATCH 073/134] chore: lint deps --- .pre-commit-config.yaml | 6 +++--- setup.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 73e2b1d..f209910 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml @@ -10,7 +10,7 @@ repos: - id: isort - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.10.1 hooks: - id: black name: black @@ -21,7 +21,7 @@ repos: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.6.1 hooks: - id: mypy additional_dependencies: [types-requests, types-setuptools, pydantic] diff --git a/setup.py b/setup.py index bbea50f..feb6d38 100644 --- a/setup.py +++ b/setup.py @@ -12,8 +12,8 @@ "ape-foundry", # Needed for forked-network features ], "lint": [ - "black>=23.7.0,<24", # Auto-formatter and linter - "mypy>=1.5.1,<2", # Static type analyzer + "black>=23.10.1,<24", # Auto-formatter and linter + "mypy>=1.6.1,<2", # Static type analyzer "types-requests", # Needed for mypy type shed "types-setuptools", # Needed for mypy type shed "flake8>=6.1.0,<7", # Style linter From 7c7d8b9c37976e503d13e37fed352e4b660f8d05 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 1 Nov 2023 13:42:46 -0500 Subject: [PATCH 074/134] refactor: switch to npm dep --- .github/workflows/test.yaml | 14 ++++++++----- ape-config.yaml | 42 ++++++++++++++++++++++++++++--------- safe-contracts.json | 1 - setup.py | 3 ++- tests/ape-config.yaml | 18 ---------------- tests/conftest.py | 9 ++++++++ 6 files changed, 52 insertions(+), 35 deletions(-) delete mode 100644 safe-contracts.json delete mode 100644 tests/ape-config.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fa1f76f..e9aadcb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -60,7 +60,8 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] # eventually add `windows-latest` - python-version: [3.8, 3.9, "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] + safe-version: ["1.3.0"] env: GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -86,10 +87,13 @@ jobs: - name: Install plugins run: ape plugins install . - # TODO: Remove once dependency work is completed - # https://github.com/ApeWorX/ape/issues/1327 - - name: Copy smart-contracts to dependencies - run: mkdir -p ~/.ape/packages/safe-contracts/v1.3.0/ && cp safe-contracts.json ~/.ape/packages/safe-contracts/v1.3.0/ + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Install gnosis safe + run: npm install "@gnosis.pm/safe-contracts@${{ matrix.safe-version }}" - name: Run Tests run: pytest -n 0 -s --cov diff --git a/ape-config.yaml b/ape-config.yaml index 4ab0b56..78de3e6 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -1,18 +1,40 @@ -plugins: - - name: solidity - - name: foundry +contracts_folder: tests/contracts + +ethereum: + mainnet: + default_provider: alchemy + local: + default_provider: foundry dependencies: + - name: openzeppelin + github: OpenZeppelin/openzeppelin-contracts + version: 3.4.0 - name: safe-contracts - github: safe-global/safe-contracts + npm: "@gnosis.pm/safe-contracts" version: 1.3.0 + config_override: + solidity: + version: 0.7.6 + compile: + exclude: + - "test/*" + - "interfaces/*" -ethereum: - local: - default_provider: foundry +solidity: + import_remapping: + - "@openzeppelin/contracts=openzeppelin/v3.4.0" + - "@gnosis=safe-contracts/v1.3.0" foundry: fork: - ethereum: - mainnet: - upstream_provider: infura + ethereum: + mainnet: + upstream_provider: alchemy + block_number: 15776634 + goerli: + upstream_provider: alchemy + block_number: 7849922 + sepolia: + upstream_provider: alchemy + block_number: 3091950 diff --git a/safe-contracts.json b/safe-contracts.json deleted file mode 100644 index 3249ee3..0000000 --- a/safe-contracts.json +++ /dev/null @@ -1 +0,0 @@ -{"contractTypes":{"BaseGuard":{"abi":[{"inputs":[{"internalType":"bytes32","name":"txHash","type":"bytes32"},{"internalType":"bool","name":"success","type":"bool"}],"name":"checkAfterExecution","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address payable","name":"refundReceiver","type":"address"},{"internalType":"bytes","name":"signatures","type":"bytes"},{"internalType":"address","name":"msgSender","type":"address"}],"name":"checkTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"contractName":"BaseGuard","deploymentBytecode":{},"devdoc":{"kind":"dev","methods":{"supportsInterface(bytes4)":{"details":"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas."}},"version":1},"runtimeBytecode":{},"sourceId":"base/GuardManager.sol","userdoc":{"kind":"user","methods":{},"version":1}},"CompatibilityFallbackHandler":{"abi":[{"inputs":[],"name":"NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"message","type":"bytes"}],"name":"getMessageHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract GnosisSafe","name":"safe","type":"address"},{"internalType":"bytes","name":"message","type":"bytes"}],"name":"getMessageHashForSafe","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getModules","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_dataHash","type":"bytes32"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"targetContract","type":"address"},{"internalType":"bytes","name":"calldataPayload","type":"bytes"}],"name":"simulate","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"tokensReceived","outputs":[],"stateMutability":"pure","type":"function"}],"contractName":"CompatibilityFallbackHandler","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50610f30806100206000396000f3fe608060405234801561001057600080fd5b50600436106100ce5760003560e01c80636ac247841161008c578063bc197c8111610066578063bc197c8114610613578063bd61951d1461073a578063f23a6e61146107b8578063ffa1ad741461084b576100ce565b80636ac247841461048a578063a3f4df7e1461053e578063b2494df3146105bb576100ce565b806223de29146100d357806301ffc9a7146101bb5780630a1028c4146101f6578063150b7a02146102ac5780631626ba7e1461035757806320c13b0b146103cc575b600080fd5b6101b9600480360360c08110156100e957600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b81111561012b57600080fd5b82018360208201111561013d57600080fd5b803590602001918460018302840111600160201b8311171561015e57600080fd5b919390929091602081019035600160201b81111561017b57600080fd5b82018360208201111561018d57600080fd5b803590602001918460018302840111600160201b831117156101ae57600080fd5b509092509050610853565b005b6101e2600480360360208110156101d157600080fd5b50356001600160e01b03191661085d565b604080519115158252519081900360200190f35b61029a6004803603602081101561020c57600080fd5b810190602081018135600160201b81111561022657600080fd5b82018360208201111561023857600080fd5b803590602001918460018302840111600160201b8311171561025957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506108af945050505050565b60408051918252519081900360200190f35b61033a600480360360808110156102c257600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b8111156102fc57600080fd5b82018360208201111561030e57600080fd5b803590602001918460018302840111600160201b8311171561032f57600080fd5b5090925090506108bb565b604080516001600160e01b03199092168252519081900360200190f35b61033a6004803603604081101561036d57600080fd5b81359190810190604081016020820135600160201b81111561038e57600080fd5b8201836020820111156103a057600080fd5b803590602001918460018302840111600160201b831117156103c157600080fd5b5090925090506108cc565b61033a600480360360408110156103e257600080fd5b810190602081018135600160201b8111156103fc57600080fd5b82018360208201111561040e57600080fd5b803590602001918460018302840111600160201b8311171561042f57600080fd5b919390929091602081019035600160201b81111561044c57600080fd5b82018360208201111561045e57600080fd5b803590602001918460018302840111600160201b8311171561047f57600080fd5b509092509050610a1b565b61029a600480360360408110156104a057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156104ca57600080fd5b8201836020820111156104dc57600080fd5b803590602001918460018302840111600160201b831117156104fd57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610bf4945050505050565b610546610d12565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610580578181015183820152602001610568565b50505050905090810190601f1680156105ad5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6105c3610d4b565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156105ff5781810151838201526020016105e7565b505050509050019250505060405180910390f35b61033a600480360360a081101561062957600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561065c57600080fd5b82018360208201111561066e57600080fd5b803590602001918460208302840111600160201b8311171561068f57600080fd5b919390929091602081019035600160201b8111156106ac57600080fd5b8201836020820111156106be57600080fd5b803590602001918460208302840111600160201b831117156106df57600080fd5b919390929091602081019035600160201b8111156106fc57600080fd5b82018360208201111561070e57600080fd5b803590602001918460018302840111600160201b8311171561072f57600080fd5b509092509050610e69565b6105466004803603604081101561075057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561077a57600080fd5b82018360208201111561078c57600080fd5b803590602001918460018302840111600160201b831117156107ad57600080fd5b509092509050610e7d565b61033a600480360360a08110156107ce57600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135600160201b81111561080d57600080fd5b82018360208201111561081f57600080fd5b803590602001918460018302840111600160201b8311171561084057600080fd5b509092509050610ec7565b610546610ed9565b5050505050505050565b60006001600160e01b03198216630271189760e51b148061088e57506001600160e01b03198216630a85bd0160e11b145b806108a957506001600160e01b031982166301ffc9a760e01b145b92915050565b60006108a93383610bf4565b630a85bd0160e11b95945050505050565b6040805160208082018690528251808303820181528284018085526320c13b0b60e01b9052604483019384528051608484015280516000943394869486946320c13b0b9490938b938b9391928392606483019260a40191908801908083838e5b8381101561094457818101518382015260200161092c565b50505050905090810190601f1680156109715780820380516001836020036101000a031916815260200191505b508381038252848152602001858580828437600081840152601f19601f8201169050808301925050509550505050505060206040518083038186803b1580156109b957600080fd5b505afa1580156109cd573d6000803e3d6000fd5b505050506040513d60208110156109e357600080fd5b505190506001600160e01b031981166320c13b0b60e01b14610a06576000610a0f565b630b135d3f60e11b5b925050505b9392505050565b6000803390506000610a638288888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610bf492505050565b905083610b2457816001600160a01b0316635ae6bd37826040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015610aae57600080fd5b505afa158015610ac2573d6000803e3d6000fd5b505050506040513d6020811015610ad857600080fd5b5051610b1f576040805162461bcd60e51b815260206004820152601160248201527012185cda081b9bdd08185c1c1c9bdd9959607a1b604482015290519081900360640190fd5b610be1565b816001600160a01b031663934f3a1182898989896040518663ffffffff1660e01b81526004018086815260200180602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f82011690508083019250505097505050505050505060006040518083038186803b158015610bc857600080fd5b505afa158015610bdc573d6000803e3d6000fd5b505050505b506320c13b0b60e01b9695505050505050565b6000807f60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca60001b83805190602001206040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209050601960f81b600160f81b856001600160a01b031663f698da256040518163ffffffff1660e01b815260040160206040518083038186803b158015610c9557600080fd5b505afa158015610ca9573d6000803e3d6000fd5b505050506040513d6020811015610cbf57600080fd5b5051604080516001600160f81b0319948516602080830191909152939094166021850152602284019190915260428084019490945280518084039094018452606290920190915281519101209392505050565b6040518060400160405280601881526020017f44656661756c742043616c6c6261636b2048616e646c6572000000000000000081525081565b60408051636617c22960e11b815260016004820152600a602482015290516060913391600091839163cc2f8452916044808201928692909190829003018186803b158015610d9857600080fd5b505afa158015610dac573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040908152811015610dd557600080fd5b8101908080516040519392919084600160201b821115610df457600080fd5b908301906020820185811115610e0957600080fd5b82518660208202830111600160201b82111715610e2557600080fd5b82525081516020918201928201910280838360005b83811015610e52578181015183820152602001610e3a565b505050509190910160405250929550505050505090565b63bc197c8160e01b98975050505050505050565b606060405163b4faba0960e01b8152600436036004808301376020600036836000335af1505060203d036040519150808201604052806020833e50600051610a1457805160208201fd5b63f23a6e6160e01b9695505050505050565b604051806040016040528060058152602001640312e302e360dc1b8152508156fea2646970667358221220db5b4deab2382d11afa42a14f202f9a1a34be7b0510e05121e3d8e939e64e5f864736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{"getMessageHash(bytes)":{"details":"Returns hash of a message that can be signed by owners.","params":{"message":"Message that should be hashed"},"returns":{"_0":"Message hash."}},"getMessageHashForSafe(address,bytes)":{"details":"Returns hash of a message that can be signed by owners.","params":{"message":"Message that should be hashed","safe":"Safe to which the message is targeted"},"returns":{"_0":"Message hash."}},"getModules()":{"details":"Returns array of first 10 modules.","returns":{"_0":"Array of modules."}},"isValidSignature(bytes,bytes)":{"details":"Should return whether the signature provided is valid for the provided data.","params":{"_data":"Arbitrary length data signed on the behalf of address(msg.sender)","_signature":"Signature byte array associated with _data"},"returns":{"_0":"a bool upon valid or invalid signature with corresponding _data"}},"isValidSignature(bytes32,bytes)":{"details":"Should return whether the signature provided is valid for the provided data. The save does not implement the interface since `checkSignatures` is not a view method. The method will not perform any state changes (see parameters of `checkSignatures`)","params":{"_dataHash":"Hash of the data signed on the behalf of address(msg.sender)","_signature":"Signature byte array associated with _dataHash"},"returns":{"_0":"a bool upon valid or invalid signature with corresponding _dataHash"}},"simulate(address,bytes)":{"details":"Performs a delegetecall on a targetContract in the context of self. Internally reverts execution to avoid side effects (making it static). Catches revert and returns encoded result as bytes.","params":{"calldataPayload":"Calldata that should be sent to the target contract (encoded method name and arguments).","targetContract":"Address of the contract containing the code to execute."}},"supportsInterface(bytes4)":{"details":"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas."}},"title":"Compatibility Fallback Handler - fallback handler to provider compatibility between pre 1.3.0 and 1.3.0+ Safe contracts","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50610f30806100206000396000f3fe608060405234801561001057600080fd5b50600436106100ce5760003560e01c80636ac247841161008c578063bc197c8111610066578063bc197c8114610613578063bd61951d1461073a578063f23a6e61146107b8578063ffa1ad741461084b576100ce565b80636ac247841461048a578063a3f4df7e1461053e578063b2494df3146105bb576100ce565b806223de29146100d357806301ffc9a7146101bb5780630a1028c4146101f6578063150b7a02146102ac5780631626ba7e1461035757806320c13b0b146103cc575b600080fd5b6101b9600480360360c08110156100e957600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b81111561012b57600080fd5b82018360208201111561013d57600080fd5b803590602001918460018302840111600160201b8311171561015e57600080fd5b919390929091602081019035600160201b81111561017b57600080fd5b82018360208201111561018d57600080fd5b803590602001918460018302840111600160201b831117156101ae57600080fd5b509092509050610853565b005b6101e2600480360360208110156101d157600080fd5b50356001600160e01b03191661085d565b604080519115158252519081900360200190f35b61029a6004803603602081101561020c57600080fd5b810190602081018135600160201b81111561022657600080fd5b82018360208201111561023857600080fd5b803590602001918460018302840111600160201b8311171561025957600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506108af945050505050565b60408051918252519081900360200190f35b61033a600480360360808110156102c257600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b8111156102fc57600080fd5b82018360208201111561030e57600080fd5b803590602001918460018302840111600160201b8311171561032f57600080fd5b5090925090506108bb565b604080516001600160e01b03199092168252519081900360200190f35b61033a6004803603604081101561036d57600080fd5b81359190810190604081016020820135600160201b81111561038e57600080fd5b8201836020820111156103a057600080fd5b803590602001918460018302840111600160201b831117156103c157600080fd5b5090925090506108cc565b61033a600480360360408110156103e257600080fd5b810190602081018135600160201b8111156103fc57600080fd5b82018360208201111561040e57600080fd5b803590602001918460018302840111600160201b8311171561042f57600080fd5b919390929091602081019035600160201b81111561044c57600080fd5b82018360208201111561045e57600080fd5b803590602001918460018302840111600160201b8311171561047f57600080fd5b509092509050610a1b565b61029a600480360360408110156104a057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156104ca57600080fd5b8201836020820111156104dc57600080fd5b803590602001918460018302840111600160201b831117156104fd57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610bf4945050505050565b610546610d12565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610580578181015183820152602001610568565b50505050905090810190601f1680156105ad5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6105c3610d4b565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156105ff5781810151838201526020016105e7565b505050509050019250505060405180910390f35b61033a600480360360a081101561062957600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561065c57600080fd5b82018360208201111561066e57600080fd5b803590602001918460208302840111600160201b8311171561068f57600080fd5b919390929091602081019035600160201b8111156106ac57600080fd5b8201836020820111156106be57600080fd5b803590602001918460208302840111600160201b831117156106df57600080fd5b919390929091602081019035600160201b8111156106fc57600080fd5b82018360208201111561070e57600080fd5b803590602001918460018302840111600160201b8311171561072f57600080fd5b509092509050610e69565b6105466004803603604081101561075057600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561077a57600080fd5b82018360208201111561078c57600080fd5b803590602001918460018302840111600160201b831117156107ad57600080fd5b509092509050610e7d565b61033a600480360360a08110156107ce57600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135600160201b81111561080d57600080fd5b82018360208201111561081f57600080fd5b803590602001918460018302840111600160201b8311171561084057600080fd5b509092509050610ec7565b610546610ed9565b5050505050505050565b60006001600160e01b03198216630271189760e51b148061088e57506001600160e01b03198216630a85bd0160e11b145b806108a957506001600160e01b031982166301ffc9a760e01b145b92915050565b60006108a93383610bf4565b630a85bd0160e11b95945050505050565b6040805160208082018690528251808303820181528284018085526320c13b0b60e01b9052604483019384528051608484015280516000943394869486946320c13b0b9490938b938b9391928392606483019260a40191908801908083838e5b8381101561094457818101518382015260200161092c565b50505050905090810190601f1680156109715780820380516001836020036101000a031916815260200191505b508381038252848152602001858580828437600081840152601f19601f8201169050808301925050509550505050505060206040518083038186803b1580156109b957600080fd5b505afa1580156109cd573d6000803e3d6000fd5b505050506040513d60208110156109e357600080fd5b505190506001600160e01b031981166320c13b0b60e01b14610a06576000610a0f565b630b135d3f60e11b5b925050505b9392505050565b6000803390506000610a638288888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610bf492505050565b905083610b2457816001600160a01b0316635ae6bd37826040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015610aae57600080fd5b505afa158015610ac2573d6000803e3d6000fd5b505050506040513d6020811015610ad857600080fd5b5051610b1f576040805162461bcd60e51b815260206004820152601160248201527012185cda081b9bdd08185c1c1c9bdd9959607a1b604482015290519081900360640190fd5b610be1565b816001600160a01b031663934f3a1182898989896040518663ffffffff1660e01b81526004018086815260200180602001806020018381038352878782818152602001925080828437600083820152601f01601f191690910184810383528581526020019050858580828437600081840152601f19601f82011690508083019250505097505050505050505060006040518083038186803b158015610bc857600080fd5b505afa158015610bdc573d6000803e3d6000fd5b505050505b506320c13b0b60e01b9695505050505050565b6000807f60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca60001b83805190602001206040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209050601960f81b600160f81b856001600160a01b031663f698da256040518163ffffffff1660e01b815260040160206040518083038186803b158015610c9557600080fd5b505afa158015610ca9573d6000803e3d6000fd5b505050506040513d6020811015610cbf57600080fd5b5051604080516001600160f81b0319948516602080830191909152939094166021850152602284019190915260428084019490945280518084039094018452606290920190915281519101209392505050565b6040518060400160405280601881526020017f44656661756c742043616c6c6261636b2048616e646c6572000000000000000081525081565b60408051636617c22960e11b815260016004820152600a602482015290516060913391600091839163cc2f8452916044808201928692909190829003018186803b158015610d9857600080fd5b505afa158015610dac573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040908152811015610dd557600080fd5b8101908080516040519392919084600160201b821115610df457600080fd5b908301906020820185811115610e0957600080fd5b82518660208202830111600160201b82111715610e2557600080fd5b82525081516020918201928201910280838360005b83811015610e52578181015183820152602001610e3a565b505050509190910160405250929550505050505090565b63bc197c8160e01b98975050505050505050565b606060405163b4faba0960e01b8152600436036004808301376020600036836000335af1505060203d036040519150808201604052806020833e50600051610a1457805160208201fd5b63f23a6e6160e01b9695505050505050565b604051806040016040528060058152602001640312e302e360dc1b8152508156fea2646970667358221220db5b4deab2382d11afa42a14f202f9a1a34be7b0510e05121e3d8e939e64e5f864736f6c63430007060033"},"sourceId":"handler/CompatibilityFallbackHandler.sol","userdoc":{"kind":"user","methods":{"isValidSignature(bytes,bytes)":{"notice":"Implementation of ISignatureValidator (see `interfaces/ISignatureValidator.sol`)"},"isValidSignature(bytes32,bytes)":{"notice":"Implementation of updated EIP-1271See https://github.com/gnosis/util-contracts/blob/bb5fe5fb5df6d8400998094fb1b32a178a47c3a1/contracts/StorageAccessible.sol"}},"version":1}},"CreateCall":{"abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newContract","type":"address"}],"name":"ContractCreation","type":"event"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"deploymentData","type":"bytes"}],"name":"performCreate","outputs":[{"internalType":"address","name":"newContract","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"deploymentData","type":"bytes"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"name":"performCreate2","outputs":[{"internalType":"address","name":"newContract","type":"address"}],"stateMutability":"nonpayable","type":"function"}],"contractName":"CreateCall","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50610335806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80634847be6f1461003b5780634c8c9ea114610106575b600080fd5b6100ea6004803603606081101561005157600080fd5b8135919081019060408101602082013564010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955050913592506101b3915050565b604080516001600160a01b039092168252519081900360200190f35b6100ea6004803603604081101561011c57600080fd5b8135919081019060408101602082013564010000000081111561013e57600080fd5b82018360208201111561015057600080fd5b8035906020019184600183028401116401000000008311171561017257600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061025a945050505050565b60008183518460200186f590506001600160a01b038116610217576040805162461bcd60e51b815260206004820152601960248201527810dbdd5b19081b9bdd0819195c1b1bde4818dbdb9d1c9858dd603a1b604482015290519081900360640190fd5b604080516001600160a01b038316815290517f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b5119181900360200190a19392505050565b600081516020830184f090506001600160a01b0381166102bd576040805162461bcd60e51b815260206004820152601960248201527810dbdd5b19081b9bdd0819195c1b1bde4818dbdb9d1c9858dd603a1b604482015290519081900360640190fd5b604080516001600160a01b038316815290517f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b5119181900360200190a19291505056fea2646970667358221220bf63b3266b646b3c53d86b6294e6724f31be386adb01eeba97a58091cb72899864736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"Create Call - Allows to use the different create opcodes to deploy a contract","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50610335806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80634847be6f1461003b5780634c8c9ea114610106575b600080fd5b6100ea6004803603606081101561005157600080fd5b8135919081019060408101602082013564010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955050913592506101b3915050565b604080516001600160a01b039092168252519081900360200190f35b6100ea6004803603604081101561011c57600080fd5b8135919081019060408101602082013564010000000081111561013e57600080fd5b82018360208201111561015057600080fd5b8035906020019184600183028401116401000000008311171561017257600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061025a945050505050565b60008183518460200186f590506001600160a01b038116610217576040805162461bcd60e51b815260206004820152601960248201527810dbdd5b19081b9bdd0819195c1b1bde4818dbdb9d1c9858dd603a1b604482015290519081900360640190fd5b604080516001600160a01b038316815290517f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b5119181900360200190a19392505050565b600081516020830184f090506001600160a01b0381166102bd576040805162461bcd60e51b815260206004820152601960248201527810dbdd5b19081b9bdd0819195c1b1bde4818dbdb9d1c9858dd603a1b604482015290519081900360640190fd5b604080516001600160a01b038316815290517f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b5119181900360200190a19291505056fea2646970667358221220bf63b3266b646b3c53d86b6294e6724f31be386adb01eeba97a58091cb72899864736f6c63430007060033"},"sourceId":"libraries/CreateCall.sol","userdoc":{"kind":"user","methods":{},"version":1}},"DebugTransactionGuard":{"abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"safe","type":"address"},{"indexed":true,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"}],"name":"GasUsage","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"safe","type":"address"},{"indexed":true,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"indexed":false,"internalType":"bool","name":"usesRefund","type":"bool"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"TransactionDetails","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[{"internalType":"bytes32","name":"txHash","type":"bytes32"},{"internalType":"bool","name":"success","type":"bool"}],"name":"checkAfterExecution","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address payable","name":"refundReceiver","type":"address"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"address","name":"","type":"address"}],"name":"checkTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"txNonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"contractName":"DebugTransactionGuard","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506105e2806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806375f0bb521461004357806393271368146101c2578063ddbdba63146101e7575b005b610041600480360361016081101561005a57600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561008a57600080fd5b82018360208201111561009c57600080fd5b803590602001918460018302840111640100000000831117156100be57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929560ff8535169560208601359560408101359550606081013594506001600160a01b0360808201358116945060a08201351692919060e081019060c0013564010000000081111561014257600080fd5b82018360208201111561015457600080fd5b8035906020019184600183028401116401000000008311171561017657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b031691506102169050565b610041600480360360408110156101d857600080fd5b508035906020013515156104f2565b610204600480360360208110156101fd57600080fd5b503561059a565b60408051918252519081900360200190f35b60008060003390506001816001600160a01b031663affed0e06040518163ffffffff1660e01b815260040160206040518083038186803b15801561025957600080fd5b505afa15801561026d573d6000803e3d6000fd5b505050506040513d602081101561028357600080fd5b8101908080519060200190929190505050039250806001600160a01b031663d8d11f788f8f8f8f8f8f8f8f8f8d6040518b63ffffffff1660e01b8152600401808b6001600160a01b031681526020018a8152602001806020018960018111156102e857fe5b8152602001888152602001878152602001868152602001856001600160a01b03168152602001846001600160a01b0316815260200183815260200182810382528a818151815260200191508051906020019080838360005b83811015610358578181015183820152602001610340565b50505050905090810190601f1680156103855780820380516001836020036101000a031916815260200191505b509b50505050505050505050505060206040518083038186803b1580156103ab57600080fd5b505afa1580156103bf573d6000803e3d6000fd5b505050506040513d60208110156103d557600080fd5b810190808051906020019092919050505091505080336001600160a01b03167f1b68334da83afaffd8348d6c85c5146d8e7de31a6c034d2d522be12d615c4f7b8f8f8f8f8f60008f118a60405180886001600160a01b031681526020018781526020018060200186600181111561044857fe5b81526020018581526020018415158152602001838152602001828103825287818151815260200191508051906020019080838360005b8381101561049657818101518382015260200161047e565b50505050905090810190601f1680156104c35780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390a36000908152602081905260409020555050505050505050505050565b60008281526020819052604090205480610549576040805162461bcd60e51b8152602060048201526013602482015272436f756c64206e6f7420676574206e6f6e636560681b604482015290519081900360640190fd5b600083815260208181526040808320929092558151841515815291518392869233927f0dcc0fb56a30b6fe6b188f45b47369bc7f3c928a9748e245a79fc3f54ddd05689281900390910190a4505050565b6000602081905290815260409020548156fea2646970667358221220518a981bdf4ee66556ce784114e123f79a6c403368d8cb5fdb757c8f3d4500ea64736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"Debug Transaction Guard - A guard that will emit events with extended information.","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506105e2806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806375f0bb521461004357806393271368146101c2578063ddbdba63146101e7575b005b610041600480360361016081101561005a57600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561008a57600080fd5b82018360208201111561009c57600080fd5b803590602001918460018302840111640100000000831117156100be57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929560ff8535169560208601359560408101359550606081013594506001600160a01b0360808201358116945060a08201351692919060e081019060c0013564010000000081111561014257600080fd5b82018360208201111561015457600080fd5b8035906020019184600183028401116401000000008311171561017657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b031691506102169050565b610041600480360360408110156101d857600080fd5b508035906020013515156104f2565b610204600480360360208110156101fd57600080fd5b503561059a565b60408051918252519081900360200190f35b60008060003390506001816001600160a01b031663affed0e06040518163ffffffff1660e01b815260040160206040518083038186803b15801561025957600080fd5b505afa15801561026d573d6000803e3d6000fd5b505050506040513d602081101561028357600080fd5b8101908080519060200190929190505050039250806001600160a01b031663d8d11f788f8f8f8f8f8f8f8f8f8d6040518b63ffffffff1660e01b8152600401808b6001600160a01b031681526020018a8152602001806020018960018111156102e857fe5b8152602001888152602001878152602001868152602001856001600160a01b03168152602001846001600160a01b0316815260200183815260200182810382528a818151815260200191508051906020019080838360005b83811015610358578181015183820152602001610340565b50505050905090810190601f1680156103855780820380516001836020036101000a031916815260200191505b509b50505050505050505050505060206040518083038186803b1580156103ab57600080fd5b505afa1580156103bf573d6000803e3d6000fd5b505050506040513d60208110156103d557600080fd5b810190808051906020019092919050505091505080336001600160a01b03167f1b68334da83afaffd8348d6c85c5146d8e7de31a6c034d2d522be12d615c4f7b8f8f8f8f8f60008f118a60405180886001600160a01b031681526020018781526020018060200186600181111561044857fe5b81526020018581526020018415158152602001838152602001828103825287818151815260200191508051906020019080838360005b8381101561049657818101518382015260200161047e565b50505050905090810190601f1680156104c35780820380516001836020036101000a031916815260200191505b509850505050505050505060405180910390a36000908152602081905260409020555050505050505050505050565b60008281526020819052604090205480610549576040805162461bcd60e51b8152602060048201526013602482015272436f756c64206e6f7420676574206e6f6e636560681b604482015290519081900360640190fd5b600083815260208181526040808320929092558151841515815291518392869233927f0dcc0fb56a30b6fe6b188f45b47369bc7f3c928a9748e245a79fc3f54ddd05689281900390910190a4505050565b6000602081905290815260409020548156fea2646970667358221220518a981bdf4ee66556ce784114e123f79a6c403368d8cb5fdb757c8f3d4500ea64736f6c63430007060033"},"sourceId":"examples/guards/DebugTransactionGuard.sol","userdoc":{"kind":"user","methods":{},"notice":"This guard is only meant as a development tool and example","version":1}},"DefaultCallbackHandler":{"abi":[{"inputs":[],"name":"NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"tokensReceived","outputs":[],"stateMutability":"pure","type":"function"}],"contractName":"DefaultCallbackHandler","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506105b1806100206000396000f3fe608060405234801561001057600080fd5b506004361061007c5760003560e01c8063a3f4df7e1161005b578063a3f4df7e1461024f578063bc197c81146102cc578063f23a6e61146103f3578063ffa1ad74146104865761007c565b806223de291461008157806301ffc9a714610169578063150b7a02146101a4575b600080fd5b610167600480360360c081101561009757600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b8111156100d957600080fd5b8201836020820111156100eb57600080fd5b803590602001918460018302840111600160201b8311171561010c57600080fd5b919390929091602081019035600160201b81111561012957600080fd5b82018360208201111561013b57600080fd5b803590602001918460018302840111600160201b8311171561015c57600080fd5b50909250905061048e565b005b6101906004803603602081101561017f57600080fd5b50356001600160e01b031916610498565b604080519115158252519081900360200190f35b610232600480360360808110156101ba57600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b8111156101f457600080fd5b82018360208201111561020657600080fd5b803590602001918460018302840111600160201b8311171561022757600080fd5b5090925090506104ea565b604080516001600160e01b03199092168252519081900360200190f35b6102576104fb565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610291578181015183820152602001610279565b50505050905090810190601f1680156102be5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610232600480360360a08110156102e257600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561031557600080fd5b82018360208201111561032757600080fd5b803590602001918460208302840111600160201b8311171561034857600080fd5b919390929091602081019035600160201b81111561036557600080fd5b82018360208201111561037757600080fd5b803590602001918460208302840111600160201b8311171561039857600080fd5b919390929091602081019035600160201b8111156103b557600080fd5b8201836020820111156103c757600080fd5b803590602001918460018302840111600160201b831117156103e857600080fd5b509092509050610534565b610232600480360360a081101561040957600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135600160201b81111561044857600080fd5b82018360208201111561045a57600080fd5b803590602001918460018302840111600160201b8311171561047b57600080fd5b509092509050610548565b61025761055a565b5050505050505050565b60006001600160e01b03198216630271189760e51b14806104c957506001600160e01b03198216630a85bd0160e11b145b806104e457506001600160e01b031982166301ffc9a760e01b145b92915050565b630a85bd0160e11b95945050505050565b6040518060400160405280601881526020017f44656661756c742043616c6c6261636b2048616e646c6572000000000000000081525081565b63bc197c8160e01b98975050505050505050565b63f23a6e6160e01b9695505050505050565b604051806040016040528060058152602001640312e302e360dc1b8152508156fea2646970667358221220262e250c6f911b9b9db625d5798dca74dfbf9895f59303d056f588d915aa2a1964736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{"supportsInterface(bytes4)":{"details":"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas."}},"title":"Default Callback Handler - returns true for known token callbacks","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506105b1806100206000396000f3fe608060405234801561001057600080fd5b506004361061007c5760003560e01c8063a3f4df7e1161005b578063a3f4df7e1461024f578063bc197c81146102cc578063f23a6e61146103f3578063ffa1ad74146104865761007c565b806223de291461008157806301ffc9a714610169578063150b7a02146101a4575b600080fd5b610167600480360360c081101561009757600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b8111156100d957600080fd5b8201836020820111156100eb57600080fd5b803590602001918460018302840111600160201b8311171561010c57600080fd5b919390929091602081019035600160201b81111561012957600080fd5b82018360208201111561013b57600080fd5b803590602001918460018302840111600160201b8311171561015c57600080fd5b50909250905061048e565b005b6101906004803603602081101561017f57600080fd5b50356001600160e01b031916610498565b604080519115158252519081900360200190f35b610232600480360360808110156101ba57600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b8111156101f457600080fd5b82018360208201111561020657600080fd5b803590602001918460018302840111600160201b8311171561022757600080fd5b5090925090506104ea565b604080516001600160e01b03199092168252519081900360200190f35b6102576104fb565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610291578181015183820152602001610279565b50505050905090810190601f1680156102be5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610232600480360360a08110156102e257600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561031557600080fd5b82018360208201111561032757600080fd5b803590602001918460208302840111600160201b8311171561034857600080fd5b919390929091602081019035600160201b81111561036557600080fd5b82018360208201111561037757600080fd5b803590602001918460208302840111600160201b8311171561039857600080fd5b919390929091602081019035600160201b8111156103b557600080fd5b8201836020820111156103c757600080fd5b803590602001918460018302840111600160201b831117156103e857600080fd5b509092509050610534565b610232600480360360a081101561040957600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135600160201b81111561044857600080fd5b82018360208201111561045a57600080fd5b803590602001918460018302840111600160201b8311171561047b57600080fd5b509092509050610548565b61025761055a565b5050505050505050565b60006001600160e01b03198216630271189760e51b14806104c957506001600160e01b03198216630a85bd0160e11b145b806104e457506001600160e01b031982166301ffc9a760e01b145b92915050565b630a85bd0160e11b95945050505050565b6040518060400160405280601881526020017f44656661756c742043616c6c6261636b2048616e646c6572000000000000000081525081565b63bc197c8160e01b98975050505050505050565b63f23a6e6160e01b9695505050505050565b604051806040016040528060058152602001640312e302e360dc1b8152508156fea2646970667358221220262e250c6f911b9b9db625d5798dca74dfbf9895f59303d056f588d915aa2a1964736f6c63430007060033"},"sourceId":"handler/DefaultCallbackHandler.sol","userdoc":{"kind":"user","methods":{},"version":1}},"DelegateCallTransactionGuard":{"abi":[{"inputs":[{"internalType":"address","name":"target","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"allowedTarget","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bool","name":"","type":"bool"}],"name":"checkAfterExecution","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address payable","name":"","type":"address"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"address","name":"","type":"address"}],"name":"checkTransaction","outputs":[],"stateMutability":"view","type":"function"}],"contractName":"DelegateCallTransactionGuard","deploymentBytecode":{"bytecode":"0x60a060405234801561001057600080fd5b5060405161037f38038061037f8339818101604052602081101561003357600080fd5b5051606081901b6001600160601b0319166080526001600160a01b031661031361006c6000398061020d528061024752506103136000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806373a8c6821461004357806375f0bb521461006757806393271368146101e6575b005b61004b61020b565b604080516001600160a01b039092168252519081900360200190f35b610041600480360361016081101561007e57600080fd5b6001600160a01b03823516916020810135918101906060810160408201356401000000008111156100ae57600080fd5b8201836020820111156100c057600080fd5b803590602001918460018302840111640100000000831117156100e257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929560ff8535169560208601359560408101359550606081013594506001600160a01b0360808201358116945060a08201351692919060e081019060c0013564010000000081111561016657600080fd5b82018360208201111561017857600080fd5b8035906020019184600183028401116401000000008311171561019a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b0316915061022f9050565b610041600480360360408110156101fc57600080fd5b508035906020013515156102d9565b7f000000000000000000000000000000000000000000000000000000000000000081565b600188600181111561023d57fe5b14158061027b57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168b6001600160a01b0316145b6102cc576040805162461bcd60e51b815260206004820152601760248201527f546869732063616c6c2069732072657374726963746564000000000000000000604482015290519081900360640190fd5b5050505050505050505050565b505056fea26469706673582212202654803eea9c51f242ae3bb7e410c3b716ff60e40810a1d6fd84799dfc41622b64736f6c63430007060033"},"devdoc":{"kind":"dev","methods":{},"version":1},"runtimeBytecode":{"bytecode":"0x60a060405234801561001057600080fd5b5060405161037f38038061037f8339818101604052602081101561003357600080fd5b5051606081901b6001600160601b0319166080526001600160a01b031661031361006c6000398061020d528061024752506103136000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806373a8c6821461004357806375f0bb521461006757806393271368146101e6575b005b61004b61020b565b604080516001600160a01b039092168252519081900360200190f35b610041600480360361016081101561007e57600080fd5b6001600160a01b03823516916020810135918101906060810160408201356401000000008111156100ae57600080fd5b8201836020820111156100c057600080fd5b803590602001918460018302840111640100000000831117156100e257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929560ff8535169560208601359560408101359550606081013594506001600160a01b0360808201358116945060a08201351692919060e081019060c0013564010000000081111561016657600080fd5b82018360208201111561017857600080fd5b8035906020019184600183028401116401000000008311171561019a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b0316915061022f9050565b610041600480360360408110156101fc57600080fd5b508035906020013515156102d9565b7f000000000000000000000000000000000000000000000000000000000000000081565b600188600181111561023d57fe5b14158061027b57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168b6001600160a01b0316145b6102cc576040805162461bcd60e51b815260206004820152601760248201527f546869732063616c6c2069732072657374726963746564000000000000000000604482015290519081900360640190fd5b5050505050505050505050565b505056fea26469706673582212202654803eea9c51f242ae3bb7e410c3b716ff60e40810a1d6fd84799dfc41622b64736f6c63430007060033"},"sourceId":"examples/guards/DelegateCallTransactionGuard.sol","userdoc":{"kind":"user","methods":{},"version":1}},"Enum":{"abi":[],"contractName":"Enum","deploymentBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220348181afc1e5dfd8c449390625e82cf87af05d1bfff735b696ea5a4fffb535ff64736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"Enum - Collection of enums","version":1},"runtimeBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220348181afc1e5dfd8c449390625e82cf87af05d1bfff735b696ea5a4fffb535ff64736f6c63430007060033"},"sourceId":"common/Enum.sol","userdoc":{"kind":"user","methods":{},"version":1}},"EtherPaymentFallback":{"abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SafeReceived","type":"event"},{"stateMutability":"payable","type":"receive"}],"contractName":"EtherPaymentFallback","deploymentBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50607b8061001e6000396000f3fe60806040523660405760408051348152905133917f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d919081900360200190a2005b600080fdfea2646970667358221220db5fee1c5d5bec9419dedc4013219bed02edcb692584c15af15b11533923ffd664736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"EtherPaymentFallback - A contract that has a fallback to accept ether payments","version":1},"runtimeBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50607b8061001e6000396000f3fe60806040523660405760408051348152905133917f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d919081900360200190a2005b600080fdfea2646970667358221220db5fee1c5d5bec9419dedc4013219bed02edcb692584c15af15b11533923ffd664736f6c63430007060033"},"sourceId":"common/EtherPaymentFallback.sol","userdoc":{"kind":"user","methods":{},"version":1}},"Executor":{"abi":[],"contractName":"Executor","deploymentBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea26469706673582212200934578af5859bc2618c24a6a9e2ce52b60dd1c548137df78869b93a516326f264736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"Executor - A contract that can execute transactions","version":1},"runtimeBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea26469706673582212200934578af5859bc2618c24a6a9e2ce52b60dd1c548137df78869b93a516326f264736f6c63430007060033"},"sourceId":"base/Executor.sol","userdoc":{"kind":"user","methods":{},"version":1}},"FallbackManager":{"abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"handler","type":"address"}],"name":"ChangedFallbackHandler","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[{"internalType":"address","name":"handler","type":"address"}],"name":"setFallbackHandler","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"FallbackManager","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50610194806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f08a032314610084575b7f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d580548061005557005b36600080373360601b365260008060143601600080855af190503d6000803e8061007e573d6000fd5b503d6000f35b6100aa6004803603602081101561009a57600080fd5b50356001600160a01b03166100ac565b005b6100b46100fc565b6100bd8161013a565b604080516001600160a01b038316815290517f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b09181900360200190a150565b333014610138576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b565b7f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d55556fea2646970667358221220ed587905ed6a3af06825d45d130565b7c3a3456c5ae1422352963ff87ebc8b2464736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{"setFallbackHandler(address)":{"details":"Allows to add a contract to handle fallback calls. Only fallback calls without value and with data will be forwarded. This can only be done via a Safe transaction.","params":{"handler":"contract to handle fallbacks calls."}}},"title":"Fallback Manager - A contract that manages fallback calls made to this contract","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50610194806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f08a032314610084575b7f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d580548061005557005b36600080373360601b365260008060143601600080855af190503d6000803e8061007e573d6000fd5b503d6000f35b6100aa6004803603602081101561009a57600080fd5b50356001600160a01b03166100ac565b005b6100b46100fc565b6100bd8161013a565b604080516001600160a01b038316815290517f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b09181900360200190a150565b333014610138576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b565b7f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d55556fea2646970667358221220ed587905ed6a3af06825d45d130565b7c3a3456c5ae1422352963ff87ebc8b2464736f6c63430007060033"},"sourceId":"base/FallbackManager.sol","userdoc":{"kind":"user","methods":{},"version":1}},"GnosisSafe":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"AddedOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"approvedHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"owner","type":"address"}],"name":"ApproveHash","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"handler","type":"address"}],"name":"ChangedFallbackHandler","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"guard","type":"address"}],"name":"ChangedGuard","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"threshold","type":"uint256"}],"name":"ChangedThreshold","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"module","type":"address"}],"name":"DisabledModule","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"module","type":"address"}],"name":"EnabledModule","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"payment","type":"uint256"}],"name":"ExecutionFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"}],"name":"ExecutionFromModuleFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"}],"name":"ExecutionFromModuleSuccess","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"payment","type":"uint256"}],"name":"ExecutionSuccess","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"RemovedOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SafeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"initiator","type":"address"},{"indexed":false,"internalType":"address[]","name":"owners","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"threshold","type":"uint256"},{"indexed":false,"internalType":"address","name":"initializer","type":"address"},{"indexed":false,"internalType":"address","name":"fallbackHandler","type":"address"}],"name":"SafeSetup","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"msgHash","type":"bytes32"}],"name":"SignMsg","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"addOwnerWithThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hashToApprove","type":"bytes32"}],"name":"approveHash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"approvedHashes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"changeThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"dataHash","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bytes","name":"signatures","type":"bytes"},{"internalType":"uint256","name":"requiredSignatures","type":"uint256"}],"name":"checkNSignatures","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"dataHash","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bytes","name":"signatures","type":"bytes"}],"name":"checkSignatures","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"prevModule","type":"address"},{"internalType":"address","name":"module","type":"address"}],"name":"disableModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"enableModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address","name":"refundReceiver","type":"address"},{"internalType":"uint256","name":"_nonce","type":"uint256"}],"name":"encodeTransactionData","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address payable","name":"refundReceiver","type":"address"},{"internalType":"bytes","name":"signatures","type":"bytes"}],"name":"execTransaction","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"execTransactionFromModule","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"execTransactionFromModuleReturnData","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"start","type":"address"},{"internalType":"uint256","name":"pageSize","type":"uint256"}],"name":"getModulesPaginated","outputs":[{"internalType":"address[]","name":"array","type":"address[]"},{"internalType":"address","name":"next","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOwners","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"offset","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"getStorageAt","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address","name":"refundReceiver","type":"address"},{"internalType":"uint256","name":"_nonce","type":"uint256"}],"name":"getTransactionHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"isModuleEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"prevOwner","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"removeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"requiredTxGas","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"handler","type":"address"}],"name":"setFallbackHandler","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"guard","type":"address"}],"name":"setGuard","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_owners","type":"address[]"},{"internalType":"uint256","name":"_threshold","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"address","name":"fallbackHandler","type":"address"},{"internalType":"address","name":"paymentToken","type":"address"},{"internalType":"uint256","name":"payment","type":"uint256"},{"internalType":"address payable","name":"paymentReceiver","type":"address"}],"name":"setup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"signedMessages","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"targetContract","type":"address"},{"internalType":"bytes","name":"calldataPayload","type":"bytes"}],"name":"simulateAndRevert","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"prevOwner","type":"address"},{"internalType":"address","name":"oldOwner","type":"address"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"swapOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"contractName":"GnosisSafe","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50600160045561339e806100256000396000f3fe6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611078578063f698da25146110ab578063f8dc5dd9146110c0578063ffa1ad741461110357610219565b8063e19a9dd914610f26578063e318b52b14610f59578063e75235b814610f9e578063e86637db14610fb357610219565b8063cc2f8452116100d1578063cc2f845214610d5f578063d4d9bdcd14610dfc578063d8d11f7814610e26578063e009cfde14610eeb57610219565b8063affed0e014610af6578063b4faba0914610b0b578063b63e800d14610bcc578063c4ca3a9c14610ccc57610219565b80635624b25b1161017a5780636a761202116101495780636a761202146107dd5780637d8329741461091b578063934f3a1114610954578063a0e67e2b14610a9157610219565b80635624b25b146106b15780635ae6bd3714610756578063610b592514610780578063694e80c3146107b357610219565b80632f54bf6e116101b65780632f54bf6e146104405780633408e47014610473578063468721a71461049a5780635229073f1461056557610219565b80630d582f131461027f57806312fb68e0146102ba5780632d9ad53d146103f957610219565b366102195760408051348152905133917f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d919081900360200190a2005b34801561022557600080fd5b507f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d580548061025057005b36600080373360601b365260008060143601600080855af190503d6000803e80610279573d6000fd5b503d6000f35b34801561028b57600080fd5b506102b8600480360360408110156102a257600080fd5b506001600160a01b038135169060200135611118565b005b3480156102c657600080fd5b506102b8600480360360808110156102dd57600080fd5b81359190810190604081016020820135600160201b8111156102fe57600080fd5b82018360208201111561031057600080fd5b803590602001918460018302840111600160201b8311171561033157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561038357600080fd5b82018360208201111561039557600080fd5b803590602001918460018302840111600160201b831117156103b657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550509135925061129a915050565b34801561040557600080fd5b5061042c6004803603602081101561041c57600080fd5b50356001600160a01b03166117f8565b604080519115158252519081900360200190f35b34801561044c57600080fd5b5061042c6004803603602081101561046357600080fd5b50356001600160a01b0316611833565b34801561047f57600080fd5b5061048861186b565b60408051918252519081900360200190f35b3480156104a657600080fd5b5061042c600480360360808110156104bd57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b8111156104ec57600080fd5b8201836020820111156104fe57600080fd5b803590602001918460018302840111600160201b8311171561051f57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff16915061186f9050565b34801561057157600080fd5b506106306004803603608081101561058857600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b8111156105b757600080fd5b8201836020820111156105c957600080fd5b803590602001918460018302840111600160201b831117156105ea57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff16915061194b9050565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561067557818101518382015260200161065d565b50505050905090810190601f1680156106a25780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b3480156106bd57600080fd5b506106e1600480360360408110156106d457600080fd5b5080359060200135611981565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561071b578181015183820152602001610703565b50505050905090810190601f1680156107485780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561076257600080fd5b506104886004803603602081101561077957600080fd5b50356119f5565b34801561078c57600080fd5b506102b8600480360360208110156107a357600080fd5b50356001600160a01b0316611a07565b3480156107bf57600080fd5b506102b8600480360360208110156107d657600080fd5b5035611b53565b61042c60048036036101408110156107f457600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b81111561082357600080fd5b82018360208201111561083557600080fd5b803590602001918460018302840111600160201b8311171561085657600080fd5b9193909260ff833516926020810135926040820135926060830135926001600160a01b03608082013581169360a083013590911692909160e081019060c00135600160201b8111156108a757600080fd5b8201836020820111156108b957600080fd5b803590602001918460018302840111600160201b831117156108da57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611c13945050505050565b34801561092757600080fd5b506104886004803603604081101561093e57600080fd5b506001600160a01b038135169060200135612011565b34801561096057600080fd5b506102b86004803603606081101561097757600080fd5b81359190810190604081016020820135600160201b81111561099857600080fd5b8201836020820111156109aa57600080fd5b803590602001918460018302840111600160201b831117156109cb57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610a1d57600080fd5b820183602082011115610a2f57600080fd5b803590602001918460018302840111600160201b83111715610a5057600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061202e945050505050565b348015610a9d57600080fd5b50610aa661207d565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610ae2578181015183820152602001610aca565b505050509050019250505060405180910390f35b348015610b0257600080fd5b50610488612161565b348015610b1757600080fd5b506102b860048036036040811015610b2e57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610b5857600080fd5b820183602082011115610b6a57600080fd5b803590602001918460018302840111600160201b83111715610b8b57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612167945050505050565b348015610bd857600080fd5b506102b86004803603610100811015610bf057600080fd5b810190602081018135600160201b811115610c0a57600080fd5b820183602082011115610c1c57600080fd5b803590602001918460208302840111600160201b83111715610c3d57600080fd5b919390928235926001600160a01b03602082013516929190606081019060400135600160201b811115610c6f57600080fd5b820183602082011115610c8157600080fd5b803590602001918460018302840111600160201b83111715610ca257600080fd5b91935091506001600160a01b0381358116916020810135821691604082013591606001351661218a565b348015610cd857600080fd5b5061048860048036036080811015610cef57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b811115610d1e57600080fd5b820183602082011115610d3057600080fd5b803590602001918460018302840111600160201b83111715610d5157600080fd5b91935091503560ff166122da565b348015610d6b57600080fd5b50610d9860048036036040811015610d8257600080fd5b506001600160a01b0381351690602001356123d7565b6040518080602001836001600160a01b03168152602001828103825284818151815260200191508051906020019060200280838360005b83811015610de7578181015183820152602001610dcf565b50505050905001935050505060405180910390f35b348015610e0857600080fd5b506102b860048036036020811015610e1f57600080fd5b50356124c2565b348015610e3257600080fd5b506104886004803603610140811015610e4a57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b811115610e7957600080fd5b820183602082011115610e8b57600080fd5b803590602001918460018302840111600160201b83111715610eac57600080fd5b919350915060ff813516906020810135906040810135906060810135906001600160a01b03608082013581169160a08101359091169060c0013561255c565b348015610ef757600080fd5b506102b860048036036040811015610f0e57600080fd5b506001600160a01b0381358116916020013516612589565b348015610f3257600080fd5b506102b860048036036020811015610f4957600080fd5b50356001600160a01b03166126c3565b348015610f6557600080fd5b506102b860048036036060811015610f7c57600080fd5b506001600160a01b03813581169160208101358216916040909101351661272f565b348015610faa57600080fd5b5061048861297c565b348015610fbf57600080fd5b506106e16004803603610140811015610fd757600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b81111561100657600080fd5b82018360208201111561101857600080fd5b803590602001918460018302840111600160201b8311171561103957600080fd5b919350915060ff813516906020810135906040810135906060810135906001600160a01b03608082013581169160a08101359091169060c00135612982565b34801561108457600080fd5b506102b86004803603602081101561109b57600080fd5b50356001600160a01b0316612ac3565b3480156110b757600080fd5b50610488612b13565b3480156110cc57600080fd5b506102b8600480360360608110156110e357600080fd5b506001600160a01b03813581169160208101359091169060400135612b81565b34801561110f57600080fd5b506106e1612d1a565b611120612d3b565b6001600160a01b0382161580159061114257506001600160a01b038216600114155b801561115757506001600160a01b0382163014155b611190576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b0382811660009081526002602052604090205416156111e5576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b600260209081527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e080546001600160a01b03858116600081815260408082208054949095166001600160a01b031994851617909455600190819052845490921681179093556003805490910190558051918252517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea26929181900390910190a180600454146112965761129681611b53565b5050565b6112a5816041612d79565b825110156112e2576040805162461bcd60e51b8152602060048201526005602482015264047533032360dc1b604482015290519081900360640190fd5b6000808060008060005b868110156117ec576112fe8882612da7565b9195509350915060ff841661159f57919350839161131d876041612d79565b821015611359576040805162461bcd60e51b8152602060048201526005602482015264475330323160d81b604482015290519081900360640190fd5b8751611366836020612dc5565b11156113a1576040805162461bcd60e51b815260206004820152600560248201526423a998191960d91b604482015290519081900360640190fd5b6020828901810151895190916113c49083906113be908790612dc5565b90612dc5565b11156113ff576040805162461bcd60e51b8152602060048201526005602482015264475330323360d81b604482015290519081900360640190fd5b60606020848b010190506320c13b0b60e01b6001600160e01b031916876001600160a01b03166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561147c578181015183820152602001611464565b50505050905090810190601f1680156114a95780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b838110156114dc5781810151838201526020016114c4565b50505050905090810190601f1680156115095780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b15801561152857600080fd5b505afa15801561153c573d6000803e3d6000fd5b505050506040513d602081101561155257600080fd5b50516001600160e01b03191614611598576040805162461bcd60e51b815260206004820152600560248201526411d4cc0c8d60da1b604482015290519081900360640190fd5b5050611756565b8360ff1660011415611627579193508391336001600160a01b03841614806115e957506001600160a01b03851660009081526008602090815260408083208d845290915290205415155b611622576040805162461bcd60e51b8152602060048201526005602482015264475330323560d81b604482015290519081900360640190fd5b611756565b601e8460ff1611156116ef5760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156116de573d6000803e3d6000fd5b505050602060405103519450611756565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611749573d6000803e3d6000fd5b5050506020604051035194505b856001600160a01b0316856001600160a01b031611801561179057506001600160a01b038581166000908152600260205260409020541615155b80156117a657506001600160a01b038516600114155b6117df576040805162461bcd60e51b815260206004820152600560248201526423a998191b60d91b604482015290519081900360640190fd5b93945084936001016112ec565b50505050505050505050565b600060016001600160a01b0383161480159061182d57506001600160a01b038281166000908152600160205260409020541615155b92915050565b60006001600160a01b03821660011480159061182d5750506001600160a01b0390811660009081526002602052604090205416151590565b4690565b6000336001148015906118995750336000908152600160205260409020546001600160a01b031615155b6118d2576040805162461bcd60e51b815260206004820152600560248201526411d4cc4c0d60da1b604482015290519081900360640190fd5b6118df858585855a612dd7565b905080156119175760405133907f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb890600090a2611943565b60405133907facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37590600090a25b949350505050565b6000606061195b8686868661186f565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060008260200267ffffffffffffffff8111801561199f57600080fd5b506040519080825280601f01601f1916602001820160405280156119ca576020820181803683370190505b50905060005b838110156119ed57848101546020808302840101526001016119d0565b509392505050565b60076020526000908152604090205481565b611a0f612d3b565b6001600160a01b03811615801590611a3157506001600160a01b038116600114155b611a6a576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b038181166000908152600160205260409020541615611abf576040805162461bcd60e51b815260206004820152600560248201526423a998981960d91b604482015290519081900360640190fd5b600160208181527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03858116600081815260408082208054949095166001600160a01b0319948516179094559590955282541684179091558051928352517fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f84409281900390910190a150565b611b5b612d3b565b600354811115611b9a576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001811015611bd8576040805162461bcd60e51b815260206004820152600560248201526423a999181960d91b604482015290519081900360640190fd5b60048190556040805182815290517f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c939181900360200190a150565b6000806000611c2d8e8e8e8e8e8e8e8e8e8e600554612982565b6005805460010190558051602082012092509050611c4c82828661202e565b506000611c57612e19565b90506001600160a01b03811615611dd257806001600160a01b03166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d6001600160a01b031681526020018c8152602001806020018a6001811115611cbb57fe5b8152602001898152602001888152602001878152602001866001600160a01b03168152602001856001600160a01b0316815260200180602001846001600160a01b0316815260200183810383528d8d828181526020019250808284376000838201819052601f909101601f191690920185810384528751815287516020918201939189019250908190849084905b83811015611d61578181015183820152602001611d49565b50505050905090810190601f168015611d8e5780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015611db957600080fd5b505af1158015611dcd573d6000803e3d6000fd5b505050505b611de6603f60408b02046109c48b01612e3e565b6101f4015a1015611e26576040805162461bcd60e51b8152602060048201526005602482015264047533031360dc1b604482015290519081900360640190fd5b60005a9050611e8f8f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e8c600014611e84578e611e8a565b6109c45a035b612dd7565b9350611e9c5a8290612e55565b90508380611ea957508915155b80611eb357508715155b611eec576040805162461bcd60e51b8152602060048201526005602482015264475330313360d81b604482015290519081900360640190fd5b60008815611f0457611f01828b8b8b8b612e6a565b90505b8415611f4a57604080518581526020810183905281517f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e929181900390910190a1611f86565b604080518581526020810183905281517f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23929181900390910190a15b50506001600160a01b0381161561200057806001600160a01b0316639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b158015611fe757600080fd5b505af1158015611ffb573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b600860209081526000928352604080842090915290825290205481565b6004548061206b576040805162461bcd60e51b8152602060048201526005602482015264475330303160d81b604482015290519081900360640190fd5b6120778484848461129a565b50505050565b6060600060035467ffffffffffffffff8111801561209a57600080fd5b506040519080825280602002602001820160405280156120c4578160200160208202803683370190505b506001600090815260026020527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e054919250906001600160a01b03165b6001600160a01b038116600114612159578083838151811061211f57fe5b6001600160a01b03928316602091820292909201810191909152918116600090815260029092526040909120546001929092019116612101565b509091505090565b60055481565b600080825160208401855af480600052503d6020523d600060403e60403d016000fd5b6121c88a8a808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508c9250612f7a915050565b6001600160a01b038416156121e0576121e0846131b1565b6122208787878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506131d592505050565b81156122375761223582600060018685612e6a565b505b336001600160a01b03167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b896040518080602001858152602001846001600160a01b03168152602001836001600160a01b031681526020018281038252878782818152602001925060200280828437600083820152604051601f909101601f19169092018290039850909650505050505050a250505050505050505050565b6000805a9050612323878787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525089925050505a612dd7565b61232c57600080fd5b60005a82039050806040516020018082815260200191505060405160208183030381529060405260405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561239c578181015183820152602001612384565b50505050905090810190601f1680156123c95780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff811180156123f257600080fd5b5060405190808252806020026020018201604052801561241c578160200160208202803683370190505b506001600160a01b0380861660009081526001602052604081205492945091165b6001600160a01b0381161580159061245f57506001600160a01b038116600114155b801561246a57508482105b156124b4578084838151811061247c57fe5b6001600160a01b039283166020918202929092018101919091529181166000908152600192839052604090205492909101911661243d565b908352919491935090915050565b336000908152600260205260409020546001600160a01b0316612514576040805162461bcd60e51b8152602060048201526005602482015264047533033360dc1b604482015290519081900360640190fd5b336000818152600860209081526040808320858452909152808220600190555183917ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c91a350565b60006125718c8c8c8c8c8c8c8c8c8c8c612982565b8051906020012090509b9a5050505050505050505050565b612591612d3b565b6001600160a01b038116158015906125b357506001600160a01b038116600114155b6125ec576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b03828116600090815260016020526040902054811690821614612645576040805162461bcd60e51b8152602060048201526005602482015264475331303360d81b604482015290519081900360640190fd5b6001600160a01b038181166000818152600160209081526040808320805488871685528285208054919097166001600160a01b031991821617909655928490528254909416909155825191825291517faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace4054276929181900390910190a15050565b6126cb612d3b565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8818155604080516001600160a01b038416815290517f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa29181900360200190a15050565b612737612d3b565b6001600160a01b0381161580159061275957506001600160a01b038116600114155b801561276e57506001600160a01b0381163014155b6127a7576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b0381811660009081526002602052604090205416156127fc576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b6001600160a01b0382161580159061281e57506001600160a01b038216600114155b612857576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038381166000908152600260205260409020548116908316146128b0576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b038281166000818152600260209081526040808320805487871680865283862080549289166001600160a01b0319938416179055968a16855282852080548216909717909655928490528254909416909155825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a1604080516001600160a01b038316815290517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269181900360200190a1505050565b60045490565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b6001600160a01b031681526020018a8152602001898152602001886001811115612a0657fe5b8152602001878152602001868152602001858152602001846001600160a01b03168152602001836001600160a01b031681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b612a78612b13565b604080516001600160f81b0319948516602082015292909316602183015260228201526042808201939093528151808203909301835260620190529c9b505050505050505050505050565b612acb612d3b565b612ad4816131b1565b604080516001600160a01b038316815290517f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b09181900360200190a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218612b3e61186b565b3060405160200180848152602001838152602001826001600160a01b03168152602001935050505060405160208183030381529060405280519060200120905090565b612b89612d3b565b806001600354031015612bcb576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001600160a01b03821615801590612bed57506001600160a01b038216600114155b612c26576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b03838116600090815260026020526040902054811690831614612c7f576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b038281166000818152600260209081526040808320805489871685528285208054919097166001600160a01b03199182161790965592849052825490941690915560038054600019019055825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a18060045414612d1557612d1581611b53565b505050565b604051806040016040528060058152602001640312e332e360dc1b81525081565b333014612d77576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b565b600082612d885750600061182d565b82820282848281612d9557fe5b0414612da057600080fd5b9392505050565b60419081029190910160208101516040820151919092015160ff1692565b600082820183811015612da057600080fd5b60006001836001811115612de757fe5b1415612e00576000808551602087018986f49050612e10565b600080855160208701888a87f190505b95945050505050565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c85490565b600081831015612e4e5781612da0565b5090919050565b600082821115612e6457600080fd5b50900390565b6000806001600160a01b03831615612e825782612e84565b325b90506001600160a01b038416612f1c57612eb63a8610612ea4573a612ea6565b855b612eb08989612dc5565b90612d79565b6040519092506001600160a01b0382169083156108fc029084906000818181858888f19350505050612f17576040805162461bcd60e51b8152602060048201526005602482015264475330313160d81b604482015290519081900360640190fd5b612f70565b612f2a85612eb08989612dc5565b9150612f378482846132d9565b612f70576040805162461bcd60e51b815260206004820152600560248201526423a998189960d91b604482015290519081900360640190fd5b5095945050505050565b60045415612fb7576040805162461bcd60e51b8152602060048201526005602482015264047533230360dc1b604482015290519081900360640190fd5b8151811115612ff5576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001811015613033576040805162461bcd60e51b815260206004820152600560248201526423a999181960d91b604482015290519081900360640190fd5b600160005b835181101561317e57600084828151811061304f57fe5b6020026020010151905060006001600160a01b0316816001600160a01b03161415801561308657506001600160a01b038116600114155b801561309b57506001600160a01b0381163014155b80156130b95750806001600160a01b0316836001600160a01b031614155b6130f2576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038181166000908152600260205260409020541615613147576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b6001600160a01b03928316600090815260026020526040902080546001600160a01b03191693821693909317909255600101613038565b506001600160a01b0316600090815260026020526040902080546001600160a01b03191660011790559051600355600455565b7f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d555565b600160008190526020527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f546001600160a01b031615613244576040805162461bcd60e51b8152602060048201526005602482015264047533130360dc1b604482015290519081900360640190fd5b6001600081905260208190527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03191690911790556001600160a01b03821615611296576132a08260008360015a612dd7565b611296576040805162461bcd60e51b8152602060048201526005602482015264047533030360dc1b604482015290519081900360640190fd5b604080516001600160a01b03841660248201526044808201849052825180830390910181526064909101909152602081810180516001600160e01b031663a9059cbb60e01b1781528251600093929184919082896127105a03f13d801561334b5760208114613353576000935061335e565b81935061335e565b600051158215171593505b505050939250505056fea2646970667358221220ff207ad9f6346a793f28b83147f4fb3ba78b1aca110c7cccf3b0a00f400b6d8764736f6c63430007060033"},"devdoc":{"author":"Stefan George - Richard Meissner - ","kind":"dev","methods":{"addOwnerWithThreshold(address,uint256)":{"details":"Allows to add a new owner to the Safe and update the threshold at the same time. This can only be done via a Safe transaction.","params":{"_threshold":"New threshold.","owner":"New owner address."}},"approveHash(bytes32)":{"details":"Marks a hash as approved. This can be used to validate a hash that is used by a signature.","params":{"hashToApprove":"The hash that should be marked as approved for signatures that are verified by this contract."}},"changeThreshold(uint256)":{"details":"Allows to update the number of required confirmations by Safe owners. This can only be done via a Safe transaction.","params":{"_threshold":"New threshold."}},"checkNSignatures(bytes32,bytes,bytes,uint256)":{"details":"Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.","params":{"data":"That should be signed (this is passed to an external validator contract)","dataHash":"Hash of the data (could be either a message hash or transaction hash)","requiredSignatures":"Amount of required valid signatures.","signatures":"Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash."}},"checkSignatures(bytes32,bytes,bytes)":{"details":"Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.","params":{"data":"That should be signed (this is passed to an external validator contract)","dataHash":"Hash of the data (could be either a message hash or transaction hash)","signatures":"Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash."}},"disableModule(address,address)":{"details":"Allows to remove a module from the whitelist. This can only be done via a Safe transaction.","params":{"module":"Module to be removed.","prevModule":"Module that pointed to the module to be removed in the linked list"}},"enableModule(address)":{"details":"Allows to add a module to the whitelist. This can only be done via a Safe transaction.","params":{"module":"Module to be whitelisted."}},"encodeTransactionData(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,uint256)":{"details":"Returns the bytes that are hashed to be signed by owners.","params":{"_nonce":"Transaction nonce.","baseGas":"Gas costs for that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)","data":"Data payload.","gasPrice":"Maximum gas price that should be used for this transaction.","gasToken":"Token address (or 0 if ETH) that is used for the payment.","operation":"Operation type.","refundReceiver":"Address of receiver of gas payment (or 0 if tx.origin).","safeTxGas":"Gas that should be used for the safe transaction.","to":"Destination address.","value":"Ether value."},"returns":{"_0":"Transaction hash bytes."}},"execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)":{"details":"Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction. Note: The fees are always transferred, even if the user transaction fails.","params":{"baseGas":"Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)","data":"Data payload of Safe transaction.","gasPrice":"Gas price that should be used for the payment calculation.","gasToken":"Token address (or 0 if ETH) that is used for the payment.","operation":"Operation type of Safe transaction.","refundReceiver":"Address of receiver of gas payment (or 0 if tx.origin).","safeTxGas":"Gas that should be used for the Safe transaction.","signatures":"Packed signature data ({bytes32 r}{bytes32 s}{uint8 v})","to":"Destination address of Safe transaction.","value":"Ether value of Safe transaction."}},"execTransactionFromModule(address,uint256,bytes,uint8)":{"details":"Allows a Module to execute a Safe transaction without any further confirmations.","params":{"data":"Data payload of module transaction.","operation":"Operation type of module transaction.","to":"Destination address of module transaction.","value":"Ether value of module transaction."}},"execTransactionFromModuleReturnData(address,uint256,bytes,uint8)":{"details":"Allows a Module to execute a Safe transaction without any further confirmations and return data","params":{"data":"Data payload of module transaction.","operation":"Operation type of module transaction.","to":"Destination address of module transaction.","value":"Ether value of module transaction."}},"getChainId()":{"details":"Returns the chain id used by this contract."},"getModulesPaginated(address,uint256)":{"details":"Returns array of modules.","params":{"pageSize":"Maximum number of modules that should be returned.","start":"Start of the page."},"returns":{"array":"Array of modules.","next":"Start of the next page."}},"getOwners()":{"details":"Returns array of owners.","returns":{"_0":"Array of Safe owners."}},"getStorageAt(uint256,uint256)":{"details":"Reads `length` bytes of storage in the currents contract","params":{"length":"- the number of words (32 bytes) of data to read","offset":"- the offset in the current contract's storage in words to start reading from"},"returns":{"_0":"the bytes that were read."}},"getTransactionHash(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,uint256)":{"details":"Returns hash to be signed by owners.","params":{"_nonce":"Transaction nonce.","baseGas":"Gas costs for data used to trigger the safe transaction.","data":"Data payload.","gasPrice":"Maximum gas price that should be used for this transaction.","gasToken":"Token address (or 0 if ETH) that is used for the payment.","operation":"Operation type.","refundReceiver":"Address of receiver of gas payment (or 0 if tx.origin).","safeTxGas":"Fas that should be used for the safe transaction.","to":"Destination address.","value":"Ether value."},"returns":{"_0":"Transaction hash."}},"isModuleEnabled(address)":{"details":"Returns if an module is enabled","returns":{"_0":"True if the module is enabled"}},"removeOwner(address,address,uint256)":{"details":"Allows to remove an owner from the Safe and update the threshold at the same time. This can only be done via a Safe transaction.","params":{"_threshold":"New threshold.","owner":"Owner address to be removed.","prevOwner":"Owner that pointed to the owner to be removed in the linked list"}},"requiredTxGas(address,uint256,bytes,uint8)":{"details":"Allows to estimate a Safe transaction. This method is only meant for estimation purpose, therefore the call will always revert and encode the result in the revert data. Since the `estimateGas` function includes refunds, call this method to get an estimated of the costs that are deducted from the safe with `execTransaction`","params":{"data":"Data payload of Safe transaction.","operation":"Operation type of Safe transaction.","to":"Destination address of Safe transaction.","value":"Ether value of Safe transaction."},"returns":{"_0":"Estimate without refunds and overhead fees (base transaction and payload data gas costs)."}},"setFallbackHandler(address)":{"details":"Allows to add a contract to handle fallback calls. Only fallback calls without value and with data will be forwarded. This can only be done via a Safe transaction.","params":{"handler":"contract to handle fallbacks calls."}},"setGuard(address)":{"details":"Set a guard that checks transactions before execution","params":{"guard":"The address of the guard to be used or the 0 address to disable the guard"}},"setup(address[],uint256,address,bytes,address,address,uint256,address)":{"details":"Setup function sets initial storage of contract.","params":{"_owners":"List of Safe owners.","_threshold":"Number of required confirmations for a Safe transaction.","data":"Data payload for optional delegate call.","fallbackHandler":"Handler for fallback calls to this contract","payment":"Value that should be paid","paymentReceiver":"Adddress that should receive the payment (or 0 if tx.origin)","paymentToken":"Token that should be used for the payment (0 is ETH)","to":"Contract address for optional delegate call."}},"simulateAndRevert(address,bytes)":{"details":"Performs a delegetecall on a targetContract in the context of self. Internally reverts execution to avoid side effects (making it static). This method reverts with data equal to `abi.encode(bool(success), bytes(response))`. Specifically, the `returndata` after a call to this method will be: `success:bool || response.length:uint256 || response:bytes`.","params":{"calldataPayload":"Calldata that should be sent to the target contract (encoded method name and arguments).","targetContract":"Address of the contract containing the code to execute."}},"swapOwner(address,address,address)":{"details":"Allows to swap/replace an owner from the Safe with another address. This can only be done via a Safe transaction.","params":{"newOwner":"New owner address.","oldOwner":"Owner address to be replaced.","prevOwner":"Owner that pointed to the owner to be replaced in the linked list"}}},"title":"Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191.","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50600160045561339e806100256000396000f3fe6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611078578063f698da25146110ab578063f8dc5dd9146110c0578063ffa1ad741461110357610219565b8063e19a9dd914610f26578063e318b52b14610f59578063e75235b814610f9e578063e86637db14610fb357610219565b8063cc2f8452116100d1578063cc2f845214610d5f578063d4d9bdcd14610dfc578063d8d11f7814610e26578063e009cfde14610eeb57610219565b8063affed0e014610af6578063b4faba0914610b0b578063b63e800d14610bcc578063c4ca3a9c14610ccc57610219565b80635624b25b1161017a5780636a761202116101495780636a761202146107dd5780637d8329741461091b578063934f3a1114610954578063a0e67e2b14610a9157610219565b80635624b25b146106b15780635ae6bd3714610756578063610b592514610780578063694e80c3146107b357610219565b80632f54bf6e116101b65780632f54bf6e146104405780633408e47014610473578063468721a71461049a5780635229073f1461056557610219565b80630d582f131461027f57806312fb68e0146102ba5780632d9ad53d146103f957610219565b366102195760408051348152905133917f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d919081900360200190a2005b34801561022557600080fd5b507f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d580548061025057005b36600080373360601b365260008060143601600080855af190503d6000803e80610279573d6000fd5b503d6000f35b34801561028b57600080fd5b506102b8600480360360408110156102a257600080fd5b506001600160a01b038135169060200135611118565b005b3480156102c657600080fd5b506102b8600480360360808110156102dd57600080fd5b81359190810190604081016020820135600160201b8111156102fe57600080fd5b82018360208201111561031057600080fd5b803590602001918460018302840111600160201b8311171561033157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561038357600080fd5b82018360208201111561039557600080fd5b803590602001918460018302840111600160201b831117156103b657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550509135925061129a915050565b34801561040557600080fd5b5061042c6004803603602081101561041c57600080fd5b50356001600160a01b03166117f8565b604080519115158252519081900360200190f35b34801561044c57600080fd5b5061042c6004803603602081101561046357600080fd5b50356001600160a01b0316611833565b34801561047f57600080fd5b5061048861186b565b60408051918252519081900360200190f35b3480156104a657600080fd5b5061042c600480360360808110156104bd57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b8111156104ec57600080fd5b8201836020820111156104fe57600080fd5b803590602001918460018302840111600160201b8311171561051f57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff16915061186f9050565b34801561057157600080fd5b506106306004803603608081101561058857600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b8111156105b757600080fd5b8201836020820111156105c957600080fd5b803590602001918460018302840111600160201b831117156105ea57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff16915061194b9050565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561067557818101518382015260200161065d565b50505050905090810190601f1680156106a25780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b3480156106bd57600080fd5b506106e1600480360360408110156106d457600080fd5b5080359060200135611981565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561071b578181015183820152602001610703565b50505050905090810190601f1680156107485780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561076257600080fd5b506104886004803603602081101561077957600080fd5b50356119f5565b34801561078c57600080fd5b506102b8600480360360208110156107a357600080fd5b50356001600160a01b0316611a07565b3480156107bf57600080fd5b506102b8600480360360208110156107d657600080fd5b5035611b53565b61042c60048036036101408110156107f457600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b81111561082357600080fd5b82018360208201111561083557600080fd5b803590602001918460018302840111600160201b8311171561085657600080fd5b9193909260ff833516926020810135926040820135926060830135926001600160a01b03608082013581169360a083013590911692909160e081019060c00135600160201b8111156108a757600080fd5b8201836020820111156108b957600080fd5b803590602001918460018302840111600160201b831117156108da57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611c13945050505050565b34801561092757600080fd5b506104886004803603604081101561093e57600080fd5b506001600160a01b038135169060200135612011565b34801561096057600080fd5b506102b86004803603606081101561097757600080fd5b81359190810190604081016020820135600160201b81111561099857600080fd5b8201836020820111156109aa57600080fd5b803590602001918460018302840111600160201b831117156109cb57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610a1d57600080fd5b820183602082011115610a2f57600080fd5b803590602001918460018302840111600160201b83111715610a5057600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061202e945050505050565b348015610a9d57600080fd5b50610aa661207d565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610ae2578181015183820152602001610aca565b505050509050019250505060405180910390f35b348015610b0257600080fd5b50610488612161565b348015610b1757600080fd5b506102b860048036036040811015610b2e57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610b5857600080fd5b820183602082011115610b6a57600080fd5b803590602001918460018302840111600160201b83111715610b8b57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612167945050505050565b348015610bd857600080fd5b506102b86004803603610100811015610bf057600080fd5b810190602081018135600160201b811115610c0a57600080fd5b820183602082011115610c1c57600080fd5b803590602001918460208302840111600160201b83111715610c3d57600080fd5b919390928235926001600160a01b03602082013516929190606081019060400135600160201b811115610c6f57600080fd5b820183602082011115610c8157600080fd5b803590602001918460018302840111600160201b83111715610ca257600080fd5b91935091506001600160a01b0381358116916020810135821691604082013591606001351661218a565b348015610cd857600080fd5b5061048860048036036080811015610cef57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b811115610d1e57600080fd5b820183602082011115610d3057600080fd5b803590602001918460018302840111600160201b83111715610d5157600080fd5b91935091503560ff166122da565b348015610d6b57600080fd5b50610d9860048036036040811015610d8257600080fd5b506001600160a01b0381351690602001356123d7565b6040518080602001836001600160a01b03168152602001828103825284818151815260200191508051906020019060200280838360005b83811015610de7578181015183820152602001610dcf565b50505050905001935050505060405180910390f35b348015610e0857600080fd5b506102b860048036036020811015610e1f57600080fd5b50356124c2565b348015610e3257600080fd5b506104886004803603610140811015610e4a57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b811115610e7957600080fd5b820183602082011115610e8b57600080fd5b803590602001918460018302840111600160201b83111715610eac57600080fd5b919350915060ff813516906020810135906040810135906060810135906001600160a01b03608082013581169160a08101359091169060c0013561255c565b348015610ef757600080fd5b506102b860048036036040811015610f0e57600080fd5b506001600160a01b0381358116916020013516612589565b348015610f3257600080fd5b506102b860048036036020811015610f4957600080fd5b50356001600160a01b03166126c3565b348015610f6557600080fd5b506102b860048036036060811015610f7c57600080fd5b506001600160a01b03813581169160208101358216916040909101351661272f565b348015610faa57600080fd5b5061048861297c565b348015610fbf57600080fd5b506106e16004803603610140811015610fd757600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b81111561100657600080fd5b82018360208201111561101857600080fd5b803590602001918460018302840111600160201b8311171561103957600080fd5b919350915060ff813516906020810135906040810135906060810135906001600160a01b03608082013581169160a08101359091169060c00135612982565b34801561108457600080fd5b506102b86004803603602081101561109b57600080fd5b50356001600160a01b0316612ac3565b3480156110b757600080fd5b50610488612b13565b3480156110cc57600080fd5b506102b8600480360360608110156110e357600080fd5b506001600160a01b03813581169160208101359091169060400135612b81565b34801561110f57600080fd5b506106e1612d1a565b611120612d3b565b6001600160a01b0382161580159061114257506001600160a01b038216600114155b801561115757506001600160a01b0382163014155b611190576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b0382811660009081526002602052604090205416156111e5576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b600260209081527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e080546001600160a01b03858116600081815260408082208054949095166001600160a01b031994851617909455600190819052845490921681179093556003805490910190558051918252517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea26929181900390910190a180600454146112965761129681611b53565b5050565b6112a5816041612d79565b825110156112e2576040805162461bcd60e51b8152602060048201526005602482015264047533032360dc1b604482015290519081900360640190fd5b6000808060008060005b868110156117ec576112fe8882612da7565b9195509350915060ff841661159f57919350839161131d876041612d79565b821015611359576040805162461bcd60e51b8152602060048201526005602482015264475330323160d81b604482015290519081900360640190fd5b8751611366836020612dc5565b11156113a1576040805162461bcd60e51b815260206004820152600560248201526423a998191960d91b604482015290519081900360640190fd5b6020828901810151895190916113c49083906113be908790612dc5565b90612dc5565b11156113ff576040805162461bcd60e51b8152602060048201526005602482015264475330323360d81b604482015290519081900360640190fd5b60606020848b010190506320c13b0b60e01b6001600160e01b031916876001600160a01b03166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561147c578181015183820152602001611464565b50505050905090810190601f1680156114a95780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b838110156114dc5781810151838201526020016114c4565b50505050905090810190601f1680156115095780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b15801561152857600080fd5b505afa15801561153c573d6000803e3d6000fd5b505050506040513d602081101561155257600080fd5b50516001600160e01b03191614611598576040805162461bcd60e51b815260206004820152600560248201526411d4cc0c8d60da1b604482015290519081900360640190fd5b5050611756565b8360ff1660011415611627579193508391336001600160a01b03841614806115e957506001600160a01b03851660009081526008602090815260408083208d845290915290205415155b611622576040805162461bcd60e51b8152602060048201526005602482015264475330323560d81b604482015290519081900360640190fd5b611756565b601e8460ff1611156116ef5760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156116de573d6000803e3d6000fd5b505050602060405103519450611756565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611749573d6000803e3d6000fd5b5050506020604051035194505b856001600160a01b0316856001600160a01b031611801561179057506001600160a01b038581166000908152600260205260409020541615155b80156117a657506001600160a01b038516600114155b6117df576040805162461bcd60e51b815260206004820152600560248201526423a998191b60d91b604482015290519081900360640190fd5b93945084936001016112ec565b50505050505050505050565b600060016001600160a01b0383161480159061182d57506001600160a01b038281166000908152600160205260409020541615155b92915050565b60006001600160a01b03821660011480159061182d5750506001600160a01b0390811660009081526002602052604090205416151590565b4690565b6000336001148015906118995750336000908152600160205260409020546001600160a01b031615155b6118d2576040805162461bcd60e51b815260206004820152600560248201526411d4cc4c0d60da1b604482015290519081900360640190fd5b6118df858585855a612dd7565b905080156119175760405133907f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb890600090a2611943565b60405133907facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37590600090a25b949350505050565b6000606061195b8686868661186f565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060008260200267ffffffffffffffff8111801561199f57600080fd5b506040519080825280601f01601f1916602001820160405280156119ca576020820181803683370190505b50905060005b838110156119ed57848101546020808302840101526001016119d0565b509392505050565b60076020526000908152604090205481565b611a0f612d3b565b6001600160a01b03811615801590611a3157506001600160a01b038116600114155b611a6a576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b038181166000908152600160205260409020541615611abf576040805162461bcd60e51b815260206004820152600560248201526423a998981960d91b604482015290519081900360640190fd5b600160208181527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03858116600081815260408082208054949095166001600160a01b0319948516179094559590955282541684179091558051928352517fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f84409281900390910190a150565b611b5b612d3b565b600354811115611b9a576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001811015611bd8576040805162461bcd60e51b815260206004820152600560248201526423a999181960d91b604482015290519081900360640190fd5b60048190556040805182815290517f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c939181900360200190a150565b6000806000611c2d8e8e8e8e8e8e8e8e8e8e600554612982565b6005805460010190558051602082012092509050611c4c82828661202e565b506000611c57612e19565b90506001600160a01b03811615611dd257806001600160a01b03166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d6001600160a01b031681526020018c8152602001806020018a6001811115611cbb57fe5b8152602001898152602001888152602001878152602001866001600160a01b03168152602001856001600160a01b0316815260200180602001846001600160a01b0316815260200183810383528d8d828181526020019250808284376000838201819052601f909101601f191690920185810384528751815287516020918201939189019250908190849084905b83811015611d61578181015183820152602001611d49565b50505050905090810190601f168015611d8e5780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015611db957600080fd5b505af1158015611dcd573d6000803e3d6000fd5b505050505b611de6603f60408b02046109c48b01612e3e565b6101f4015a1015611e26576040805162461bcd60e51b8152602060048201526005602482015264047533031360dc1b604482015290519081900360640190fd5b60005a9050611e8f8f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e8c600014611e84578e611e8a565b6109c45a035b612dd7565b9350611e9c5a8290612e55565b90508380611ea957508915155b80611eb357508715155b611eec576040805162461bcd60e51b8152602060048201526005602482015264475330313360d81b604482015290519081900360640190fd5b60008815611f0457611f01828b8b8b8b612e6a565b90505b8415611f4a57604080518581526020810183905281517f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e929181900390910190a1611f86565b604080518581526020810183905281517f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23929181900390910190a15b50506001600160a01b0381161561200057806001600160a01b0316639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b158015611fe757600080fd5b505af1158015611ffb573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b600860209081526000928352604080842090915290825290205481565b6004548061206b576040805162461bcd60e51b8152602060048201526005602482015264475330303160d81b604482015290519081900360640190fd5b6120778484848461129a565b50505050565b6060600060035467ffffffffffffffff8111801561209a57600080fd5b506040519080825280602002602001820160405280156120c4578160200160208202803683370190505b506001600090815260026020527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e054919250906001600160a01b03165b6001600160a01b038116600114612159578083838151811061211f57fe5b6001600160a01b03928316602091820292909201810191909152918116600090815260029092526040909120546001929092019116612101565b509091505090565b60055481565b600080825160208401855af480600052503d6020523d600060403e60403d016000fd5b6121c88a8a808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508c9250612f7a915050565b6001600160a01b038416156121e0576121e0846131b1565b6122208787878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506131d592505050565b81156122375761223582600060018685612e6a565b505b336001600160a01b03167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b896040518080602001858152602001846001600160a01b03168152602001836001600160a01b031681526020018281038252878782818152602001925060200280828437600083820152604051601f909101601f19169092018290039850909650505050505050a250505050505050505050565b6000805a9050612323878787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525089925050505a612dd7565b61232c57600080fd5b60005a82039050806040516020018082815260200191505060405160208183030381529060405260405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561239c578181015183820152602001612384565b50505050905090810190601f1680156123c95780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff811180156123f257600080fd5b5060405190808252806020026020018201604052801561241c578160200160208202803683370190505b506001600160a01b0380861660009081526001602052604081205492945091165b6001600160a01b0381161580159061245f57506001600160a01b038116600114155b801561246a57508482105b156124b4578084838151811061247c57fe5b6001600160a01b039283166020918202929092018101919091529181166000908152600192839052604090205492909101911661243d565b908352919491935090915050565b336000908152600260205260409020546001600160a01b0316612514576040805162461bcd60e51b8152602060048201526005602482015264047533033360dc1b604482015290519081900360640190fd5b336000818152600860209081526040808320858452909152808220600190555183917ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c91a350565b60006125718c8c8c8c8c8c8c8c8c8c8c612982565b8051906020012090509b9a5050505050505050505050565b612591612d3b565b6001600160a01b038116158015906125b357506001600160a01b038116600114155b6125ec576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b03828116600090815260016020526040902054811690821614612645576040805162461bcd60e51b8152602060048201526005602482015264475331303360d81b604482015290519081900360640190fd5b6001600160a01b038181166000818152600160209081526040808320805488871685528285208054919097166001600160a01b031991821617909655928490528254909416909155825191825291517faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace4054276929181900390910190a15050565b6126cb612d3b565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8818155604080516001600160a01b038416815290517f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa29181900360200190a15050565b612737612d3b565b6001600160a01b0381161580159061275957506001600160a01b038116600114155b801561276e57506001600160a01b0381163014155b6127a7576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b0381811660009081526002602052604090205416156127fc576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b6001600160a01b0382161580159061281e57506001600160a01b038216600114155b612857576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038381166000908152600260205260409020548116908316146128b0576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b038281166000818152600260209081526040808320805487871680865283862080549289166001600160a01b0319938416179055968a16855282852080548216909717909655928490528254909416909155825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a1604080516001600160a01b038316815290517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269181900360200190a1505050565b60045490565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b6001600160a01b031681526020018a8152602001898152602001886001811115612a0657fe5b8152602001878152602001868152602001858152602001846001600160a01b03168152602001836001600160a01b031681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b612a78612b13565b604080516001600160f81b0319948516602082015292909316602183015260228201526042808201939093528151808203909301835260620190529c9b505050505050505050505050565b612acb612d3b565b612ad4816131b1565b604080516001600160a01b038316815290517f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b09181900360200190a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218612b3e61186b565b3060405160200180848152602001838152602001826001600160a01b03168152602001935050505060405160208183030381529060405280519060200120905090565b612b89612d3b565b806001600354031015612bcb576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001600160a01b03821615801590612bed57506001600160a01b038216600114155b612c26576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b03838116600090815260026020526040902054811690831614612c7f576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b038281166000818152600260209081526040808320805489871685528285208054919097166001600160a01b03199182161790965592849052825490941690915560038054600019019055825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a18060045414612d1557612d1581611b53565b505050565b604051806040016040528060058152602001640312e332e360dc1b81525081565b333014612d77576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b565b600082612d885750600061182d565b82820282848281612d9557fe5b0414612da057600080fd5b9392505050565b60419081029190910160208101516040820151919092015160ff1692565b600082820183811015612da057600080fd5b60006001836001811115612de757fe5b1415612e00576000808551602087018986f49050612e10565b600080855160208701888a87f190505b95945050505050565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c85490565b600081831015612e4e5781612da0565b5090919050565b600082821115612e6457600080fd5b50900390565b6000806001600160a01b03831615612e825782612e84565b325b90506001600160a01b038416612f1c57612eb63a8610612ea4573a612ea6565b855b612eb08989612dc5565b90612d79565b6040519092506001600160a01b0382169083156108fc029084906000818181858888f19350505050612f17576040805162461bcd60e51b8152602060048201526005602482015264475330313160d81b604482015290519081900360640190fd5b612f70565b612f2a85612eb08989612dc5565b9150612f378482846132d9565b612f70576040805162461bcd60e51b815260206004820152600560248201526423a998189960d91b604482015290519081900360640190fd5b5095945050505050565b60045415612fb7576040805162461bcd60e51b8152602060048201526005602482015264047533230360dc1b604482015290519081900360640190fd5b8151811115612ff5576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001811015613033576040805162461bcd60e51b815260206004820152600560248201526423a999181960d91b604482015290519081900360640190fd5b600160005b835181101561317e57600084828151811061304f57fe5b6020026020010151905060006001600160a01b0316816001600160a01b03161415801561308657506001600160a01b038116600114155b801561309b57506001600160a01b0381163014155b80156130b95750806001600160a01b0316836001600160a01b031614155b6130f2576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038181166000908152600260205260409020541615613147576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b6001600160a01b03928316600090815260026020526040902080546001600160a01b03191693821693909317909255600101613038565b506001600160a01b0316600090815260026020526040902080546001600160a01b03191660011790559051600355600455565b7f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d555565b600160008190526020527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f546001600160a01b031615613244576040805162461bcd60e51b8152602060048201526005602482015264047533130360dc1b604482015290519081900360640190fd5b6001600081905260208190527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03191690911790556001600160a01b03821615611296576132a08260008360015a612dd7565b611296576040805162461bcd60e51b8152602060048201526005602482015264047533030360dc1b604482015290519081900360640190fd5b604080516001600160a01b03841660248201526044808201849052825180830390910181526064909101909152602081810180516001600160e01b031663a9059cbb60e01b1781528251600093929184919082896127105a03f13d801561334b5760208114613353576000935061335e565b81935061335e565b600051158215171593505b505050939250505056fea2646970667358221220ff207ad9f6346a793f28b83147f4fb3ba78b1aca110c7cccf3b0a00f400b6d8764736f6c63430007060033"},"sourceId":"GnosisSafe.sol","userdoc":{"kind":"user","methods":{"addOwnerWithThreshold(address,uint256)":{"notice":"Adds the owner `owner` to the Safe and updates the threshold to `_threshold`."},"changeThreshold(uint256)":{"notice":"Changes the threshold of the Safe to `_threshold`."},"disableModule(address,address)":{"notice":"Disables the module `module` for the Safe."},"enableModule(address)":{"notice":"Enables the module `module` for the Safe."},"removeOwner(address,address,uint256)":{"notice":"Removes the owner `owner` from the Safe and updates the threshold to `_threshold`."},"requiredTxGas(address,uint256,bytes,uint8)":{"notice":"Deprecated in favor of common/StorageAccessible.sol and will be removed in next version."},"swapOwner(address,address,address)":{"notice":"Replaces the owner `oldOwner` in the Safe with `newOwner`."}},"version":1}},"GnosisSafeL2":{"abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"AddedOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"approvedHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"owner","type":"address"}],"name":"ApproveHash","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"handler","type":"address"}],"name":"ChangedFallbackHandler","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"guard","type":"address"}],"name":"ChangedGuard","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"threshold","type":"uint256"}],"name":"ChangedThreshold","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"module","type":"address"}],"name":"DisabledModule","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"module","type":"address"}],"name":"EnabledModule","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"payment","type":"uint256"}],"name":"ExecutionFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"}],"name":"ExecutionFromModuleFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"}],"name":"ExecutionFromModuleSuccess","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"payment","type":"uint256"}],"name":"ExecutionSuccess","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"RemovedOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"module","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"SafeModuleTransaction","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseGas","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gasPrice","type":"uint256"},{"indexed":false,"internalType":"address","name":"gasToken","type":"address"},{"indexed":false,"internalType":"address payable","name":"refundReceiver","type":"address"},{"indexed":false,"internalType":"bytes","name":"signatures","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"additionalInfo","type":"bytes"}],"name":"SafeMultiSigTransaction","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SafeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"initiator","type":"address"},{"indexed":false,"internalType":"address[]","name":"owners","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"threshold","type":"uint256"},{"indexed":false,"internalType":"address","name":"initializer","type":"address"},{"indexed":false,"internalType":"address","name":"fallbackHandler","type":"address"}],"name":"SafeSetup","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"msgHash","type":"bytes32"}],"name":"SignMsg","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"addOwnerWithThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hashToApprove","type":"bytes32"}],"name":"approveHash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"approvedHashes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"changeThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"dataHash","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bytes","name":"signatures","type":"bytes"},{"internalType":"uint256","name":"requiredSignatures","type":"uint256"}],"name":"checkNSignatures","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"dataHash","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bytes","name":"signatures","type":"bytes"}],"name":"checkSignatures","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"prevModule","type":"address"},{"internalType":"address","name":"module","type":"address"}],"name":"disableModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"enableModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address","name":"refundReceiver","type":"address"},{"internalType":"uint256","name":"_nonce","type":"uint256"}],"name":"encodeTransactionData","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address payable","name":"refundReceiver","type":"address"},{"internalType":"bytes","name":"signatures","type":"bytes"}],"name":"execTransaction","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"execTransactionFromModule","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"execTransactionFromModuleReturnData","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"start","type":"address"},{"internalType":"uint256","name":"pageSize","type":"uint256"}],"name":"getModulesPaginated","outputs":[{"internalType":"address[]","name":"array","type":"address[]"},{"internalType":"address","name":"next","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOwners","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"offset","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"getStorageAt","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address","name":"refundReceiver","type":"address"},{"internalType":"uint256","name":"_nonce","type":"uint256"}],"name":"getTransactionHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"isModuleEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"prevOwner","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"removeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"requiredTxGas","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"handler","type":"address"}],"name":"setFallbackHandler","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"guard","type":"address"}],"name":"setGuard","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_owners","type":"address[]"},{"internalType":"uint256","name":"_threshold","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"address","name":"fallbackHandler","type":"address"},{"internalType":"address","name":"paymentToken","type":"address"},{"internalType":"uint256","name":"payment","type":"uint256"},{"internalType":"address payable","name":"paymentReceiver","type":"address"}],"name":"setup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"signedMessages","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"targetContract","type":"address"},{"internalType":"bytes","name":"calldataPayload","type":"bytes"}],"name":"simulateAndRevert","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"prevOwner","type":"address"},{"internalType":"address","name":"oldOwner","type":"address"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"swapOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"contractName":"GnosisSafeL2","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506001600455613688806100256000396000f3fe6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611078578063f698da25146110ab578063f8dc5dd9146110c0578063ffa1ad741461110357610219565b8063e19a9dd914610f26578063e318b52b14610f59578063e75235b814610f9e578063e86637db14610fb357610219565b8063cc2f8452116100d1578063cc2f845214610d5f578063d4d9bdcd14610dfc578063d8d11f7814610e26578063e009cfde14610eeb57610219565b8063affed0e014610af6578063b4faba0914610b0b578063b63e800d14610bcc578063c4ca3a9c14610ccc57610219565b80635624b25b1161017a5780636a761202116101495780636a761202146107dd5780637d8329741461091b578063934f3a1114610954578063a0e67e2b14610a9157610219565b80635624b25b146106b15780635ae6bd3714610756578063610b592514610780578063694e80c3146107b357610219565b80632f54bf6e116101b65780632f54bf6e146104405780633408e47014610473578063468721a71461049a5780635229073f1461056557610219565b80630d582f131461027f57806312fb68e0146102ba5780632d9ad53d146103f957610219565b366102195760408051348152905133917f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d919081900360200190a2005b34801561022557600080fd5b507f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d580548061025057005b36600080373360601b365260008060143601600080855af190503d6000803e80610279573d6000fd5b503d6000f35b34801561028b57600080fd5b506102b8600480360360408110156102a257600080fd5b506001600160a01b038135169060200135611118565b005b3480156102c657600080fd5b506102b8600480360360808110156102dd57600080fd5b81359190810190604081016020820135600160201b8111156102fe57600080fd5b82018360208201111561031057600080fd5b803590602001918460018302840111600160201b8311171561033157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561038357600080fd5b82018360208201111561039557600080fd5b803590602001918460018302840111600160201b831117156103b657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550509135925061129a915050565b34801561040557600080fd5b5061042c6004803603602081101561041c57600080fd5b50356001600160a01b03166117f8565b604080519115158252519081900360200190f35b34801561044c57600080fd5b5061042c6004803603602081101561046357600080fd5b50356001600160a01b0316611833565b34801561047f57600080fd5b5061048861186b565b60408051918252519081900360200190f35b3480156104a657600080fd5b5061042c600480360360808110156104bd57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b8111156104ec57600080fd5b8201836020820111156104fe57600080fd5b803590602001918460018302840111600160201b8311171561051f57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff16915061186f9050565b34801561057157600080fd5b506106306004803603608081101561058857600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b8111156105b757600080fd5b8201836020820111156105c957600080fd5b803590602001918460018302840111600160201b831117156105ea57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff16915061195c9050565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561067557818101518382015260200161065d565b50505050905090810190601f1680156106a25780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b3480156106bd57600080fd5b506106e1600480360360408110156106d457600080fd5b5080359060200135611992565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561071b578181015183820152602001610703565b50505050905090810190601f1680156107485780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561076257600080fd5b506104886004803603602081101561077957600080fd5b5035611a06565b34801561078c57600080fd5b506102b8600480360360208110156107a357600080fd5b50356001600160a01b0316611a18565b3480156107bf57600080fd5b506102b8600480360360208110156107d657600080fd5b5035611b64565b61042c60048036036101408110156107f457600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b81111561082357600080fd5b82018360208201111561083557600080fd5b803590602001918460018302840111600160201b8311171561085657600080fd5b9193909260ff833516926020810135926040820135926060830135926001600160a01b03608082013581169360a083013590911692909160e081019060c00135600160201b8111156108a757600080fd5b8201836020820111156108b957600080fd5b803590602001918460018302840111600160201b831117156108da57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611c24945050505050565b34801561092757600080fd5b506104886004803603604081101561093e57600080fd5b506001600160a01b038135169060200135611e23565b34801561096057600080fd5b506102b86004803603606081101561097757600080fd5b81359190810190604081016020820135600160201b81111561099857600080fd5b8201836020820111156109aa57600080fd5b803590602001918460018302840111600160201b831117156109cb57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610a1d57600080fd5b820183602082011115610a2f57600080fd5b803590602001918460018302840111600160201b83111715610a5057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611e40945050505050565b348015610a9d57600080fd5b50610aa6611e8f565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610ae2578181015183820152602001610aca565b505050509050019250505060405180910390f35b348015610b0257600080fd5b50610488611f73565b348015610b1757600080fd5b506102b860048036036040811015610b2e57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610b5857600080fd5b820183602082011115610b6a57600080fd5b803590602001918460018302840111600160201b83111715610b8b57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611f79945050505050565b348015610bd857600080fd5b506102b86004803603610100811015610bf057600080fd5b810190602081018135600160201b811115610c0a57600080fd5b820183602082011115610c1c57600080fd5b803590602001918460208302840111600160201b83111715610c3d57600080fd5b919390928235926001600160a01b03602082013516929190606081019060400135600160201b811115610c6f57600080fd5b820183602082011115610c8157600080fd5b803590602001918460018302840111600160201b83111715610ca257600080fd5b91935091506001600160a01b03813581169160208101358216916040820135916060013516611f9c565b348015610cd857600080fd5b5061048860048036036080811015610cef57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b811115610d1e57600080fd5b820183602082011115610d3057600080fd5b803590602001918460018302840111600160201b83111715610d5157600080fd5b91935091503560ff166120ec565b348015610d6b57600080fd5b50610d9860048036036040811015610d8257600080fd5b506001600160a01b0381351690602001356121e9565b6040518080602001836001600160a01b03168152602001828103825284818151815260200191508051906020019060200280838360005b83811015610de7578181015183820152602001610dcf565b50505050905001935050505060405180910390f35b348015610e0857600080fd5b506102b860048036036020811015610e1f57600080fd5b50356122d4565b348015610e3257600080fd5b506104886004803603610140811015610e4a57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b811115610e7957600080fd5b820183602082011115610e8b57600080fd5b803590602001918460018302840111600160201b83111715610eac57600080fd5b919350915060ff813516906020810135906040810135906060810135906001600160a01b03608082013581169160a08101359091169060c0013561236e565b348015610ef757600080fd5b506102b860048036036040811015610f0e57600080fd5b506001600160a01b038135811691602001351661239b565b348015610f3257600080fd5b506102b860048036036020811015610f4957600080fd5b50356001600160a01b03166124d5565b348015610f6557600080fd5b506102b860048036036060811015610f7c57600080fd5b506001600160a01b038135811691602081013582169160409091013516612541565b348015610faa57600080fd5b5061048861278e565b348015610fbf57600080fd5b506106e16004803603610140811015610fd757600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b81111561100657600080fd5b82018360208201111561101857600080fd5b803590602001918460018302840111600160201b8311171561103957600080fd5b919350915060ff813516906020810135906040810135906060810135906001600160a01b03608082013581169160a08101359091169060c00135612794565b34801561108457600080fd5b506102b86004803603602081101561109b57600080fd5b50356001600160a01b03166128d5565b3480156110b757600080fd5b50610488612925565b3480156110cc57600080fd5b506102b8600480360360608110156110e357600080fd5b506001600160a01b03813581169160208101359091169060400135612993565b34801561110f57600080fd5b506106e1612b2c565b611120612b4d565b6001600160a01b0382161580159061114257506001600160a01b038216600114155b801561115757506001600160a01b0382163014155b611190576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b0382811660009081526002602052604090205416156111e5576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b600260209081527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e080546001600160a01b03858116600081815260408082208054949095166001600160a01b031994851617909455600190819052845490921681179093556003805490910190558051918252517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea26929181900390910190a180600454146112965761129681611b64565b5050565b6112a5816041612b8b565b825110156112e2576040805162461bcd60e51b8152602060048201526005602482015264047533032360dc1b604482015290519081900360640190fd5b6000808060008060005b868110156117ec576112fe8882612bb9565b9195509350915060ff841661159f57919350839161131d876041612b8b565b821015611359576040805162461bcd60e51b8152602060048201526005602482015264475330323160d81b604482015290519081900360640190fd5b8751611366836020612bd7565b11156113a1576040805162461bcd60e51b815260206004820152600560248201526423a998191960d91b604482015290519081900360640190fd5b6020828901810151895190916113c49083906113be908790612bd7565b90612bd7565b11156113ff576040805162461bcd60e51b8152602060048201526005602482015264475330323360d81b604482015290519081900360640190fd5b60606020848b010190506320c13b0b60e01b6001600160e01b031916876001600160a01b03166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561147c578181015183820152602001611464565b50505050905090810190601f1680156114a95780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b838110156114dc5781810151838201526020016114c4565b50505050905090810190601f1680156115095780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b15801561152857600080fd5b505afa15801561153c573d6000803e3d6000fd5b505050506040513d602081101561155257600080fd5b50516001600160e01b03191614611598576040805162461bcd60e51b815260206004820152600560248201526411d4cc0c8d60da1b604482015290519081900360640190fd5b5050611756565b8360ff1660011415611627579193508391336001600160a01b03841614806115e957506001600160a01b03851660009081526008602090815260408083208d845290915290205415155b611622576040805162461bcd60e51b8152602060048201526005602482015264475330323560d81b604482015290519081900360640190fd5b611756565b601e8460ff1611156116ef5760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156116de573d6000803e3d6000fd5b505050602060405103519450611756565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611749573d6000803e3d6000fd5b5050506020604051035194505b856001600160a01b0316856001600160a01b031611801561179057506001600160a01b038581166000908152600260205260409020541615155b80156117a657506001600160a01b038516600114155b6117df576040805162461bcd60e51b815260206004820152600560248201526423a998191b60d91b604482015290519081900360640190fd5b93945084936001016112ec565b50505050505050505050565b600060016001600160a01b0383161480159061182d57506001600160a01b038281166000908152600160205260409020541615155b92915050565b60006001600160a01b03821660011480159061182d5750506001600160a01b0390811660009081526002602052604090205416151590565b4690565b60007fb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e338686868660405180866001600160a01b03168152602001856001600160a01b03168152602001848152602001806020018360018111156118cf57fe5b8152602001828103825284818151815260200191508051906020019080838360005b838110156119095781810151838201526020016118f1565b50505050905090810190601f1680156119365780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a161195385858585612be9565b95945050505050565b6000606061196c8686868661186f565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060008260200267ffffffffffffffff811180156119b057600080fd5b506040519080825280601f01601f1916602001820160405280156119db576020820181803683370190505b50905060005b838110156119fe57848101546020808302840101526001016119e1565b509392505050565b60076020526000908152604090205481565b611a20612b4d565b6001600160a01b03811615801590611a4257506001600160a01b038116600114155b611a7b576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b038181166000908152600160205260409020541615611ad0576040805162461bcd60e51b815260206004820152600560248201526423a998981960d91b604482015290519081900360640190fd5b600160208181527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03858116600081815260408082208054949095166001600160a01b0319948516179094559590955282541684179091558051928352517fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f84409281900390910190a150565b611b6c612b4d565b600354811115611bab576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001811015611be9576040805162461bcd60e51b815260206004820152600560248201526423a999181960d91b604482015290519081900360640190fd5b60048190556040805182815290517f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c939181900360200190a150565b600060606005543360045460405160200180848152602001836001600160a01b03168152602001828152602001935050505060405160208183030381529060405290507f66753cd2356569ee081232e3be8909b950e0a76c1f8460c3a5e3c2be32b11bed8d8d8d8d8d8d8d8d8d8d8d8c604051808d6001600160a01b031681526020018c8152602001806020018a6001811115611cbd57fe5b8152602001898152602001888152602001878152602001866001600160a01b03168152602001856001600160a01b03168152602001806020018060200184810384528e8e828181526020019250808284376000838201819052601f909101601f19169092018681038552885181528851602091820193918a019250908190849084905b83811015611d58578181015183820152602001611d40565b50505050905090810190601f168015611d855780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b83811015611db8578181015183820152602001611da0565b50505050905090810190601f168015611de55780820380516001836020036101000a031916815260200191505b509f5050505050505050505050505050505060405180910390a1611e128d8d8d8d8d8d8d8d8d8d8d612cc5565b9d9c50505050505050505050505050565b600860209081526000928352604080842090915290825290205481565b60045480611e7d576040805162461bcd60e51b8152602060048201526005602482015264475330303160d81b604482015290519081900360640190fd5b611e898484848461129a565b50505050565b6060600060035467ffffffffffffffff81118015611eac57600080fd5b50604051908082528060200260200182016040528015611ed6578160200160208202803683370190505b506001600090815260026020527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e054919250906001600160a01b03165b6001600160a01b038116600114611f6b5780838381518110611f3157fe5b6001600160a01b03928316602091820292909201810191909152918116600090815260029092526040909120546001929092019116611f13565b509091505090565b60055481565b600080825160208401855af480600052503d6020523d600060403e60403d016000fd5b611fda8a8a808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508c92506130c3915050565b6001600160a01b03841615611ff257611ff2846132fa565b6120328787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061331e92505050565b81156120495761204782600060018685613422565b505b336001600160a01b03167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b896040518080602001858152602001846001600160a01b03168152602001836001600160a01b031681526020018281038252878782818152602001925060200280828437600083820152604051601f909101601f19169092018290039850909650505050505050a250505050505050505050565b6000805a9050612135878787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525089925050505a613532565b61213e57600080fd5b60005a82039050806040516020018082815260200191505060405160208183030381529060405260405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156121ae578181015183820152602001612196565b50505050905090810190601f1680156121db5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff8111801561220457600080fd5b5060405190808252806020026020018201604052801561222e578160200160208202803683370190505b506001600160a01b0380861660009081526001602052604081205492945091165b6001600160a01b0381161580159061227157506001600160a01b038116600114155b801561227c57508482105b156122c6578084838151811061228e57fe5b6001600160a01b039283166020918202929092018101919091529181166000908152600192839052604090205492909101911661224f565b908352919491935090915050565b336000908152600260205260409020546001600160a01b0316612326576040805162461bcd60e51b8152602060048201526005602482015264047533033360dc1b604482015290519081900360640190fd5b336000818152600860209081526040808320858452909152808220600190555183917ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c91a350565b60006123838c8c8c8c8c8c8c8c8c8c8c612794565b8051906020012090509b9a5050505050505050505050565b6123a3612b4d565b6001600160a01b038116158015906123c557506001600160a01b038116600114155b6123fe576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b03828116600090815260016020526040902054811690821614612457576040805162461bcd60e51b8152602060048201526005602482015264475331303360d81b604482015290519081900360640190fd5b6001600160a01b038181166000818152600160209081526040808320805488871685528285208054919097166001600160a01b031991821617909655928490528254909416909155825191825291517faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace4054276929181900390910190a15050565b6124dd612b4d565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8818155604080516001600160a01b038416815290517f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa29181900360200190a15050565b612549612b4d565b6001600160a01b0381161580159061256b57506001600160a01b038116600114155b801561258057506001600160a01b0381163014155b6125b9576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b03818116600090815260026020526040902054161561260e576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b6001600160a01b0382161580159061263057506001600160a01b038216600114155b612669576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038381166000908152600260205260409020548116908316146126c2576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b038281166000818152600260209081526040808320805487871680865283862080549289166001600160a01b0319938416179055968a16855282852080548216909717909655928490528254909416909155825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a1604080516001600160a01b038316815290517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269181900360200190a1505050565b60045490565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b6001600160a01b031681526020018a815260200189815260200188600181111561281857fe5b8152602001878152602001868152602001858152602001846001600160a01b03168152602001836001600160a01b031681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b61288a612925565b604080516001600160f81b0319948516602082015292909316602183015260228201526042808201939093528151808203909301835260620190529c9b505050505050505050505050565b6128dd612b4d565b6128e6816132fa565b604080516001600160a01b038316815290517f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b09181900360200190a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921861295061186b565b3060405160200180848152602001838152602001826001600160a01b03168152602001935050505060405160208183030381529060405280519060200120905090565b61299b612b4d565b8060016003540310156129dd576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001600160a01b038216158015906129ff57506001600160a01b038216600114155b612a38576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b03838116600090815260026020526040902054811690831614612a91576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b038281166000818152600260209081526040808320805489871685528285208054919097166001600160a01b03199182161790965592849052825490941690915560038054600019019055825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a18060045414612b2757612b2781611b64565b505050565b604051806040016040528060058152602001640312e332e360dc1b81525081565b333014612b89576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b565b600082612b9a5750600061182d565b82820282848281612ba757fe5b0414612bb257600080fd5b9392505050565b60419081029190910160208101516040820151919092015160ff1692565b600082820183811015612bb257600080fd5b600033600114801590612c135750336000908152600160205260409020546001600160a01b031615155b612c4c576040805162461bcd60e51b815260206004820152600560248201526411d4cc4c0d60da1b604482015290519081900360640190fd5b612c59858585855a613532565b90508015612c915760405133907f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb890600090a2612cbd565b60405133907facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37590600090a25b949350505050565b6000806000612cdf8e8e8e8e8e8e8e8e8e8e600554612794565b6005805460010190558051602082012092509050612cfe828286611e40565b506000612d09613572565b90506001600160a01b03811615612e8457806001600160a01b03166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d6001600160a01b031681526020018c8152602001806020018a6001811115612d6d57fe5b8152602001898152602001888152602001878152602001866001600160a01b03168152602001856001600160a01b0316815260200180602001846001600160a01b0316815260200183810383528d8d828181526020019250808284376000838201819052601f909101601f191690920185810384528751815287516020918201939189019250908190849084905b83811015612e13578181015183820152602001612dfb565b50505050905090810190601f168015612e405780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015612e6b57600080fd5b505af1158015612e7f573d6000803e3d6000fd5b505050505b612e98603f60408b02046109c48b01613597565b6101f4015a1015612ed8576040805162461bcd60e51b8152602060048201526005602482015264047533031360dc1b604482015290519081900360640190fd5b60005a9050612f418f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e8c600014612f36578e612f3c565b6109c45a035b613532565b9350612f4e5a82906135ae565b90508380612f5b57508915155b80612f6557508715155b612f9e576040805162461bcd60e51b8152602060048201526005602482015264475330313360d81b604482015290519081900360640190fd5b60008815612fb657612fb3828b8b8b8b613422565b90505b8415612ffc57604080518581526020810183905281517f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e929181900390910190a1613038565b604080518581526020810183905281517f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23929181900390910190a15b50506001600160a01b038116156130b257806001600160a01b0316639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b15801561309957600080fd5b505af11580156130ad573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b60045415613100576040805162461bcd60e51b8152602060048201526005602482015264047533230360dc1b604482015290519081900360640190fd5b815181111561313e576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b600181101561317c576040805162461bcd60e51b815260206004820152600560248201526423a999181960d91b604482015290519081900360640190fd5b600160005b83518110156132c757600084828151811061319857fe5b6020026020010151905060006001600160a01b0316816001600160a01b0316141580156131cf57506001600160a01b038116600114155b80156131e457506001600160a01b0381163014155b80156132025750806001600160a01b0316836001600160a01b031614155b61323b576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038181166000908152600260205260409020541615613290576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b6001600160a01b03928316600090815260026020526040902080546001600160a01b03191693821693909317909255600101613181565b506001600160a01b0316600090815260026020526040902080546001600160a01b03191660011790559051600355600455565b7f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d555565b600160008190526020527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f546001600160a01b03161561338d576040805162461bcd60e51b8152602060048201526005602482015264047533130360dc1b604482015290519081900360640190fd5b6001600081905260208190527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03191690911790556001600160a01b03821615611296576133e98260008360015a613532565b611296576040805162461bcd60e51b8152602060048201526005602482015264047533030360dc1b604482015290519081900360640190fd5b6000806001600160a01b0383161561343a578261343c565b325b90506001600160a01b0384166134d45761346e3a861061345c573a61345e565b855b6134688989612bd7565b90612b8b565b6040519092506001600160a01b0382169083156108fc029084906000818181858888f193505050506134cf576040805162461bcd60e51b8152602060048201526005602482015264475330313160d81b604482015290519081900360640190fd5b613528565b6134e2856134688989612bd7565b91506134ef8482846135c3565b613528576040805162461bcd60e51b815260206004820152600560248201526423a998189960d91b604482015290519081900360640190fd5b5095945050505050565b6000600183600181111561354257fe5b141561355b576000808551602087018986f49050611953565b600080855160208701888a87f19695505050505050565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c85490565b6000818310156135a75781612bb2565b5090919050565b6000828211156135bd57600080fd5b50900390565b604080516001600160a01b03841660248201526044808201849052825180830390910181526064909101909152602081810180516001600160e01b031663a9059cbb60e01b1781528251600093929184919082896127105a03f13d8015613635576020811461363d5760009350613648565b819350613648565b600051158215171593505b505050939250505056fea2646970667358221220165c247ee6e1f63a4c02dfa873388abce9ff5e17198768d86c53a89f459fa54064736f6c63430007060033"},"devdoc":{"author":"Stefan George - Richard Meissner - ","kind":"dev","methods":{"addOwnerWithThreshold(address,uint256)":{"details":"Allows to add a new owner to the Safe and update the threshold at the same time. This can only be done via a Safe transaction.","params":{"_threshold":"New threshold.","owner":"New owner address."}},"approveHash(bytes32)":{"details":"Marks a hash as approved. This can be used to validate a hash that is used by a signature.","params":{"hashToApprove":"The hash that should be marked as approved for signatures that are verified by this contract."}},"changeThreshold(uint256)":{"details":"Allows to update the number of required confirmations by Safe owners. This can only be done via a Safe transaction.","params":{"_threshold":"New threshold."}},"checkNSignatures(bytes32,bytes,bytes,uint256)":{"details":"Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.","params":{"data":"That should be signed (this is passed to an external validator contract)","dataHash":"Hash of the data (could be either a message hash or transaction hash)","requiredSignatures":"Amount of required valid signatures.","signatures":"Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash."}},"checkSignatures(bytes32,bytes,bytes)":{"details":"Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.","params":{"data":"That should be signed (this is passed to an external validator contract)","dataHash":"Hash of the data (could be either a message hash or transaction hash)","signatures":"Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash."}},"disableModule(address,address)":{"details":"Allows to remove a module from the whitelist. This can only be done via a Safe transaction.","params":{"module":"Module to be removed.","prevModule":"Module that pointed to the module to be removed in the linked list"}},"enableModule(address)":{"details":"Allows to add a module to the whitelist. This can only be done via a Safe transaction.","params":{"module":"Module to be whitelisted."}},"encodeTransactionData(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,uint256)":{"details":"Returns the bytes that are hashed to be signed by owners.","params":{"_nonce":"Transaction nonce.","baseGas":"Gas costs for that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)","data":"Data payload.","gasPrice":"Maximum gas price that should be used for this transaction.","gasToken":"Token address (or 0 if ETH) that is used for the payment.","operation":"Operation type.","refundReceiver":"Address of receiver of gas payment (or 0 if tx.origin).","safeTxGas":"Gas that should be used for the safe transaction.","to":"Destination address.","value":"Ether value."},"returns":{"_0":"Transaction hash bytes."}},"execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)":{"details":"Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction. Note: The fees are always transferred, even if the user transaction fails.","params":{"baseGas":"Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)","data":"Data payload of Safe transaction.","gasPrice":"Gas price that should be used for the payment calculation.","gasToken":"Token address (or 0 if ETH) that is used for the payment.","operation":"Operation type of Safe transaction.","refundReceiver":"Address of receiver of gas payment (or 0 if tx.origin).","safeTxGas":"Gas that should be used for the Safe transaction.","signatures":"Packed signature data ({bytes32 r}{bytes32 s}{uint8 v})","to":"Destination address of Safe transaction.","value":"Ether value of Safe transaction."}},"execTransactionFromModule(address,uint256,bytes,uint8)":{"details":"Allows a Module to execute a Safe transaction without any further confirmations.","params":{"data":"Data payload of module transaction.","operation":"Operation type of module transaction.","to":"Destination address of module transaction.","value":"Ether value of module transaction."}},"execTransactionFromModuleReturnData(address,uint256,bytes,uint8)":{"details":"Allows a Module to execute a Safe transaction without any further confirmations and return data","params":{"data":"Data payload of module transaction.","operation":"Operation type of module transaction.","to":"Destination address of module transaction.","value":"Ether value of module transaction."}},"getChainId()":{"details":"Returns the chain id used by this contract."},"getModulesPaginated(address,uint256)":{"details":"Returns array of modules.","params":{"pageSize":"Maximum number of modules that should be returned.","start":"Start of the page."},"returns":{"array":"Array of modules.","next":"Start of the next page."}},"getOwners()":{"details":"Returns array of owners.","returns":{"_0":"Array of Safe owners."}},"getStorageAt(uint256,uint256)":{"details":"Reads `length` bytes of storage in the currents contract","params":{"length":"- the number of words (32 bytes) of data to read","offset":"- the offset in the current contract's storage in words to start reading from"},"returns":{"_0":"the bytes that were read."}},"getTransactionHash(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,uint256)":{"details":"Returns hash to be signed by owners.","params":{"_nonce":"Transaction nonce.","baseGas":"Gas costs for data used to trigger the safe transaction.","data":"Data payload.","gasPrice":"Maximum gas price that should be used for this transaction.","gasToken":"Token address (or 0 if ETH) that is used for the payment.","operation":"Operation type.","refundReceiver":"Address of receiver of gas payment (or 0 if tx.origin).","safeTxGas":"Fas that should be used for the safe transaction.","to":"Destination address.","value":"Ether value."},"returns":{"_0":"Transaction hash."}},"isModuleEnabled(address)":{"details":"Returns if an module is enabled","returns":{"_0":"True if the module is enabled"}},"removeOwner(address,address,uint256)":{"details":"Allows to remove an owner from the Safe and update the threshold at the same time. This can only be done via a Safe transaction.","params":{"_threshold":"New threshold.","owner":"Owner address to be removed.","prevOwner":"Owner that pointed to the owner to be removed in the linked list"}},"requiredTxGas(address,uint256,bytes,uint8)":{"details":"Allows to estimate a Safe transaction. This method is only meant for estimation purpose, therefore the call will always revert and encode the result in the revert data. Since the `estimateGas` function includes refunds, call this method to get an estimated of the costs that are deducted from the safe with `execTransaction`","params":{"data":"Data payload of Safe transaction.","operation":"Operation type of Safe transaction.","to":"Destination address of Safe transaction.","value":"Ether value of Safe transaction."},"returns":{"_0":"Estimate without refunds and overhead fees (base transaction and payload data gas costs)."}},"setFallbackHandler(address)":{"details":"Allows to add a contract to handle fallback calls. Only fallback calls without value and with data will be forwarded. This can only be done via a Safe transaction.","params":{"handler":"contract to handle fallbacks calls."}},"setGuard(address)":{"details":"Set a guard that checks transactions before execution","params":{"guard":"The address of the guard to be used or the 0 address to disable the guard"}},"setup(address[],uint256,address,bytes,address,address,uint256,address)":{"details":"Setup function sets initial storage of contract.","params":{"_owners":"List of Safe owners.","_threshold":"Number of required confirmations for a Safe transaction.","data":"Data payload for optional delegate call.","fallbackHandler":"Handler for fallback calls to this contract","payment":"Value that should be paid","paymentReceiver":"Adddress that should receive the payment (or 0 if tx.origin)","paymentToken":"Token that should be used for the payment (0 is ETH)","to":"Contract address for optional delegate call."}},"simulateAndRevert(address,bytes)":{"details":"Performs a delegetecall on a targetContract in the context of self. Internally reverts execution to avoid side effects (making it static). This method reverts with data equal to `abi.encode(bool(success), bytes(response))`. Specifically, the `returndata` after a call to this method will be: `success:bool || response.length:uint256 || response:bytes`.","params":{"calldataPayload":"Calldata that should be sent to the target contract (encoded method name and arguments).","targetContract":"Address of the contract containing the code to execute."}},"swapOwner(address,address,address)":{"details":"Allows to swap/replace an owner from the Safe with another address. This can only be done via a Safe transaction.","params":{"newOwner":"New owner address.","oldOwner":"Owner address to be replaced.","prevOwner":"Owner that pointed to the owner to be replaced in the linked list"}}},"title":"Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191.","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506001600455613688806100256000396000f3fe6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611078578063f698da25146110ab578063f8dc5dd9146110c0578063ffa1ad741461110357610219565b8063e19a9dd914610f26578063e318b52b14610f59578063e75235b814610f9e578063e86637db14610fb357610219565b8063cc2f8452116100d1578063cc2f845214610d5f578063d4d9bdcd14610dfc578063d8d11f7814610e26578063e009cfde14610eeb57610219565b8063affed0e014610af6578063b4faba0914610b0b578063b63e800d14610bcc578063c4ca3a9c14610ccc57610219565b80635624b25b1161017a5780636a761202116101495780636a761202146107dd5780637d8329741461091b578063934f3a1114610954578063a0e67e2b14610a9157610219565b80635624b25b146106b15780635ae6bd3714610756578063610b592514610780578063694e80c3146107b357610219565b80632f54bf6e116101b65780632f54bf6e146104405780633408e47014610473578063468721a71461049a5780635229073f1461056557610219565b80630d582f131461027f57806312fb68e0146102ba5780632d9ad53d146103f957610219565b366102195760408051348152905133917f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d919081900360200190a2005b34801561022557600080fd5b507f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d580548061025057005b36600080373360601b365260008060143601600080855af190503d6000803e80610279573d6000fd5b503d6000f35b34801561028b57600080fd5b506102b8600480360360408110156102a257600080fd5b506001600160a01b038135169060200135611118565b005b3480156102c657600080fd5b506102b8600480360360808110156102dd57600080fd5b81359190810190604081016020820135600160201b8111156102fe57600080fd5b82018360208201111561031057600080fd5b803590602001918460018302840111600160201b8311171561033157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b81111561038357600080fd5b82018360208201111561039557600080fd5b803590602001918460018302840111600160201b831117156103b657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550509135925061129a915050565b34801561040557600080fd5b5061042c6004803603602081101561041c57600080fd5b50356001600160a01b03166117f8565b604080519115158252519081900360200190f35b34801561044c57600080fd5b5061042c6004803603602081101561046357600080fd5b50356001600160a01b0316611833565b34801561047f57600080fd5b5061048861186b565b60408051918252519081900360200190f35b3480156104a657600080fd5b5061042c600480360360808110156104bd57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b8111156104ec57600080fd5b8201836020820111156104fe57600080fd5b803590602001918460018302840111600160201b8311171561051f57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff16915061186f9050565b34801561057157600080fd5b506106306004803603608081101561058857600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b8111156105b757600080fd5b8201836020820111156105c957600080fd5b803590602001918460018302840111600160201b831117156105ea57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff16915061195c9050565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561067557818101518382015260200161065d565b50505050905090810190601f1680156106a25780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b3480156106bd57600080fd5b506106e1600480360360408110156106d457600080fd5b5080359060200135611992565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561071b578181015183820152602001610703565b50505050905090810190601f1680156107485780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561076257600080fd5b506104886004803603602081101561077957600080fd5b5035611a06565b34801561078c57600080fd5b506102b8600480360360208110156107a357600080fd5b50356001600160a01b0316611a18565b3480156107bf57600080fd5b506102b8600480360360208110156107d657600080fd5b5035611b64565b61042c60048036036101408110156107f457600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b81111561082357600080fd5b82018360208201111561083557600080fd5b803590602001918460018302840111600160201b8311171561085657600080fd5b9193909260ff833516926020810135926040820135926060830135926001600160a01b03608082013581169360a083013590911692909160e081019060c00135600160201b8111156108a757600080fd5b8201836020820111156108b957600080fd5b803590602001918460018302840111600160201b831117156108da57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611c24945050505050565b34801561092757600080fd5b506104886004803603604081101561093e57600080fd5b506001600160a01b038135169060200135611e23565b34801561096057600080fd5b506102b86004803603606081101561097757600080fd5b81359190810190604081016020820135600160201b81111561099857600080fd5b8201836020820111156109aa57600080fd5b803590602001918460018302840111600160201b831117156109cb57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295949360208101935035915050600160201b811115610a1d57600080fd5b820183602082011115610a2f57600080fd5b803590602001918460018302840111600160201b83111715610a5057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611e40945050505050565b348015610a9d57600080fd5b50610aa6611e8f565b60408051602080825283518183015283519192839290830191858101910280838360005b83811015610ae2578181015183820152602001610aca565b505050509050019250505060405180910390f35b348015610b0257600080fd5b50610488611f73565b348015610b1757600080fd5b506102b860048036036040811015610b2e57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b811115610b5857600080fd5b820183602082011115610b6a57600080fd5b803590602001918460018302840111600160201b83111715610b8b57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611f79945050505050565b348015610bd857600080fd5b506102b86004803603610100811015610bf057600080fd5b810190602081018135600160201b811115610c0a57600080fd5b820183602082011115610c1c57600080fd5b803590602001918460208302840111600160201b83111715610c3d57600080fd5b919390928235926001600160a01b03602082013516929190606081019060400135600160201b811115610c6f57600080fd5b820183602082011115610c8157600080fd5b803590602001918460018302840111600160201b83111715610ca257600080fd5b91935091506001600160a01b03813581169160208101358216916040820135916060013516611f9c565b348015610cd857600080fd5b5061048860048036036080811015610cef57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b811115610d1e57600080fd5b820183602082011115610d3057600080fd5b803590602001918460018302840111600160201b83111715610d5157600080fd5b91935091503560ff166120ec565b348015610d6b57600080fd5b50610d9860048036036040811015610d8257600080fd5b506001600160a01b0381351690602001356121e9565b6040518080602001836001600160a01b03168152602001828103825284818151815260200191508051906020019060200280838360005b83811015610de7578181015183820152602001610dcf565b50505050905001935050505060405180910390f35b348015610e0857600080fd5b506102b860048036036020811015610e1f57600080fd5b50356122d4565b348015610e3257600080fd5b506104886004803603610140811015610e4a57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b811115610e7957600080fd5b820183602082011115610e8b57600080fd5b803590602001918460018302840111600160201b83111715610eac57600080fd5b919350915060ff813516906020810135906040810135906060810135906001600160a01b03608082013581169160a08101359091169060c0013561236e565b348015610ef757600080fd5b506102b860048036036040811015610f0e57600080fd5b506001600160a01b038135811691602001351661239b565b348015610f3257600080fd5b506102b860048036036020811015610f4957600080fd5b50356001600160a01b03166124d5565b348015610f6557600080fd5b506102b860048036036060811015610f7c57600080fd5b506001600160a01b038135811691602081013582169160409091013516612541565b348015610faa57600080fd5b5061048861278e565b348015610fbf57600080fd5b506106e16004803603610140811015610fd757600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b81111561100657600080fd5b82018360208201111561101857600080fd5b803590602001918460018302840111600160201b8311171561103957600080fd5b919350915060ff813516906020810135906040810135906060810135906001600160a01b03608082013581169160a08101359091169060c00135612794565b34801561108457600080fd5b506102b86004803603602081101561109b57600080fd5b50356001600160a01b03166128d5565b3480156110b757600080fd5b50610488612925565b3480156110cc57600080fd5b506102b8600480360360608110156110e357600080fd5b506001600160a01b03813581169160208101359091169060400135612993565b34801561110f57600080fd5b506106e1612b2c565b611120612b4d565b6001600160a01b0382161580159061114257506001600160a01b038216600114155b801561115757506001600160a01b0382163014155b611190576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b0382811660009081526002602052604090205416156111e5576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b600260209081527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e080546001600160a01b03858116600081815260408082208054949095166001600160a01b031994851617909455600190819052845490921681179093556003805490910190558051918252517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea26929181900390910190a180600454146112965761129681611b64565b5050565b6112a5816041612b8b565b825110156112e2576040805162461bcd60e51b8152602060048201526005602482015264047533032360dc1b604482015290519081900360640190fd5b6000808060008060005b868110156117ec576112fe8882612bb9565b9195509350915060ff841661159f57919350839161131d876041612b8b565b821015611359576040805162461bcd60e51b8152602060048201526005602482015264475330323160d81b604482015290519081900360640190fd5b8751611366836020612bd7565b11156113a1576040805162461bcd60e51b815260206004820152600560248201526423a998191960d91b604482015290519081900360640190fd5b6020828901810151895190916113c49083906113be908790612bd7565b90612bd7565b11156113ff576040805162461bcd60e51b8152602060048201526005602482015264475330323360d81b604482015290519081900360640190fd5b60606020848b010190506320c13b0b60e01b6001600160e01b031916876001600160a01b03166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561147c578181015183820152602001611464565b50505050905090810190601f1680156114a95780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b838110156114dc5781810151838201526020016114c4565b50505050905090810190601f1680156115095780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b15801561152857600080fd5b505afa15801561153c573d6000803e3d6000fd5b505050506040513d602081101561155257600080fd5b50516001600160e01b03191614611598576040805162461bcd60e51b815260206004820152600560248201526411d4cc0c8d60da1b604482015290519081900360640190fd5b5050611756565b8360ff1660011415611627579193508391336001600160a01b03841614806115e957506001600160a01b03851660009081526008602090815260408083208d845290915290205415155b611622576040805162461bcd60e51b8152602060048201526005602482015264475330323560d81b604482015290519081900360640190fd5b611756565b601e8460ff1611156116ef5760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156116de573d6000803e3d6000fd5b505050602060405103519450611756565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611749573d6000803e3d6000fd5b5050506020604051035194505b856001600160a01b0316856001600160a01b031611801561179057506001600160a01b038581166000908152600260205260409020541615155b80156117a657506001600160a01b038516600114155b6117df576040805162461bcd60e51b815260206004820152600560248201526423a998191b60d91b604482015290519081900360640190fd5b93945084936001016112ec565b50505050505050505050565b600060016001600160a01b0383161480159061182d57506001600160a01b038281166000908152600160205260409020541615155b92915050565b60006001600160a01b03821660011480159061182d5750506001600160a01b0390811660009081526002602052604090205416151590565b4690565b60007fb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e338686868660405180866001600160a01b03168152602001856001600160a01b03168152602001848152602001806020018360018111156118cf57fe5b8152602001828103825284818151815260200191508051906020019080838360005b838110156119095781810151838201526020016118f1565b50505050905090810190601f1680156119365780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a161195385858585612be9565b95945050505050565b6000606061196c8686868661186f565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060008260200267ffffffffffffffff811180156119b057600080fd5b506040519080825280601f01601f1916602001820160405280156119db576020820181803683370190505b50905060005b838110156119fe57848101546020808302840101526001016119e1565b509392505050565b60076020526000908152604090205481565b611a20612b4d565b6001600160a01b03811615801590611a4257506001600160a01b038116600114155b611a7b576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b038181166000908152600160205260409020541615611ad0576040805162461bcd60e51b815260206004820152600560248201526423a998981960d91b604482015290519081900360640190fd5b600160208181527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03858116600081815260408082208054949095166001600160a01b0319948516179094559590955282541684179091558051928352517fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f84409281900390910190a150565b611b6c612b4d565b600354811115611bab576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001811015611be9576040805162461bcd60e51b815260206004820152600560248201526423a999181960d91b604482015290519081900360640190fd5b60048190556040805182815290517f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c939181900360200190a150565b600060606005543360045460405160200180848152602001836001600160a01b03168152602001828152602001935050505060405160208183030381529060405290507f66753cd2356569ee081232e3be8909b950e0a76c1f8460c3a5e3c2be32b11bed8d8d8d8d8d8d8d8d8d8d8d8c604051808d6001600160a01b031681526020018c8152602001806020018a6001811115611cbd57fe5b8152602001898152602001888152602001878152602001866001600160a01b03168152602001856001600160a01b03168152602001806020018060200184810384528e8e828181526020019250808284376000838201819052601f909101601f19169092018681038552885181528851602091820193918a019250908190849084905b83811015611d58578181015183820152602001611d40565b50505050905090810190601f168015611d855780820380516001836020036101000a031916815260200191505b50848103825285518152855160209182019187019080838360005b83811015611db8578181015183820152602001611da0565b50505050905090810190601f168015611de55780820380516001836020036101000a031916815260200191505b509f5050505050505050505050505050505060405180910390a1611e128d8d8d8d8d8d8d8d8d8d8d612cc5565b9d9c50505050505050505050505050565b600860209081526000928352604080842090915290825290205481565b60045480611e7d576040805162461bcd60e51b8152602060048201526005602482015264475330303160d81b604482015290519081900360640190fd5b611e898484848461129a565b50505050565b6060600060035467ffffffffffffffff81118015611eac57600080fd5b50604051908082528060200260200182016040528015611ed6578160200160208202803683370190505b506001600090815260026020527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e054919250906001600160a01b03165b6001600160a01b038116600114611f6b5780838381518110611f3157fe5b6001600160a01b03928316602091820292909201810191909152918116600090815260029092526040909120546001929092019116611f13565b509091505090565b60055481565b600080825160208401855af480600052503d6020523d600060403e60403d016000fd5b611fda8a8a808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508c92506130c3915050565b6001600160a01b03841615611ff257611ff2846132fa565b6120328787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061331e92505050565b81156120495761204782600060018685613422565b505b336001600160a01b03167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b896040518080602001858152602001846001600160a01b03168152602001836001600160a01b031681526020018281038252878782818152602001925060200280828437600083820152604051601f909101601f19169092018290039850909650505050505050a250505050505050505050565b6000805a9050612135878787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525089925050505a613532565b61213e57600080fd5b60005a82039050806040516020018082815260200191505060405160208183030381529060405260405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156121ae578181015183820152602001612196565b50505050905090810190601f1680156121db5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff8111801561220457600080fd5b5060405190808252806020026020018201604052801561222e578160200160208202803683370190505b506001600160a01b0380861660009081526001602052604081205492945091165b6001600160a01b0381161580159061227157506001600160a01b038116600114155b801561227c57508482105b156122c6578084838151811061228e57fe5b6001600160a01b039283166020918202929092018101919091529181166000908152600192839052604090205492909101911661224f565b908352919491935090915050565b336000908152600260205260409020546001600160a01b0316612326576040805162461bcd60e51b8152602060048201526005602482015264047533033360dc1b604482015290519081900360640190fd5b336000818152600860209081526040808320858452909152808220600190555183917ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c91a350565b60006123838c8c8c8c8c8c8c8c8c8c8c612794565b8051906020012090509b9a5050505050505050505050565b6123a3612b4d565b6001600160a01b038116158015906123c557506001600160a01b038116600114155b6123fe576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b03828116600090815260016020526040902054811690821614612457576040805162461bcd60e51b8152602060048201526005602482015264475331303360d81b604482015290519081900360640190fd5b6001600160a01b038181166000818152600160209081526040808320805488871685528285208054919097166001600160a01b031991821617909655928490528254909416909155825191825291517faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace4054276929181900390910190a15050565b6124dd612b4d565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8818155604080516001600160a01b038416815290517f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa29181900360200190a15050565b612549612b4d565b6001600160a01b0381161580159061256b57506001600160a01b038116600114155b801561258057506001600160a01b0381163014155b6125b9576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b03818116600090815260026020526040902054161561260e576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b6001600160a01b0382161580159061263057506001600160a01b038216600114155b612669576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038381166000908152600260205260409020548116908316146126c2576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b038281166000818152600260209081526040808320805487871680865283862080549289166001600160a01b0319938416179055968a16855282852080548216909717909655928490528254909416909155825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a1604080516001600160a01b038316815290517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269181900360200190a1505050565b60045490565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b6001600160a01b031681526020018a815260200189815260200188600181111561281857fe5b8152602001878152602001868152602001858152602001846001600160a01b03168152602001836001600160a01b031681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b61288a612925565b604080516001600160f81b0319948516602082015292909316602183015260228201526042808201939093528151808203909301835260620190529c9b505050505050505050505050565b6128dd612b4d565b6128e6816132fa565b604080516001600160a01b038316815290517f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b09181900360200190a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921861295061186b565b3060405160200180848152602001838152602001826001600160a01b03168152602001935050505060405160208183030381529060405280519060200120905090565b61299b612b4d565b8060016003540310156129dd576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001600160a01b038216158015906129ff57506001600160a01b038216600114155b612a38576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b03838116600090815260026020526040902054811690831614612a91576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b038281166000818152600260209081526040808320805489871685528285208054919097166001600160a01b03199182161790965592849052825490941690915560038054600019019055825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a18060045414612b2757612b2781611b64565b505050565b604051806040016040528060058152602001640312e332e360dc1b81525081565b333014612b89576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b565b600082612b9a5750600061182d565b82820282848281612ba757fe5b0414612bb257600080fd5b9392505050565b60419081029190910160208101516040820151919092015160ff1692565b600082820183811015612bb257600080fd5b600033600114801590612c135750336000908152600160205260409020546001600160a01b031615155b612c4c576040805162461bcd60e51b815260206004820152600560248201526411d4cc4c0d60da1b604482015290519081900360640190fd5b612c59858585855a613532565b90508015612c915760405133907f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb890600090a2612cbd565b60405133907facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37590600090a25b949350505050565b6000806000612cdf8e8e8e8e8e8e8e8e8e8e600554612794565b6005805460010190558051602082012092509050612cfe828286611e40565b506000612d09613572565b90506001600160a01b03811615612e8457806001600160a01b03166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d6001600160a01b031681526020018c8152602001806020018a6001811115612d6d57fe5b8152602001898152602001888152602001878152602001866001600160a01b03168152602001856001600160a01b0316815260200180602001846001600160a01b0316815260200183810383528d8d828181526020019250808284376000838201819052601f909101601f191690920185810384528751815287516020918201939189019250908190849084905b83811015612e13578181015183820152602001612dfb565b50505050905090810190601f168015612e405780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015612e6b57600080fd5b505af1158015612e7f573d6000803e3d6000fd5b505050505b612e98603f60408b02046109c48b01613597565b6101f4015a1015612ed8576040805162461bcd60e51b8152602060048201526005602482015264047533031360dc1b604482015290519081900360640190fd5b60005a9050612f418f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e8c600014612f36578e612f3c565b6109c45a035b613532565b9350612f4e5a82906135ae565b90508380612f5b57508915155b80612f6557508715155b612f9e576040805162461bcd60e51b8152602060048201526005602482015264475330313360d81b604482015290519081900360640190fd5b60008815612fb657612fb3828b8b8b8b613422565b90505b8415612ffc57604080518581526020810183905281517f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e929181900390910190a1613038565b604080518581526020810183905281517f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23929181900390910190a15b50506001600160a01b038116156130b257806001600160a01b0316639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b15801561309957600080fd5b505af11580156130ad573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b60045415613100576040805162461bcd60e51b8152602060048201526005602482015264047533230360dc1b604482015290519081900360640190fd5b815181111561313e576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b600181101561317c576040805162461bcd60e51b815260206004820152600560248201526423a999181960d91b604482015290519081900360640190fd5b600160005b83518110156132c757600084828151811061319857fe5b6020026020010151905060006001600160a01b0316816001600160a01b0316141580156131cf57506001600160a01b038116600114155b80156131e457506001600160a01b0381163014155b80156132025750806001600160a01b0316836001600160a01b031614155b61323b576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038181166000908152600260205260409020541615613290576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b6001600160a01b03928316600090815260026020526040902080546001600160a01b03191693821693909317909255600101613181565b506001600160a01b0316600090815260026020526040902080546001600160a01b03191660011790559051600355600455565b7f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d555565b600160008190526020527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f546001600160a01b03161561338d576040805162461bcd60e51b8152602060048201526005602482015264047533130360dc1b604482015290519081900360640190fd5b6001600081905260208190527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03191690911790556001600160a01b03821615611296576133e98260008360015a613532565b611296576040805162461bcd60e51b8152602060048201526005602482015264047533030360dc1b604482015290519081900360640190fd5b6000806001600160a01b0383161561343a578261343c565b325b90506001600160a01b0384166134d45761346e3a861061345c573a61345e565b855b6134688989612bd7565b90612b8b565b6040519092506001600160a01b0382169083156108fc029084906000818181858888f193505050506134cf576040805162461bcd60e51b8152602060048201526005602482015264475330313160d81b604482015290519081900360640190fd5b613528565b6134e2856134688989612bd7565b91506134ef8482846135c3565b613528576040805162461bcd60e51b815260206004820152600560248201526423a998189960d91b604482015290519081900360640190fd5b5095945050505050565b6000600183600181111561354257fe5b141561355b576000808551602087018986f49050611953565b600080855160208701888a87f19695505050505050565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c85490565b6000818310156135a75781612bb2565b5090919050565b6000828211156135bd57600080fd5b50900390565b604080516001600160a01b03841660248201526044808201849052825180830390910181526064909101909152602081810180516001600160e01b031663a9059cbb60e01b1781528251600093929184919082896127105a03f13d8015613635576020811461363d5760009350613648565b819350613648565b600051158215171593505b505050939250505056fea2646970667358221220165c247ee6e1f63a4c02dfa873388abce9ff5e17198768d86c53a89f459fa54064736f6c63430007060033"},"sourceId":"GnosisSafeL2.sol","userdoc":{"kind":"user","methods":{"addOwnerWithThreshold(address,uint256)":{"notice":"Adds the owner `owner` to the Safe and updates the threshold to `_threshold`."},"changeThreshold(uint256)":{"notice":"Changes the threshold of the Safe to `_threshold`."},"disableModule(address,address)":{"notice":"Disables the module `module` for the Safe."},"enableModule(address)":{"notice":"Enables the module `module` for the Safe."},"removeOwner(address,address,uint256)":{"notice":"Removes the owner `owner` from the Safe and updates the threshold to `_threshold`."},"requiredTxGas(address,uint256,bytes,uint8)":{"notice":"Deprecated in favor of common/StorageAccessible.sol and will be removed in next version."},"swapOwner(address,address,address)":{"notice":"Replaces the owner `oldOwner` in the Safe with `newOwner`."}},"version":1}},"GnosisSafeMath":{"abi":[],"contractName":"GnosisSafeMath","deploymentBytecode":{"bytecode":"0x60566023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122001272b0bab6665c2e59df8fc52bce9a8ecb9dfc2b885f70890b7bad44580899064736f6c63430007060033"},"devdoc":{"details":"Math operations with safety checks that revert on error Renamed from SafeMath to GnosisSafeMath to avoid conflicts TODO: remove once open zeppelin update to solc 0.5.0","kind":"dev","methods":{},"title":"GnosisSafeMath","version":1},"runtimeBytecode":{"bytecode":"0x60566023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122001272b0bab6665c2e59df8fc52bce9a8ecb9dfc2b885f70890b7bad44580899064736f6c63430007060033"},"sourceId":"external/GnosisSafeMath.sol","userdoc":{"kind":"user","methods":{},"version":1}},"GnosisSafeProxy":{"abi":[{"inputs":[{"internalType":"address","name":"_singleton","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"stateMutability":"payable","type":"fallback"}],"contractName":"GnosisSafeProxy","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506040516101473803806101478339818101604052602081101561003357600080fd5b50516001600160a01b03811661007a5760405162461bcd60e51b81526004018080602001828103825260228152602001806101256022913960400191505060405180910390fd5b600080546001600160a01b039092166001600160a01b0319909216919091179055607c806100a96000396000f3fe6080604052600080546001600160a01b0316813563530ca43760e11b1415602857808252602082f35b3682833781823684845af490503d82833e806041573d82fd5b503d81f3fea2646970667358221220503a25e71ffd41e81cc75ab5778a9e547114333f8e37e5eb46ec658e6863bced64736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f7669646564"},"devdoc":{"author":"Stefan George - Richard Meissner - ","kind":"dev","methods":{"constructor":{"details":"Constructor function sets address of singleton contract.","params":{"_singleton":"Singleton address."}}},"title":"GnosisSafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract.","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506040516101473803806101478339818101604052602081101561003357600080fd5b50516001600160a01b03811661007a5760405162461bcd60e51b81526004018080602001828103825260228152602001806101256022913960400191505060405180910390fd5b600080546001600160a01b039092166001600160a01b0319909216919091179055607c806100a96000396000f3fe6080604052600080546001600160a01b0316813563530ca43760e11b1415602857808252602082f35b3682833781823684845af490503d82833e806041573d82fd5b503d81f3fea2646970667358221220503a25e71ffd41e81cc75ab5778a9e547114333f8e37e5eb46ec658e6863bced64736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f7669646564"},"sourceId":"proxies/GnosisSafeProxy.sol","userdoc":{"kind":"user","methods":{},"version":1}},"GnosisSafeProxyFactory":{"abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"},{"indexed":false,"internalType":"address","name":"singleton","type":"address"}],"name":"ProxyCreation","type":"event"},{"inputs":[{"internalType":"address","name":"_singleton","type":"address"},{"internalType":"bytes","name":"initializer","type":"bytes"},{"internalType":"uint256","name":"saltNonce","type":"uint256"}],"name":"calculateCreateProxyWithNonceAddress","outputs":[{"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"singleton","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"createProxy","outputs":[{"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_singleton","type":"address"},{"internalType":"bytes","name":"initializer","type":"bytes"},{"internalType":"uint256","name":"saltNonce","type":"uint256"},{"internalType":"contract IProxyCreationCallback","name":"callback","type":"address"}],"name":"createProxyWithCallback","outputs":[{"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_singleton","type":"address"},{"internalType":"bytes","name":"initializer","type":"bytes"},{"internalType":"uint256","name":"saltNonce","type":"uint256"}],"name":"createProxyWithNonce","outputs":[{"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"proxyCreationCode","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"proxyRuntimeCode","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","type":"function"}],"contractName":"GnosisSafeProxyFactory","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50610aa5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80631688f0b9146100675780632500510e1461013957806353e5d935146101b757806361b69abd14610234578063addacc0f146102e8578063d18af54d146102f0575b600080fd5b61011d6004803603606081101561007d57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156100a757600080fd5b8201836020820111156100b957600080fd5b803590602001918460018302840111600160201b831117156100da57600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955050913592506103b2915050565b604080516001600160a01b039092168252519081900360200190f35b61011d6004803603606081101561014f57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111600160201b831117156101ac57600080fd5b919350915035610430565b6101bf610526565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101f95781810151838201526020016101e1565b50505050905090810190601f1680156102265780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61011d6004803603604081101561024a57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561027457600080fd5b82018360208201111561028657600080fd5b803590602001918460018302840111600160201b831117156102a757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610550945050505050565b6101bf6105fd565b61011d6004803603608081101561030657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561033057600080fd5b82018360208201111561034257600080fd5b803590602001918460018302840111600160201b8311171561036357600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955050823593505050602001356001600160a01b031661060f565b60006103bf84848461075d565b8351909150156103e45760008060008551602087016000865af114156103e457600080fd5b604080516001600160a01b0380841682528616602082015281517f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235929181900390910190a19392505050565b60006104748585858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525087925061075d915050565b90508060405160200180826001600160a01b031660601b815260140191505060405160208183030381529060405260405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156104eb5781810151838201526020016104d3565b50505050905090810190601f1680156105185780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b60606040518060200161053890610893565b601f1982820381018352601f90910116604052905090565b60008260405161055f90610893565b6001600160a01b03909116815260405190819003602001906000f08015801561058c573d6000803e3d6000fd5b508251909150156105b25760008060008451602086016000865af114156105b257600080fd5b604080516001600160a01b0380841682528516602082015281517f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235929181900390910190a192915050565b606060405180602001610538906108a0565b6040805160208082018590526bffffffffffffffffffffffff19606085901b168284015282516034818403018152605490920190925280519101206000906106588686836103b2565b91506001600160a01b0383161561075457826001600160a01b0316631e52b518838888886040518563ffffffff1660e01b815260040180856001600160a01b03168152602001846001600160a01b0316815260200180602001838152602001828103825284818151815260200191508051906020019080838360005b838110156106ec5781810151838201526020016106d4565b50505050905090810190601f1680156107195780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b15801561073b57600080fd5b505af115801561074f573d6000803e3d6000fd5b505050505b50949350505050565b60008083805190602001208360405160200180838152602001828152602001925050506040516020818303038152906040528051906020012090506000604051806020016107aa90610893565b6020820181038252601f19601f82011660405250866001600160a01b03166040516020018083805190602001908083835b602083106107fa5780518252601f1990920191602091820191016107db565b51815160209384036101000a600019018019909216911617905292019384525060408051808503815293820190528251929450859350840190506000f592506001600160a01b03831661088a576040805162461bcd60e51b815260206004820152601360248201527210dc99585d194c8818d85b1b0819985a5b1959606a1b604482015290519081900360640190fd5b50509392505050565b610147806108ad83390190565b607c806109f48339019056fe608060405234801561001057600080fd5b506040516101473803806101478339818101604052602081101561003357600080fd5b50516001600160a01b03811661007a5760405162461bcd60e51b81526004018080602001828103825260228152602001806101256022913960400191505060405180910390fd5b600080546001600160a01b039092166001600160a01b0319909216919091179055607c806100a96000396000f3fe6080604052600080546001600160a01b0316813563530ca43760e11b1415602857808252602082f35b3682833781823684845af490503d82833e806041573d82fd5b503d81f3fea2646970667358221220503a25e71ffd41e81cc75ab5778a9e547114333f8e37e5eb46ec658e6863bced64736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f76696465646080604052600080546001600160a01b0316813563530ca43760e11b1415602857808252602082f35b3682833781823684845af490503d82833e806041573d82fd5b503d81f3fea2646970667358221220503a25e71ffd41e81cc75ab5778a9e547114333f8e37e5eb46ec658e6863bced64736f6c63430007060033a2646970667358221220918afad7eaa2480b53f19bd4444650dcba0b64f1bb868bb3d247f6a63cf0499b64736f6c63430007060033"},"devdoc":{"author":"Stefan George - ","kind":"dev","methods":{"calculateCreateProxyWithNonceAddress(address,bytes,uint256)":{"details":"Allows to get the address for a new proxy contact created via `createProxyWithNonce` This method is only meant for address calculation purpose when you use an initializer that would revert, therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory.","params":{"_singleton":"Address of singleton contract.","initializer":"Payload for message call sent to new proxy contract.","saltNonce":"Nonce that will be used to generate the salt to calculate the address of the new proxy contract."}},"createProxy(address,bytes)":{"details":"Allows to create new proxy contact and execute a message call to the new proxy within one transaction.","params":{"data":"Payload for message call sent to new proxy contract.","singleton":"Address of singleton contract."}},"createProxyWithCallback(address,bytes,uint256,address)":{"details":"Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction","params":{"_singleton":"Address of singleton contract.","callback":"Callback that will be invoced after the new proxy contract has been successfully deployed and initialized.","initializer":"Payload for message call sent to new proxy contract.","saltNonce":"Nonce that will be used to generate the salt to calculate the address of the new proxy contract."}},"createProxyWithNonce(address,bytes,uint256)":{"details":"Allows to create new proxy contact and execute a message call to the new proxy within one transaction.","params":{"_singleton":"Address of singleton contract.","initializer":"Payload for message call sent to new proxy contract.","saltNonce":"Nonce that will be used to generate the salt to calculate the address of the new proxy contract."}},"proxyCreationCode()":{"details":"Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address."},"proxyRuntimeCode()":{"details":"Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed."}},"title":"Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction.","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50610aa5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80631688f0b9146100675780632500510e1461013957806353e5d935146101b757806361b69abd14610234578063addacc0f146102e8578063d18af54d146102f0575b600080fd5b61011d6004803603606081101561007d57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b8111156100a757600080fd5b8201836020820111156100b957600080fd5b803590602001918460018302840111600160201b831117156100da57600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955050913592506103b2915050565b604080516001600160a01b039092168252519081900360200190f35b61011d6004803603606081101561014f57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111600160201b831117156101ac57600080fd5b919350915035610430565b6101bf610526565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101f95781810151838201526020016101e1565b50505050905090810190601f1680156102265780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61011d6004803603604081101561024a57600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561027457600080fd5b82018360208201111561028657600080fd5b803590602001918460018302840111600160201b831117156102a757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610550945050505050565b6101bf6105fd565b61011d6004803603608081101561030657600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561033057600080fd5b82018360208201111561034257600080fd5b803590602001918460018302840111600160201b8311171561036357600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955050823593505050602001356001600160a01b031661060f565b60006103bf84848461075d565b8351909150156103e45760008060008551602087016000865af114156103e457600080fd5b604080516001600160a01b0380841682528616602082015281517f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235929181900390910190a19392505050565b60006104748585858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525087925061075d915050565b90508060405160200180826001600160a01b031660601b815260140191505060405160208183030381529060405260405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156104eb5781810151838201526020016104d3565b50505050905090810190601f1680156105185780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b60606040518060200161053890610893565b601f1982820381018352601f90910116604052905090565b60008260405161055f90610893565b6001600160a01b03909116815260405190819003602001906000f08015801561058c573d6000803e3d6000fd5b508251909150156105b25760008060008451602086016000865af114156105b257600080fd5b604080516001600160a01b0380841682528516602082015281517f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235929181900390910190a192915050565b606060405180602001610538906108a0565b6040805160208082018590526bffffffffffffffffffffffff19606085901b168284015282516034818403018152605490920190925280519101206000906106588686836103b2565b91506001600160a01b0383161561075457826001600160a01b0316631e52b518838888886040518563ffffffff1660e01b815260040180856001600160a01b03168152602001846001600160a01b0316815260200180602001838152602001828103825284818151815260200191508051906020019080838360005b838110156106ec5781810151838201526020016106d4565b50505050905090810190601f1680156107195780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b15801561073b57600080fd5b505af115801561074f573d6000803e3d6000fd5b505050505b50949350505050565b60008083805190602001208360405160200180838152602001828152602001925050506040516020818303038152906040528051906020012090506000604051806020016107aa90610893565b6020820181038252601f19601f82011660405250866001600160a01b03166040516020018083805190602001908083835b602083106107fa5780518252601f1990920191602091820191016107db565b51815160209384036101000a600019018019909216911617905292019384525060408051808503815293820190528251929450859350840190506000f592506001600160a01b03831661088a576040805162461bcd60e51b815260206004820152601360248201527210dc99585d194c8818d85b1b0819985a5b1959606a1b604482015290519081900360640190fd5b50509392505050565b610147806108ad83390190565b607c806109f48339019056fe608060405234801561001057600080fd5b506040516101473803806101478339818101604052602081101561003357600080fd5b50516001600160a01b03811661007a5760405162461bcd60e51b81526004018080602001828103825260228152602001806101256022913960400191505060405180910390fd5b600080546001600160a01b039092166001600160a01b0319909216919091179055607c806100a96000396000f3fe6080604052600080546001600160a01b0316813563530ca43760e11b1415602857808252602082f35b3682833781823684845af490503d82833e806041573d82fd5b503d81f3fea2646970667358221220503a25e71ffd41e81cc75ab5778a9e547114333f8e37e5eb46ec658e6863bced64736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f76696465646080604052600080546001600160a01b0316813563530ca43760e11b1415602857808252602082f35b3682833781823684845af490503d82833e806041573d82fd5b503d81f3fea2646970667358221220503a25e71ffd41e81cc75ab5778a9e547114333f8e37e5eb46ec658e6863bced64736f6c63430007060033a2646970667358221220918afad7eaa2480b53f19bd4444650dcba0b64f1bb868bb3d247f6a63cf0499b64736f6c63430007060033"},"sourceId":"proxies/GnosisSafeProxyFactory.sol","userdoc":{"kind":"user","methods":{},"version":1}},"GnosisSafeStorage":{"abi":[],"contractName":"GnosisSafeStorage","deploymentBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220917c337425df846c9d1c7d4fff74332718043e68157d8e3f860a03acc6abeea664736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"GnosisSafeStorage - Storage layout of the Safe contracts to be used in libraries","version":1},"runtimeBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220917c337425df846c9d1c7d4fff74332718043e68157d8e3f860a03acc6abeea664736f6c63430007060033"},"sourceId":"examples/libraries/GnosisSafeStorage.sol","userdoc":{"kind":"user","methods":{},"version":1}},"Guard":{"abi":[{"inputs":[{"internalType":"bytes32","name":"txHash","type":"bytes32"},{"internalType":"bool","name":"success","type":"bool"}],"name":"checkAfterExecution","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address payable","name":"refundReceiver","type":"address"},{"internalType":"bytes","name":"signatures","type":"bytes"},{"internalType":"address","name":"msgSender","type":"address"}],"name":"checkTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"Guard","deploymentBytecode":{},"devdoc":{"kind":"dev","methods":{},"version":1},"runtimeBytecode":{},"sourceId":"base/GuardManager.sol","userdoc":{"kind":"user","methods":{},"version":1}},"GuardManager":{"abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"guard","type":"address"}],"name":"ChangedGuard","type":"event"},{"inputs":[{"internalType":"address","name":"guard","type":"address"}],"name":"setGuard","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"GuardManager","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b5061012f806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063e19a9dd914602d575b600080fd5b605060048036036020811015604157600080fd5b50356001600160a01b03166052565b005b605860bc565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8818155604080516001600160a01b038416815290517f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa29181900360200190a15050565b33301460f7576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b56fea26469706673582212206c6e509b8b6e947210e12564660b27bfcb37c86a50cee39cd46da51af25fcb9064736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{"setGuard(address)":{"details":"Set a guard that checks transactions before execution","params":{"guard":"The address of the guard to be used or the 0 address to disable the guard"}}},"title":"Fallback Manager - A contract that manages fallback calls made to this contract","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b5061012f806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063e19a9dd914602d575b600080fd5b605060048036036020811015604157600080fd5b50356001600160a01b03166052565b005b605860bc565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8818155604080516001600160a01b038416815290517f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa29181900360200190a15050565b33301460f7576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b56fea26469706673582212206c6e509b8b6e947210e12564660b27bfcb37c86a50cee39cd46da51af25fcb9064736f6c63430007060033"},"sourceId":"base/GuardManager.sol","userdoc":{"kind":"user","methods":{},"version":1}},"HandlerContext":{"abi":[],"contractName":"HandlerContext","deploymentBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea26469706673582212208e1813a08ca8338d384f4f4f636dba328a67ca705127986d4cbac5f643a326f364736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"Handler Context - allows to extract calling context","version":1},"runtimeBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea26469706673582212208e1813a08ca8338d384f4f4f636dba328a67ca705127986d4cbac5f643a326f364736f6c63430007060033"},"sourceId":"handler/HandlerContext.sol","userdoc":{"kind":"user","methods":{},"notice":"based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f8cc8b844a9f92f63dc55aa581f7d643a1bc5ac1/contracts/metatx/ERC2771Context.sol","version":1}},"IProxy":{"abi":[{"inputs":[],"name":"masterCopy","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"contractName":"IProxy","deploymentBytecode":{},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"IProxy - Helper interface to access masterCopy of the Proxy on-chain","version":1},"runtimeBytecode":{},"sourceId":"proxies/GnosisSafeProxy.sol","userdoc":{"kind":"user","methods":{},"version":1}},"IProxyCreationCallback":{"abi":[{"inputs":[{"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"},{"internalType":"address","name":"_singleton","type":"address"},{"internalType":"bytes","name":"initializer","type":"bytes"},{"internalType":"uint256","name":"saltNonce","type":"uint256"}],"name":"proxyCreated","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"IProxyCreationCallback","deploymentBytecode":{},"devdoc":{"kind":"dev","methods":{},"version":1},"runtimeBytecode":{},"sourceId":"proxies/IProxyCreationCallback.sol","userdoc":{"kind":"user","methods":{},"version":1}},"Migration":{"abi":[{"inputs":[{"internalType":"address","name":"targetSingleton","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"singleton","type":"address"}],"name":"ChangedMasterCopy","type":"event"},{"inputs":[],"name":"migrate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"migrationSingleton","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"safe120Singleton","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"contractName":"Migration","deploymentBytecode":{"bytecode":"0x60c060405234801561001057600080fd5b5060405161033b38038061033b8339818101604052602081101561003357600080fd5b50516001600160a01b03811661007a5760405162461bcd60e51b81526004018080602001828103825260228152602001806103196022913960400191505060405180910390fd5b606081811b6001600160601b03191660a052309081901b608052906001600160a01b03166102576100c26000398060a2528061013a525080607e528060cf52506102576000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80632e7731851461004657806389f543081461006a5780638fd3ab8014610072575b600080fd5b61004e61007c565b604080516001600160a01b039092168252519081900360200190f35b61004e6100a0565b61007a6100c4565b005b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016141561012c5760405162461bcd60e51b81526004018080602001828103825260308152602001806101f26030913960400191505060405180910390fd5b600080546001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166001600160a01b03199092169190911791829055604080517f035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749602080830191909152308284015282518083038401815260608301808552815191909201206006559390921690925290517f75e41bc35ff1bf14d81d1d2f649c0084a0f974f9289c803ec9898eeec4c8d0b89181900360800190a156fe4d6967726174696f6e2073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca2646970667358221220a9ecddeefb38416492ba61f3b9ce9d04773f5419ed4116e5f56e8f4eace5bcdd64736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f7669646564"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{"migrate()":{"details":"Allows to migrate the contract. This can only be called via a delegatecall."}},"title":"Migration - migrates a Safe contract from 1.3.0 to 1.2.0","version":1},"runtimeBytecode":{"bytecode":"0x60c060405234801561001057600080fd5b5060405161033b38038061033b8339818101604052602081101561003357600080fd5b50516001600160a01b03811661007a5760405162461bcd60e51b81526004018080602001828103825260228152602001806103196022913960400191505060405180910390fd5b606081811b6001600160601b03191660a052309081901b608052906001600160a01b03166102576100c26000398060a2528061013a525080607e528060cf52506102576000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80632e7731851461004657806389f543081461006a5780638fd3ab8014610072575b600080fd5b61004e61007c565b604080516001600160a01b039092168252519081900360200190f35b61004e6100a0565b61007a6100c4565b005b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016141561012c5760405162461bcd60e51b81526004018080602001828103825260308152602001806101f26030913960400191505060405180910390fd5b600080546001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166001600160a01b03199092169190911791829055604080517f035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749602080830191909152308284015282518083038401815260608301808552815191909201206006559390921690925290517f75e41bc35ff1bf14d81d1d2f649c0084a0f974f9289c803ec9898eeec4c8d0b89181900360800190a156fe4d6967726174696f6e2073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca2646970667358221220a9ecddeefb38416492ba61f3b9ce9d04773f5419ed4116e5f56e8f4eace5bcdd64736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f7669646564"},"sourceId":"examples/libraries/Migrate_1_3_0_to_1_2_0.sol","userdoc":{"kind":"user","methods":{},"version":1}},"ModuleManager":{"abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"module","type":"address"}],"name":"DisabledModule","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"module","type":"address"}],"name":"EnabledModule","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"}],"name":"ExecutionFromModuleFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"}],"name":"ExecutionFromModuleSuccess","type":"event"},{"inputs":[{"internalType":"address","name":"prevModule","type":"address"},{"internalType":"address","name":"module","type":"address"}],"name":"disableModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"enableModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"execTransactionFromModule","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"execTransactionFromModuleReturnData","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"start","type":"address"},{"internalType":"uint256","name":"pageSize","type":"uint256"}],"name":"getModulesPaginated","outputs":[{"internalType":"address[]","name":"array","type":"address[]"},{"internalType":"address","name":"next","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"isModuleEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"contractName":"ModuleManager","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506108fa806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80632d9ad53d14610067578063468721a7146100a15780635229073f14610161578063610b5925146102a2578063cc2f8452146102ca578063e009cfde1461035a575b600080fd5b61008d6004803603602081101561007d57600080fd5b50356001600160a01b0316610388565b604080519115158252519081900360200190f35b61008d600480360360808110156100b757600080fd5b6001600160a01b03823516916020810135918101906060810160408201356401000000008111156100e757600080fd5b8201836020820111156100f957600080fd5b8035906020019184600183028401116401000000008311171561011b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff1691506103c39050565b6102216004803603608081101561017757600080fd5b6001600160a01b03823516916020810135918101906060810160408201356401000000008111156101a757600080fd5b8201836020820111156101b957600080fd5b803590602001918460018302840111640100000000831117156101db57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff16915061049f9050565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561026657818101518382015260200161024e565b50505050905090810190601f1680156102935780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b6102c8600480360360208110156102b857600080fd5b50356001600160a01b03166104d5565b005b6102f6600480360360408110156102e057600080fd5b506001600160a01b038135169060200135610620565b6040518080602001836001600160a01b03168152602001828103825284818151815260200191508051906020019060200280838360005b8381101561034557818101518382015260200161032d565b50505050905001935050505060405180910390f35b6102c86004803603604081101561037057600080fd5b506001600160a01b038135811691602001351661070c565b600060016001600160a01b038316148015906103bd57506001600160a01b038281166000908152602081905260409020541615155b92915050565b6000336001148015906103ed5750336000908152602081905260409020546001600160a01b031615155b610426576040805162461bcd60e51b815260206004820152600560248201526411d4cc4c0d60da1b604482015290519081900360640190fd5b610433858585855a610844565b9050801561046b5760405133907f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb890600090a2610497565b60405133907facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37590600090a25b949350505050565b600060606104af868686866103c3565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b6104dd610886565b6001600160a01b038116158015906104ff57506001600160a01b038116600114155b610538576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b03818116600090815260208190526040902054161561058d576040805162461bcd60e51b815260206004820152600560248201526423a998981960d91b604482015290519081900360640190fd5b600060208181527fada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7d80546001600160a01b0385811680865260408087208054939094166001600160a01b031993841617909355600190955282541684179091558051928352517fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f84409281900390910190a150565b606060008267ffffffffffffffff8111801561063b57600080fd5b50604051908082528060200260200182016040528015610665578160200160208202803683370190505b506001600160a01b0380861660009081526020819052604081205492945091165b6001600160a01b038116158015906106a857506001600160a01b038116600114155b80156106b357508482105b156106fe57808483815181106106c557fe5b6001600160a01b039283166020918202929092018101919091529181166000908152918290526040909120546001929092019116610686565b908352919491935090915050565b610714610886565b6001600160a01b0381161580159061073657506001600160a01b038116600114155b61076f576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b038281166000908152602081905260409020548116908216146107c8576040805162461bcd60e51b8152602060048201526005602482015264475331303360d81b604482015290519081900360640190fd5b6001600160a01b03818116600081815260208181526040808320805488871685528285208054919097166001600160a01b031991821617909655928490528254909416909155825191825291517faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace4054276929181900390910190a15050565b6000600183600181111561085457fe5b141561086d576000808551602087018986f4905061087d565b600080855160208701888a87f190505b95945050505050565b3330146108c2576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b56fea2646970667358221220979224c09d8672ec22bd334ed4385625a49e1caaa40668c8c118813d8ac5af0b64736f6c63430007060033"},"devdoc":{"author":"Stefan George - Richard Meissner - ","kind":"dev","methods":{"disableModule(address,address)":{"details":"Allows to remove a module from the whitelist. This can only be done via a Safe transaction.","params":{"module":"Module to be removed.","prevModule":"Module that pointed to the module to be removed in the linked list"}},"enableModule(address)":{"details":"Allows to add a module to the whitelist. This can only be done via a Safe transaction.","params":{"module":"Module to be whitelisted."}},"execTransactionFromModule(address,uint256,bytes,uint8)":{"details":"Allows a Module to execute a Safe transaction without any further confirmations.","params":{"data":"Data payload of module transaction.","operation":"Operation type of module transaction.","to":"Destination address of module transaction.","value":"Ether value of module transaction."}},"execTransactionFromModuleReturnData(address,uint256,bytes,uint8)":{"details":"Allows a Module to execute a Safe transaction without any further confirmations and return data","params":{"data":"Data payload of module transaction.","operation":"Operation type of module transaction.","to":"Destination address of module transaction.","value":"Ether value of module transaction."}},"getModulesPaginated(address,uint256)":{"details":"Returns array of modules.","params":{"pageSize":"Maximum number of modules that should be returned.","start":"Start of the page."},"returns":{"array":"Array of modules.","next":"Start of the next page."}},"isModuleEnabled(address)":{"details":"Returns if an module is enabled","returns":{"_0":"True if the module is enabled"}}},"title":"Module Manager - A contract that manages modules that can execute transactions via this contract","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506108fa806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80632d9ad53d14610067578063468721a7146100a15780635229073f14610161578063610b5925146102a2578063cc2f8452146102ca578063e009cfde1461035a575b600080fd5b61008d6004803603602081101561007d57600080fd5b50356001600160a01b0316610388565b604080519115158252519081900360200190f35b61008d600480360360808110156100b757600080fd5b6001600160a01b03823516916020810135918101906060810160408201356401000000008111156100e757600080fd5b8201836020820111156100f957600080fd5b8035906020019184600183028401116401000000008311171561011b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff1691506103c39050565b6102216004803603608081101561017757600080fd5b6001600160a01b03823516916020810135918101906060810160408201356401000000008111156101a757600080fd5b8201836020820111156101b957600080fd5b803590602001918460018302840111640100000000831117156101db57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050903560ff16915061049f9050565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561026657818101518382015260200161024e565b50505050905090810190601f1680156102935780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b6102c8600480360360208110156102b857600080fd5b50356001600160a01b03166104d5565b005b6102f6600480360360408110156102e057600080fd5b506001600160a01b038135169060200135610620565b6040518080602001836001600160a01b03168152602001828103825284818151815260200191508051906020019060200280838360005b8381101561034557818101518382015260200161032d565b50505050905001935050505060405180910390f35b6102c86004803603604081101561037057600080fd5b506001600160a01b038135811691602001351661070c565b600060016001600160a01b038316148015906103bd57506001600160a01b038281166000908152602081905260409020541615155b92915050565b6000336001148015906103ed5750336000908152602081905260409020546001600160a01b031615155b610426576040805162461bcd60e51b815260206004820152600560248201526411d4cc4c0d60da1b604482015290519081900360640190fd5b610433858585855a610844565b9050801561046b5760405133907f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb890600090a2610497565b60405133907facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37590600090a25b949350505050565b600060606104af868686866103c3565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b6104dd610886565b6001600160a01b038116158015906104ff57506001600160a01b038116600114155b610538576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b03818116600090815260208190526040902054161561058d576040805162461bcd60e51b815260206004820152600560248201526423a998981960d91b604482015290519081900360640190fd5b600060208181527fada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7d80546001600160a01b0385811680865260408087208054939094166001600160a01b031993841617909355600190955282541684179091558051928352517fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f84409281900390910190a150565b606060008267ffffffffffffffff8111801561063b57600080fd5b50604051908082528060200260200182016040528015610665578160200160208202803683370190505b506001600160a01b0380861660009081526020819052604081205492945091165b6001600160a01b038116158015906106a857506001600160a01b038116600114155b80156106b357508482105b156106fe57808483815181106106c557fe5b6001600160a01b039283166020918202929092018101919091529181166000908152918290526040909120546001929092019116610686565b908352919491935090915050565b610714610886565b6001600160a01b0381161580159061073657506001600160a01b038116600114155b61076f576040805162461bcd60e51b8152602060048201526005602482015264475331303160d81b604482015290519081900360640190fd5b6001600160a01b038281166000908152602081905260409020548116908216146107c8576040805162461bcd60e51b8152602060048201526005602482015264475331303360d81b604482015290519081900360640190fd5b6001600160a01b03818116600081815260208181526040808320805488871685528285208054919097166001600160a01b031991821617909655928490528254909416909155825191825291517faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace4054276929181900390910190a15050565b6000600183600181111561085457fe5b141561086d576000808551602087018986f4905061087d565b600080855160208701888a87f190505b95945050505050565b3330146108c2576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b56fea2646970667358221220979224c09d8672ec22bd334ed4385625a49e1caaa40668c8c118813d8ac5af0b64736f6c63430007060033"},"sourceId":"base/ModuleManager.sol","userdoc":{"kind":"user","methods":{"disableModule(address,address)":{"notice":"Disables the module `module` for the Safe."},"enableModule(address)":{"notice":"Enables the module `module` for the Safe."}},"version":1}},"MultiSend":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bytes","name":"transactions","type":"bytes"}],"name":"multiSend","outputs":[],"stateMutability":"payable","type":"function"}],"contractName":"MultiSend","deploymentBytecode":{"bytecode":"0x60a060405234801561001057600080fd5b5030606081901b6080526102216100306000398060d652506102216000f3fe60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100c96004803603602081101561003957600080fd5b81019060208101813564010000000081111561005457600080fd5b82018360208201111561006657600080fd5b8035906020019184600183028401116401000000008311171561008857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506100cb945050505050565b005b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156101335760405162461bcd60e51b81526004018080602001828103825260308152602001806101bc6030913960400191505060405180910390fd5b805160205b818110156101b6578083015160f81c6001820184015160601c60158301850151603584018601516055850187016000856000811461017d576001811461018d57610198565b6000808585888a5af19150610198565b6000808585895af491505b50806101a357600080fd5b5050806055018501945050505050610138565b50505056fe4d756c746953656e642073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca2646970667358221220e1f7634caf12069f6838ba44ea5ce63cdf229517c6d4493bc6d02634f43b2dc064736f6c63430007060033"},"devdoc":{"author":"Nick Dodson - Gon\u00e7alo S\u00e1 - Stefan George - Richard Meissner - ","kind":"dev","methods":{"multiSend(bytes)":{"details":"Sends multiple transactions and reverts all if one fails.","params":{"transactions":"Encoded transactions. Each transaction is encoded as a packed bytes of operation as a uint8 with 0 for a call or 1 for a delegatecall (=> 1 byte), to as a address (=> 20 bytes), value as a uint256 (=> 32 bytes), data length as a uint256 (=> 32 bytes), data as bytes. see abi.encodePacked for more information on packed encoding"}}},"title":"Multi Send - Allows to batch multiple transactions into one.","version":1},"runtimeBytecode":{"bytecode":"0x60a060405234801561001057600080fd5b5030606081901b6080526102216100306000398060d652506102216000f3fe60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100c96004803603602081101561003957600080fd5b81019060208101813564010000000081111561005457600080fd5b82018360208201111561006657600080fd5b8035906020019184600183028401116401000000008311171561008857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506100cb945050505050565b005b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156101335760405162461bcd60e51b81526004018080602001828103825260308152602001806101bc6030913960400191505060405180910390fd5b805160205b818110156101b6578083015160f81c6001820184015160601c60158301850151603584018601516055850187016000856000811461017d576001811461018d57610198565b6000808585888a5af19150610198565b6000808585895af491505b50806101a357600080fd5b5050806055018501945050505050610138565b50505056fe4d756c746953656e642073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca2646970667358221220e1f7634caf12069f6838ba44ea5ce63cdf229517c6d4493bc6d02634f43b2dc064736f6c63430007060033"},"sourceId":"libraries/MultiSend.sol","userdoc":{"kind":"user","methods":{"multiSend(bytes)":{"notice":"This method is payable as delegatecalls keep the msg.value from the previous call If the calling method (e.g. execTransaction) received ETH this would revert otherwise"}},"version":1}},"MultiSendCallOnly":{"abi":[{"inputs":[{"internalType":"bytes","name":"transactions","type":"bytes"}],"name":"multiSend","outputs":[],"stateMutability":"payable","type":"function"}],"contractName":"MultiSendCallOnly","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b5061017a806100206000396000f3fe60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100c96004803603602081101561003957600080fd5b81019060208101813564010000000081111561005457600080fd5b82018360208201111561006657600080fd5b8035906020019184600183028401116401000000008311171561008857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506100cb945050505050565b005b805160205b8181101561013f578083015160f81c6001820184015160601c601583018501516035840186015160558501870160008560008114610115576001811461001e57610121565b6000808585888a5af191505b508061012c57600080fd5b50508060550185019450505050506100d0565b50505056fea2646970667358221220bcdb990346e42a0880780aa261b8d23a57f65b3b9cc2dc09f1c2dfe041922ca764736f6c63430007060033"},"devdoc":{"author":"Stefan George - Richard Meissner - ","kind":"dev","methods":{"multiSend(bytes)":{"details":"Sends multiple transactions and reverts all if one fails.","params":{"transactions":"Encoded transactions. Each transaction is encoded as a packed bytes of operation has to be uint8(0) in this version (=> 1 byte), to as a address (=> 20 bytes), value as a uint256 (=> 32 bytes), data length as a uint256 (=> 32 bytes), data as bytes. see abi.encodePacked for more information on packed encoding"}}},"title":"Multi Send Call Only - Allows to batch multiple transactions into one, but only calls","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b5061017a806100206000396000f3fe60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100c96004803603602081101561003957600080fd5b81019060208101813564010000000081111561005457600080fd5b82018360208201111561006657600080fd5b8035906020019184600183028401116401000000008311171561008857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506100cb945050505050565b005b805160205b8181101561013f578083015160f81c6001820184015160601c601583018501516035840186015160558501870160008560008114610115576001811461001e57610121565b6000808585888a5af191505b508061012c57600080fd5b50508060550185019450505050506100d0565b50505056fea2646970667358221220bcdb990346e42a0880780aa261b8d23a57f65b3b9cc2dc09f1c2dfe041922ca764736f6c63430007060033"},"sourceId":"libraries/MultiSendCallOnly.sol","userdoc":{"kind":"user","methods":{"multiSend(bytes)":{"notice":"The code is for most part the same as the normal MultiSend (to keep compatibility), but reverts if a transaction tries to use a delegatecall.This method is payable as delegatecalls keep the msg.value from the previous call If the calling method (e.g. execTransaction) received ETH this would revert otherwise"}},"notice":"The guard logic is not required here as this contract doesn't support nested delegate calls","version":1}},"OwnerManager":{"abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"AddedOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"threshold","type":"uint256"}],"name":"ChangedThreshold","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"RemovedOwner","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"addOwnerWithThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"changeThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getOwners","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"prevOwner","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"removeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"prevOwner","type":"address"},{"internalType":"address","name":"oldOwner","type":"address"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"swapOwner","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"OwnerManager","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b5061099e806100206000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063a0e67e2b1161005b578063a0e67e2b14610107578063e318b52b1461015f578063e75235b814610197578063f8dc5dd9146101b15761007d565b80630d582f13146100825780632f54bf6e146100b0578063694e80c3146100ea575b600080fd5b6100ae6004803603604081101561009857600080fd5b506001600160a01b0381351690602001356101e7565b005b6100d6600480360360208110156100c657600080fd5b50356001600160a01b0316610365565b604080519115158252519081900360200190f35b6100ae6004803603602081101561010057600080fd5b50356103a0565b61010f610460565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561014b578181015183820152602001610133565b505050509050019250505060405180910390f35b6100ae6004803603606081101561017557600080fd5b506001600160a01b038135811691602081013582169160409091013516610543565b61019f61078e565b60408051918252519081900360200190f35b6100ae600480360360608110156101c757600080fd5b506001600160a01b03813581169160208101359091169060400135610794565b6101ef61092a565b6001600160a01b0382161580159061021157506001600160a01b038216600114155b801561022657506001600160a01b0382163014155b61025f576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b0382811660009081526020819052604090205416156102b4576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b600060208181527fada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7d80546001600160a01b0386811680865260408087208054939094166001600160a01b0319938416179093556001958690528354909116811790925583548401909355825190815291517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269281900390910190a1806002541461036157610361816103a0565b5050565b60006001600160a01b03821660011480159061039a57506001600160a01b038281166000908152602081905260409020541615155b92915050565b6103a861092a565b6001548111156103e7576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001811015610425576040805162461bcd60e51b815260206004820152600560248201526423a999181960d91b604482015290519081900360640190fd5b60028190556040805182815290517f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c939181900360200190a150565b6060600060015467ffffffffffffffff8111801561047d57600080fd5b506040519080825280602002602001820160405280156104a7578160200160208202803683370190505b506001600090815260208190527fada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7d54919250906001600160a01b03165b6001600160a01b03811660011461053b578083838151811061050257fe5b6001600160a01b0392831660209182029290920181019190915291811660009081529182905260409091205460019290920191166104e4565b509091505090565b61054b61092a565b6001600160a01b0381161580159061056d57506001600160a01b038116600114155b801561058257506001600160a01b0381163014155b6105bb576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038181166000908152602081905260409020541615610610576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b6001600160a01b0382161580159061063257506001600160a01b038216600114155b61066b576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038381166000908152602081905260409020548116908316146106c4576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b03828116600081815260208181526040808320805487871680865283862080549289166001600160a01b0319938416179055968a16855282852080548216909717909655928490528254909416909155825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a1604080516001600160a01b038316815290517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269181900360200190a1505050565b60025490565b61079c61092a565b80600180540310156107dd576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001600160a01b038216158015906107ff57506001600160a01b038216600114155b610838576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b03838116600090815260208190526040902054811690831614610891576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b03828116600081815260208181526040808320805489871685528285208054919097166001600160a01b03199182161790965592849052825490941690915560018054600019019055825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a1806002541461092557610925816103a0565b505050565b333014610966576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b56fea264697066735822122074ca77d925c911bdda6c8da5421c38c1329142f67382480bf13064459a94acf664736f6c63430007060033"},"devdoc":{"author":"Stefan George - Richard Meissner - ","kind":"dev","methods":{"addOwnerWithThreshold(address,uint256)":{"details":"Allows to add a new owner to the Safe and update the threshold at the same time. This can only be done via a Safe transaction.","params":{"_threshold":"New threshold.","owner":"New owner address."}},"changeThreshold(uint256)":{"details":"Allows to update the number of required confirmations by Safe owners. This can only be done via a Safe transaction.","params":{"_threshold":"New threshold."}},"getOwners()":{"details":"Returns array of owners.","returns":{"_0":"Array of Safe owners."}},"removeOwner(address,address,uint256)":{"details":"Allows to remove an owner from the Safe and update the threshold at the same time. This can only be done via a Safe transaction.","params":{"_threshold":"New threshold.","owner":"Owner address to be removed.","prevOwner":"Owner that pointed to the owner to be removed in the linked list"}},"swapOwner(address,address,address)":{"details":"Allows to swap/replace an owner from the Safe with another address. This can only be done via a Safe transaction.","params":{"newOwner":"New owner address.","oldOwner":"Owner address to be replaced.","prevOwner":"Owner that pointed to the owner to be replaced in the linked list"}}},"title":"OwnerManager - Manages a set of owners and a threshold to perform actions.","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b5061099e806100206000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063a0e67e2b1161005b578063a0e67e2b14610107578063e318b52b1461015f578063e75235b814610197578063f8dc5dd9146101b15761007d565b80630d582f13146100825780632f54bf6e146100b0578063694e80c3146100ea575b600080fd5b6100ae6004803603604081101561009857600080fd5b506001600160a01b0381351690602001356101e7565b005b6100d6600480360360208110156100c657600080fd5b50356001600160a01b0316610365565b604080519115158252519081900360200190f35b6100ae6004803603602081101561010057600080fd5b50356103a0565b61010f610460565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561014b578181015183820152602001610133565b505050509050019250505060405180910390f35b6100ae6004803603606081101561017557600080fd5b506001600160a01b038135811691602081013582169160409091013516610543565b61019f61078e565b60408051918252519081900360200190f35b6100ae600480360360608110156101c757600080fd5b506001600160a01b03813581169160208101359091169060400135610794565b6101ef61092a565b6001600160a01b0382161580159061021157506001600160a01b038216600114155b801561022657506001600160a01b0382163014155b61025f576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b0382811660009081526020819052604090205416156102b4576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b600060208181527fada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7d80546001600160a01b0386811680865260408087208054939094166001600160a01b0319938416179093556001958690528354909116811790925583548401909355825190815291517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269281900390910190a1806002541461036157610361816103a0565b5050565b60006001600160a01b03821660011480159061039a57506001600160a01b038281166000908152602081905260409020541615155b92915050565b6103a861092a565b6001548111156103e7576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001811015610425576040805162461bcd60e51b815260206004820152600560248201526423a999181960d91b604482015290519081900360640190fd5b60028190556040805182815290517f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c939181900360200190a150565b6060600060015467ffffffffffffffff8111801561047d57600080fd5b506040519080825280602002602001820160405280156104a7578160200160208202803683370190505b506001600090815260208190527fada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7d54919250906001600160a01b03165b6001600160a01b03811660011461053b578083838151811061050257fe5b6001600160a01b0392831660209182029290920181019190915291811660009081529182905260409091205460019290920191166104e4565b509091505090565b61054b61092a565b6001600160a01b0381161580159061056d57506001600160a01b038116600114155b801561058257506001600160a01b0381163014155b6105bb576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038181166000908152602081905260409020541615610610576040805162461bcd60e51b815260206004820152600560248201526411d4cc8c0d60da1b604482015290519081900360640190fd5b6001600160a01b0382161580159061063257506001600160a01b038216600114155b61066b576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b038381166000908152602081905260409020548116908316146106c4576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b03828116600081815260208181526040808320805487871680865283862080549289166001600160a01b0319938416179055968a16855282852080548216909717909655928490528254909416909155825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a1604080516001600160a01b038316815290517f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269181900360200190a1505050565b60025490565b61079c61092a565b80600180540310156107dd576040805162461bcd60e51b8152602060048201526005602482015264475332303160d81b604482015290519081900360640190fd5b6001600160a01b038216158015906107ff57506001600160a01b038216600114155b610838576040805162461bcd60e51b8152602060048201526005602482015264475332303360d81b604482015290519081900360640190fd5b6001600160a01b03838116600090815260208190526040902054811690831614610891576040805162461bcd60e51b8152602060048201526005602482015264475332303560d81b604482015290519081900360640190fd5b6001600160a01b03828116600081815260208181526040808320805489871685528285208054919097166001600160a01b03199182161790965592849052825490941690915560018054600019019055825191825291517ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf929181900390910190a1806002541461092557610925816103a0565b505050565b333014610966576040805162461bcd60e51b8152602060048201526005602482015264475330333160d81b604482015290519081900360640190fd5b56fea264697066735822122074ca77d925c911bdda6c8da5421c38c1329142f67382480bf13064459a94acf664736f6c63430007060033"},"sourceId":"base/OwnerManager.sol","userdoc":{"kind":"user","methods":{"addOwnerWithThreshold(address,uint256)":{"notice":"Adds the owner `owner` to the Safe and updates the threshold to `_threshold`."},"changeThreshold(uint256)":{"notice":"Changes the threshold of the Safe to `_threshold`."},"removeOwner(address,address,uint256)":{"notice":"Removes the owner `owner` from the Safe and updates the threshold to `_threshold`."},"swapOwner(address,address,address)":{"notice":"Replaces the owner `oldOwner` in the Safe with `newOwner`."}},"version":1}},"ReentrancyTransactionGuard":{"abi":[{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bool","name":"","type":"bool"}],"name":"checkAfterExecution","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"enum Enum.Operation","name":"","type":"uint8"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address payable","name":"","type":"address"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"address","name":"","type":"address"}],"name":"checkTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"ReentrancyTransactionGuard","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506102c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806375f0bb521461003857806393271368146101b7575b005b610036600480360361016081101561004f57600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561007f57600080fd5b82018360208201111561009157600080fd5b803590602001918460018302840111640100000000831117156100b357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929560ff8535169560208601359560408101359550606081013594506001600160a01b0360808201358116945060a08201351692919060e081019060c0013564010000000081111561013757600080fd5b82018360208201111561014957600080fd5b8035906020019184600183028401116401000000008311171561016b57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b031691506101dc9050565b610036600480360360408110156101cd57600080fd5b5080359060200135151561024e565b60006101e661026b565b805490915060ff1615610236576040805162461bcd60e51b81526020600482015260136024820152721499595b9d1c985b98de4819195d1958dd1959606a1b604482015290519081900360640190fd5b805460ff191660011790555050505050505050505050565b600061025861026b565b805460ff19169115159190911790555050565b7f7c1d45961c2d0298f999d2c3d4a7a5e0f688d137f4c32466e3056a97e673b83a9056fea264697066735822122011a575bcfde6d4a38346f0c723338b0e198c09db76dd7e9c5501804c842ecf6a64736f6c63430007060033"},"devdoc":{"kind":"dev","methods":{},"version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b506102c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806375f0bb521461003857806393271368146101b7575b005b610036600480360361016081101561004f57600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561007f57600080fd5b82018360208201111561009157600080fd5b803590602001918460018302840111640100000000831117156100b357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929560ff8535169560208601359560408101359550606081013594506001600160a01b0360808201358116945060a08201351692919060e081019060c0013564010000000081111561013757600080fd5b82018360208201111561014957600080fd5b8035906020019184600183028401116401000000008311171561016b57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550505090356001600160a01b031691506101dc9050565b610036600480360360408110156101cd57600080fd5b5080359060200135151561024e565b60006101e661026b565b805490915060ff1615610236576040805162461bcd60e51b81526020600482015260136024820152721499595b9d1c985b98de4819195d1958dd1959606a1b604482015290519081900360640190fd5b805460ff191660011790555050505050505050505050565b600061025861026b565b805460ff19169115159190911790555050565b7f7c1d45961c2d0298f999d2c3d4a7a5e0f688d137f4c32466e3056a97e673b83a9056fea264697066735822122011a575bcfde6d4a38346f0c723338b0e198c09db76dd7e9c5501804c842ecf6a64736f6c63430007060033"},"sourceId":"examples/guards/ReentrancyTransactionGuard.sol","userdoc":{"kind":"user","methods":{},"version":1}},"SecuredTokenTransfer":{"abi":[],"contractName":"SecuredTokenTransfer","deploymentBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea264697066735822122048c70361e7dc61447e76ba5cb5ea625d54f194f99c33dac0fc555de7058acc3264736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"SecuredTokenTransfer - Secure token transfer","version":1},"runtimeBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea264697066735822122048c70361e7dc61447e76ba5cb5ea625d54f194f99c33dac0fc555de7058acc3264736f6c63430007060033"},"sourceId":"common/SecuredTokenTransfer.sol","userdoc":{"kind":"user","methods":{},"version":1}},"SelfAuthorized":{"abi":[],"contractName":"SelfAuthorized","deploymentBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220222263c0f1672a34a9ef1c008e0065dea707eec5712dc911c21204e4ea479f3f64736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"SelfAuthorized - authorizes current contract to perform actions","version":1},"runtimeBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220222263c0f1672a34a9ef1c008e0065dea707eec5712dc911c21204e4ea479f3f64736f6c63430007060033"},"sourceId":"common/SelfAuthorized.sol","userdoc":{"kind":"user","methods":{},"version":1}},"SignMessageLib":{"abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"msgHash","type":"bytes32"}],"name":"SignMsg","type":"event"},{"inputs":[{"internalType":"bytes","name":"message","type":"bytes"}],"name":"getMessageHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"signMessage","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"SignMessageLib","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b5061033b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80630a1028c41461003b57806385a5affe146100f3575b600080fd5b6100e16004803603602081101561005157600080fd5b81019060208101813564010000000081111561006c57600080fd5b82018360208201111561007e57600080fd5b803590602001918460018302840111640100000000831117156100a057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610165945050505050565b60408051918252519081900360200190f35b6101636004803603602081101561010957600080fd5b81019060208101813564010000000081111561012457600080fd5b82018360208201111561013657600080fd5b8035906020019184600183028401116401000000008311171561015857600080fd5b509092509050610282565b005b6000807f60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca60001b83805190602001206040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209050601960f81b600160f81b306001600160a01b031663f698da256040518163ffffffff1660e01b815260040160206040518083038186803b15801561020657600080fd5b505afa15801561021a573d6000803e3d6000fd5b505050506040513d602081101561023057600080fd5b5051604080516001600160f81b03199485166020808301919091529390941660218501526022840191909152604280840194909452805180840390940184526062909201909152815191012092915050565b60006102c383838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061016592505050565b600081815260076020526040808220600190555191925082917fe7f4675038f4f6034dfcbbb24c4dc08e4ebf10eb9d257d3d02c0f38d122ac6e49190a250505056fea2646970667358221220df6e2c1d89b0ee6e5f1532d17184041648a4b30173a4f3885e83c301373867bc64736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{"getMessageHash(bytes)":{"details":"Returns hash of a message that can be signed by owners.","params":{"message":"Message that should be hashed"},"returns":{"_0":"Message hash."}},"signMessage(bytes)":{"details":"Marks a message as signed, so that it can be used with EIP-1271","params":{"_data":"Arbitrary length data that should be marked as signed on the behalf of address(this)"}}},"title":"SignMessageLib - Allows to set an entry in the signedMessages","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b5061033b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80630a1028c41461003b57806385a5affe146100f3575b600080fd5b6100e16004803603602081101561005157600080fd5b81019060208101813564010000000081111561006c57600080fd5b82018360208201111561007e57600080fd5b803590602001918460018302840111640100000000831117156100a057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610165945050505050565b60408051918252519081900360200190f35b6101636004803603602081101561010957600080fd5b81019060208101813564010000000081111561012457600080fd5b82018360208201111561013657600080fd5b8035906020019184600183028401116401000000008311171561015857600080fd5b509092509050610282565b005b6000807f60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca60001b83805190602001206040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209050601960f81b600160f81b306001600160a01b031663f698da256040518163ffffffff1660e01b815260040160206040518083038186803b15801561020657600080fd5b505afa15801561021a573d6000803e3d6000fd5b505050506040513d602081101561023057600080fd5b5051604080516001600160f81b03199485166020808301919091529390941660218501526022840191909152604280840194909452805180840390940184526062909201909152815191012092915050565b60006102c383838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061016592505050565b600081815260076020526040808220600190555191925082917fe7f4675038f4f6034dfcbbb24c4dc08e4ebf10eb9d257d3d02c0f38d122ac6e49190a250505056fea2646970667358221220df6e2c1d89b0ee6e5f1532d17184041648a4b30173a4f3885e83c301373867bc64736f6c63430007060033"},"sourceId":"examples/libraries/SignMessage.sol","userdoc":{"kind":"user","methods":{"signMessage(bytes)":{"notice":"Marks a message (`_data`) as signed."}},"version":1}},"SignatureDecoder":{"abi":[],"contractName":"SignatureDecoder","deploymentBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220206e0953e66634162f3b30fa0ffe0f4fd6369e0370eec99264d3546aa3ba573864736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"SignatureDecoder - Decodes signatures that a encoded as bytes","version":1},"runtimeBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220206e0953e66634162f3b30fa0ffe0f4fd6369e0370eec99264d3546aa3ba573864736f6c63430007060033"},"sourceId":"common/SignatureDecoder.sol","userdoc":{"kind":"user","methods":{},"version":1}},"SimulateTxAccessor":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"simulate","outputs":[{"internalType":"uint256","name":"estimate","type":"uint256"},{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"stateMutability":"nonpayable","type":"function"}],"contractName":"SimulateTxAccessor","deploymentBytecode":{"bytecode":"0x60a060405234801561001057600080fd5b5030606081901b6080526102d46100316000398061015052506102d46000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80631c5fb21114610030575b600080fd5b6100b86004803603608081101561004657600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561007657600080fd5b82018360208201111561008857600080fd5b803590602001918460018302840111640100000000831117156100aa57600080fd5b91935091503560ff16610140565b60405180848152602001831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156101035781810151838201526020016100eb565b50505050905090810190601f1680156101305780820380516001836020036101000a031916815260200191505b5094505050505060405180910390f35b6000806060306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156101ad5760405162461bcd60e51b81526004018080602001828103825260398152602001806102666039913960400191505060405180910390fd5b60005a90506101f5898989898080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508b925050505a610223565b92505a8103935060405160203d0181016040523d81523d6000602083013e8092505050955095509592505050565b6000600183600181111561023357fe5b141561024c576000808551602087018986f4905061025c565b600080855160208701888a87f190505b9594505050505056fe53696d756c61746554784163636573736f722073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca264697066735822122062ace18647629309e0ac9e7b8b73530b415e4de6c708b5a4747be3eca7f0702064736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"Simulate Transaction Accessor - can be used with StorageAccessible to simulate Safe transactions","version":1},"runtimeBytecode":{"bytecode":"0x60a060405234801561001057600080fd5b5030606081901b6080526102d46100316000398061015052506102d46000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80631c5fb21114610030575b600080fd5b6100b86004803603608081101561004657600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561007657600080fd5b82018360208201111561008857600080fd5b803590602001918460018302840111640100000000831117156100aa57600080fd5b91935091503560ff16610140565b60405180848152602001831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156101035781810151838201526020016100eb565b50505050905090810190601f1680156101305780820380516001836020036101000a031916815260200191505b5094505050505060405180910390f35b6000806060306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614156101ad5760405162461bcd60e51b81526004018080602001828103825260398152602001806102666039913960400191505060405180910390fd5b60005a90506101f5898989898080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508b925050505a610223565b92505a8103935060405160203d0181016040523d81523d6000602083013e8092505050955095509592505050565b6000600183600181111561023357fe5b141561024c576000808551602087018986f4905061025c565b600080855160208701888a87f190505b9594505050505056fe53696d756c61746554784163636573736f722073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca264697066735822122062ace18647629309e0ac9e7b8b73530b415e4de6c708b5a4747be3eca7f0702064736f6c63430007060033"},"sourceId":"accessors/SimulateTxAccessor.sol","userdoc":{"kind":"user","methods":{},"version":1}},"Singleton":{"abi":[],"contractName":"Singleton","deploymentBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea26469706673582212208ed9bd3502cda534b616b0c6157d645551928ffb66a7cf9eee0f56defd632cc364736f6c63430007060033"},"devdoc":{"author":"Richard Meissner - ","kind":"dev","methods":{},"title":"Singleton - Base for singleton contracts (should always be first super contract) This contract is tightly coupled to our proxy contract (see `proxies/GnosisSafeProxy.sol`)","version":1},"runtimeBytecode":{"bytecode":"0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea26469706673582212208ed9bd3502cda534b616b0c6157d645551928ffb66a7cf9eee0f56defd632cc364736f6c63430007060033"},"sourceId":"common/Singleton.sol","userdoc":{"kind":"user","methods":{},"version":1}},"StorageAccessible":{"abi":[{"inputs":[{"internalType":"uint256","name":"offset","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"getStorageAt","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"targetContract","type":"address"},{"internalType":"bytes","name":"calldataPayload","type":"bytes"}],"name":"simulateAndRevert","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"StorageAccessible","deploymentBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50610258806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635624b25b1461003b578063b4faba09146100d3575b600080fd5b61005e6004803603604081101561005157600080fd5b508035906020013561018b565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610098578181015183820152602001610080565b50505050905090810190601f1680156100c55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610189600480360360408110156100e957600080fd5b6001600160a01b03823516919081019060408101602082013564010000000081111561011457600080fd5b82018360208201111561012657600080fd5b8035906020019184600183028401116401000000008311171561014857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506101ff945050505050565b005b606060008260200267ffffffffffffffff811180156101a957600080fd5b506040519080825280601f01601f1916602001820160405280156101d4576020820181803683370190505b50905060005b838110156101f757848101546020808302840101526001016101da565b509392505050565b600080825160208401855af480600052503d6020523d600060403e60403d016000fdfea2646970667358221220e833ead982802eb4dedb4442b37857b87e13e5509df8bb854ca6a5567a5cde3764736f6c63430007060033"},"devdoc":{"kind":"dev","methods":{"getStorageAt(uint256,uint256)":{"details":"Reads `length` bytes of storage in the currents contract","params":{"length":"- the number of words (32 bytes) of data to read","offset":"- the offset in the current contract's storage in words to start reading from"},"returns":{"_0":"the bytes that were read."}},"simulateAndRevert(address,bytes)":{"details":"Performs a delegetecall on a targetContract in the context of self. Internally reverts execution to avoid side effects (making it static). This method reverts with data equal to `abi.encode(bool(success), bytes(response))`. Specifically, the `returndata` after a call to this method will be: `success:bool || response.length:uint256 || response:bytes`.","params":{"calldataPayload":"Calldata that should be sent to the target contract (encoded method name and arguments).","targetContract":"Address of the contract containing the code to execute."}}},"title":"StorageAccessible - generic base contract that allows callers to access all internal storage.","version":1},"runtimeBytecode":{"bytecode":"0x608060405234801561001057600080fd5b50610258806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635624b25b1461003b578063b4faba09146100d3575b600080fd5b61005e6004803603604081101561005157600080fd5b508035906020013561018b565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610098578181015183820152602001610080565b50505050905090810190601f1680156100c55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610189600480360360408110156100e957600080fd5b6001600160a01b03823516919081019060408101602082013564010000000081111561011457600080fd5b82018360208201111561012657600080fd5b8035906020019184600183028401116401000000008311171561014857600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506101ff945050505050565b005b606060008260200267ffffffffffffffff811180156101a957600080fd5b506040519080825280601f01601f1916602001820160405280156101d4576020820181803683370190505b50905060005b838110156101f757848101546020808302840101526001016101da565b509392505050565b600080825160208401855af480600052503d6020523d600060403e60403d016000fdfea2646970667358221220e833ead982802eb4dedb4442b37857b87e13e5509df8bb854ca6a5567a5cde3764736f6c63430007060033"},"sourceId":"common/StorageAccessible.sol","userdoc":{"kind":"user","methods":{},"notice":"See https://github.com/gnosis/util-contracts/blob/bb5fe5fb5df6d8400998094fb1b32a178a47c3a1/contracts/StorageAccessible.sol","version":1}}},"manifest":"ethpm/3","sources":{"GnosisSafe.sol":{"checksum":{"algorithm":"md5","hash":"0x6302a9ad4b2767251b680676d0dfa334"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"./base/ModuleManager.sol\";\nimport \"./base/OwnerManager.sol\";\nimport \"./base/FallbackManager.sol\";\nimport \"./base/GuardManager.sol\";\nimport \"./common/EtherPaymentFallback.sol\";\nimport \"./common/Singleton.sol\";\nimport \"./common/SignatureDecoder.sol\";\nimport \"./common/SecuredTokenTransfer.sol\";\nimport \"./common/StorageAccessible.sol\";\nimport \"./interfaces/ISignatureValidator.sol\";\nimport \"./external/GnosisSafeMath.sol\";\n\n/// @title Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191.\n/// @author Stefan George - \n/// @author Richard Meissner - \ncontract GnosisSafe is\n EtherPaymentFallback,\n Singleton,\n ModuleManager,\n OwnerManager,\n SignatureDecoder,\n SecuredTokenTransfer,\n ISignatureValidatorConstants,\n FallbackManager,\n StorageAccessible,\n GuardManager\n{\n using GnosisSafeMath for uint256;\n\n string public constant VERSION = \"1.3.0\";\n\n // keccak256(\n // \"EIP712Domain(uint256 chainId,address verifyingContract)\"\n // );\n bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218;\n\n // keccak256(\n // \"SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)\"\n // );\n bytes32 private constant SAFE_TX_TYPEHASH = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8;\n\n event SafeSetup(address indexed initiator, address[] owners, uint256 threshold, address initializer, address fallbackHandler);\n event ApproveHash(bytes32 indexed approvedHash, address indexed owner);\n event SignMsg(bytes32 indexed msgHash);\n event ExecutionFailure(bytes32 txHash, uint256 payment);\n event ExecutionSuccess(bytes32 txHash, uint256 payment);\n\n uint256 public nonce;\n bytes32 private _deprecatedDomainSeparator;\n // Mapping to keep track of all message hashes that have been approve by ALL REQUIRED owners\n mapping(bytes32 => uint256) public signedMessages;\n // Mapping to keep track of all hashes (message or transaction) that have been approve by ANY owners\n mapping(address => mapping(bytes32 => uint256)) public approvedHashes;\n\n // This constructor ensures that this contract can only be used as a master copy for Proxy contracts\n constructor() {\n // By setting the threshold it is not possible to call setup anymore,\n // so we create a Safe with 0 owners and threshold 1.\n // This is an unusable Safe, perfect for the singleton\n threshold = 1;\n }\n\n /// @dev Setup function sets initial storage of contract.\n /// @param _owners List of Safe owners.\n /// @param _threshold Number of required confirmations for a Safe transaction.\n /// @param to Contract address for optional delegate call.\n /// @param data Data payload for optional delegate call.\n /// @param fallbackHandler Handler for fallback calls to this contract\n /// @param paymentToken Token that should be used for the payment (0 is ETH)\n /// @param payment Value that should be paid\n /// @param paymentReceiver Adddress that should receive the payment (or 0 if tx.origin)\n function setup(\n address[] calldata _owners,\n uint256 _threshold,\n address to,\n bytes calldata data,\n address fallbackHandler,\n address paymentToken,\n uint256 payment,\n address payable paymentReceiver\n ) external {\n // setupOwners checks if the Threshold is already set, therefore preventing that this method is called twice\n setupOwners(_owners, _threshold);\n if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler);\n // As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules\n setupModules(to, data);\n\n if (payment > 0) {\n // To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code of that has been verified we do not adjust the method itself)\n // baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment\n handlePayment(payment, 0, 1, paymentToken, paymentReceiver);\n }\n emit SafeSetup(msg.sender, _owners, _threshold, to, fallbackHandler);\n }\n\n /// @dev Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction.\n /// Note: The fees are always transferred, even if the user transaction fails.\n /// @param to Destination address of Safe transaction.\n /// @param value Ether value of Safe transaction.\n /// @param data Data payload of Safe transaction.\n /// @param operation Operation type of Safe transaction.\n /// @param safeTxGas Gas that should be used for the Safe transaction.\n /// @param baseGas Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)\n /// @param gasPrice Gas price that should be used for the payment calculation.\n /// @param gasToken Token address (or 0 if ETH) that is used for the payment.\n /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin).\n /// @param signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v})\n function execTransaction(\n address to,\n uint256 value,\n bytes calldata data,\n Enum.Operation operation,\n uint256 safeTxGas,\n uint256 baseGas,\n uint256 gasPrice,\n address gasToken,\n address payable refundReceiver,\n bytes memory signatures\n ) public payable virtual returns (bool success) {\n bytes32 txHash;\n // Use scope here to limit variable lifetime and prevent `stack too deep` errors\n {\n bytes memory txHashData =\n encodeTransactionData(\n // Transaction info\n to,\n value,\n data,\n operation,\n safeTxGas,\n // Payment info\n baseGas,\n gasPrice,\n gasToken,\n refundReceiver,\n // Signature info\n nonce\n );\n // Increase nonce and execute transaction.\n nonce++;\n txHash = keccak256(txHashData);\n checkSignatures(txHash, txHashData, signatures);\n }\n address guard = getGuard();\n {\n if (guard != address(0)) {\n Guard(guard).checkTransaction(\n // Transaction info\n to,\n value,\n data,\n operation,\n safeTxGas,\n // Payment info\n baseGas,\n gasPrice,\n gasToken,\n refundReceiver,\n // Signature info\n signatures,\n msg.sender\n );\n }\n }\n // We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500)\n // We also include the 1/64 in the check that is not send along with a call to counteract potential shortings because of EIP-150\n require(gasleft() >= ((safeTxGas * 64) / 63).max(safeTxGas + 2500) + 500, \"GS010\");\n // Use scope here to limit variable lifetime and prevent `stack too deep` errors\n {\n uint256 gasUsed = gasleft();\n // If the gasPrice is 0 we assume that nearly all available gas can be used (it is always more than safeTxGas)\n // We only substract 2500 (compared to the 3000 before) to ensure that the amount passed is still higher than safeTxGas\n success = execute(to, value, data, operation, gasPrice == 0 ? (gasleft() - 2500) : safeTxGas);\n gasUsed = gasUsed.sub(gasleft());\n // If no safeTxGas and no gasPrice was set (e.g. both are 0), then the internal tx is required to be successful\n // This makes it possible to use `estimateGas` without issues, as it searches for the minimum gas where the tx doesn't revert\n require(success || safeTxGas != 0 || gasPrice != 0, \"GS013\");\n // We transfer the calculated tx costs to the tx.origin to avoid sending it to intermediate contracts that have made calls\n uint256 payment = 0;\n if (gasPrice > 0) {\n payment = handlePayment(gasUsed, baseGas, gasPrice, gasToken, refundReceiver);\n }\n if (success) emit ExecutionSuccess(txHash, payment);\n else emit ExecutionFailure(txHash, payment);\n }\n {\n if (guard != address(0)) {\n Guard(guard).checkAfterExecution(txHash, success);\n }\n }\n }\n\n function handlePayment(\n uint256 gasUsed,\n uint256 baseGas,\n uint256 gasPrice,\n address gasToken,\n address payable refundReceiver\n ) private returns (uint256 payment) {\n // solhint-disable-next-line avoid-tx-origin\n address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver;\n if (gasToken == address(0)) {\n // For ETH we will only adjust the gas price to not be higher than the actual used gas price\n payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice);\n require(receiver.send(payment), \"GS011\");\n } else {\n payment = gasUsed.add(baseGas).mul(gasPrice);\n require(transferToken(gasToken, receiver, payment), \"GS012\");\n }\n }\n\n /**\n * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.\n * @param dataHash Hash of the data (could be either a message hash or transaction hash)\n * @param data That should be signed (this is passed to an external validator contract)\n * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash.\n */\n function checkSignatures(\n bytes32 dataHash,\n bytes memory data,\n bytes memory signatures\n ) public view {\n // Load threshold to avoid multiple storage loads\n uint256 _threshold = threshold;\n // Check that a threshold is set\n require(_threshold > 0, \"GS001\");\n checkNSignatures(dataHash, data, signatures, _threshold);\n }\n\n /**\n * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.\n * @param dataHash Hash of the data (could be either a message hash or transaction hash)\n * @param data That should be signed (this is passed to an external validator contract)\n * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash.\n * @param requiredSignatures Amount of required valid signatures.\n */\n function checkNSignatures(\n bytes32 dataHash,\n bytes memory data,\n bytes memory signatures,\n uint256 requiredSignatures\n ) public view {\n // Check that the provided signature data is not too short\n require(signatures.length >= requiredSignatures.mul(65), \"GS020\");\n // There cannot be an owner with address 0.\n address lastOwner = address(0);\n address currentOwner;\n uint8 v;\n bytes32 r;\n bytes32 s;\n uint256 i;\n for (i = 0; i < requiredSignatures; i++) {\n (v, r, s) = signatureSplit(signatures, i);\n if (v == 0) {\n // If v is 0 then it is a contract signature\n // When handling contract signatures the address of the contract is encoded into r\n currentOwner = address(uint160(uint256(r)));\n\n // Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes\n // This check is not completely accurate, since it is possible that more signatures than the threshold are send.\n // Here we only check that the pointer is not pointing inside the part that is being processed\n require(uint256(s) >= requiredSignatures.mul(65), \"GS021\");\n\n // Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes)\n require(uint256(s).add(32) <= signatures.length, \"GS022\");\n\n // Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length\n uint256 contractSignatureLen;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n contractSignatureLen := mload(add(add(signatures, s), 0x20))\n }\n require(uint256(s).add(32).add(contractSignatureLen) <= signatures.length, \"GS023\");\n\n // Check signature\n bytes memory contractSignature;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n // The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s\n contractSignature := add(add(signatures, s), 0x20)\n }\n require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, \"GS024\");\n } else if (v == 1) {\n // If v is 1 then it is an approved hash\n // When handling approved hashes the address of the approver is encoded into r\n currentOwner = address(uint160(uint256(r)));\n // Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction\n require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, \"GS025\");\n } else if (v > 30) {\n // If v > 30 then default va (27,28) has been adjusted for eth_sign flow\n // To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover\n currentOwner = ecrecover(keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", dataHash)), v - 4, r, s);\n } else {\n // Default is the ecrecover flow with the provided data hash\n // Use ecrecover with the messageHash for EOA signatures\n currentOwner = ecrecover(dataHash, v, r, s);\n }\n require(currentOwner > lastOwner && owners[currentOwner] != address(0) && currentOwner != SENTINEL_OWNERS, \"GS026\");\n lastOwner = currentOwner;\n }\n }\n\n /// @dev Allows to estimate a Safe transaction.\n /// This method is only meant for estimation purpose, therefore the call will always revert and encode the result in the revert data.\n /// Since the `estimateGas` function includes refunds, call this method to get an estimated of the costs that are deducted from the safe with `execTransaction`\n /// @param to Destination address of Safe transaction.\n /// @param value Ether value of Safe transaction.\n /// @param data Data payload of Safe transaction.\n /// @param operation Operation type of Safe transaction.\n /// @return Estimate without refunds and overhead fees (base transaction and payload data gas costs).\n /// @notice Deprecated in favor of common/StorageAccessible.sol and will be removed in next version.\n function requiredTxGas(\n address to,\n uint256 value,\n bytes calldata data,\n Enum.Operation operation\n ) external returns (uint256) {\n uint256 startGas = gasleft();\n // We don't provide an error message here, as we use it to return the estimate\n require(execute(to, value, data, operation, gasleft()));\n uint256 requiredGas = startGas - gasleft();\n // Convert response to string and return via error message\n revert(string(abi.encodePacked(requiredGas)));\n }\n\n /**\n * @dev Marks a hash as approved. This can be used to validate a hash that is used by a signature.\n * @param hashToApprove The hash that should be marked as approved for signatures that are verified by this contract.\n */\n function approveHash(bytes32 hashToApprove) external {\n require(owners[msg.sender] != address(0), \"GS030\");\n approvedHashes[msg.sender][hashToApprove] = 1;\n emit ApproveHash(hashToApprove, msg.sender);\n }\n\n /// @dev Returns the chain id used by this contract.\n function getChainId() public view returns (uint256) {\n uint256 id;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n id := chainid()\n }\n return id;\n }\n\n function domainSeparator() public view returns (bytes32) {\n return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this));\n }\n\n /// @dev Returns the bytes that are hashed to be signed by owners.\n /// @param to Destination address.\n /// @param value Ether value.\n /// @param data Data payload.\n /// @param operation Operation type.\n /// @param safeTxGas Gas that should be used for the safe transaction.\n /// @param baseGas Gas costs for that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)\n /// @param gasPrice Maximum gas price that should be used for this transaction.\n /// @param gasToken Token address (or 0 if ETH) that is used for the payment.\n /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin).\n /// @param _nonce Transaction nonce.\n /// @return Transaction hash bytes.\n function encodeTransactionData(\n address to,\n uint256 value,\n bytes calldata data,\n Enum.Operation operation,\n uint256 safeTxGas,\n uint256 baseGas,\n uint256 gasPrice,\n address gasToken,\n address refundReceiver,\n uint256 _nonce\n ) public view returns (bytes memory) {\n bytes32 safeTxHash =\n keccak256(\n abi.encode(\n SAFE_TX_TYPEHASH,\n to,\n value,\n keccak256(data),\n operation,\n safeTxGas,\n baseGas,\n gasPrice,\n gasToken,\n refundReceiver,\n _nonce\n )\n );\n return abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator(), safeTxHash);\n }\n\n /// @dev Returns hash to be signed by owners.\n /// @param to Destination address.\n /// @param value Ether value.\n /// @param data Data payload.\n /// @param operation Operation type.\n /// @param safeTxGas Fas that should be used for the safe transaction.\n /// @param baseGas Gas costs for data used to trigger the safe transaction.\n /// @param gasPrice Maximum gas price that should be used for this transaction.\n /// @param gasToken Token address (or 0 if ETH) that is used for the payment.\n /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin).\n /// @param _nonce Transaction nonce.\n /// @return Transaction hash.\n function getTransactionHash(\n address to,\n uint256 value,\n bytes calldata data,\n Enum.Operation operation,\n uint256 safeTxGas,\n uint256 baseGas,\n uint256 gasPrice,\n address gasToken,\n address refundReceiver,\n uint256 _nonce\n ) public view returns (bytes32) {\n return keccak256(encodeTransactionData(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce));\n }\n}\n","imports":["common/EtherPaymentFallback.sol","common/SecuredTokenTransfer.sol","base/OwnerManager.sol","common/SignatureDecoder.sol","common/Singleton.sol","base/ModuleManager.sol","interfaces/ISignatureValidator.sol","base/GuardManager.sol","common/StorageAccessible.sol","base/FallbackManager.sol","external/GnosisSafeMath.sol"],"references":["examples/guards/DebugTransactionGuard.sol","GnosisSafeL2.sol","examples/libraries/SignMessage.sol","examples/guards/DelegateCallTransactionGuard.sol","handler/CompatibilityFallbackHandler.sol","examples/guards/ReentrancyTransactionGuard.sol"],"urls":[]},"GnosisSafeL2.sol":{"checksum":{"algorithm":"md5","hash":"0xb7617493b9f2a136251658cc880640e9"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"./GnosisSafe.sol\";\n\n/// @title Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191.\n/// @author Stefan George - \n/// @author Richard Meissner - \ncontract GnosisSafeL2 is GnosisSafe {\n event SafeMultiSigTransaction(\n address to,\n uint256 value,\n bytes data,\n Enum.Operation operation,\n uint256 safeTxGas,\n uint256 baseGas,\n uint256 gasPrice,\n address gasToken,\n address payable refundReceiver,\n bytes signatures,\n // We combine nonce, sender and threshold into one to avoid stack too deep\n // Dev note: additionalInfo should not contain `bytes`, as this complicates decoding\n bytes additionalInfo\n );\n\n event SafeModuleTransaction(address module, address to, uint256 value, bytes data, Enum.Operation operation);\n\n /// @dev Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction.\n /// Note: The fees are always transferred, even if the user transaction fails.\n /// @param to Destination address of Safe transaction.\n /// @param value Ether value of Safe transaction.\n /// @param data Data payload of Safe transaction.\n /// @param operation Operation type of Safe transaction.\n /// @param safeTxGas Gas that should be used for the Safe transaction.\n /// @param baseGas Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)\n /// @param gasPrice Gas price that should be used for the payment calculation.\n /// @param gasToken Token address (or 0 if ETH) that is used for the payment.\n /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin).\n /// @param signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v})\n function execTransaction(\n address to,\n uint256 value,\n bytes calldata data,\n Enum.Operation operation,\n uint256 safeTxGas,\n uint256 baseGas,\n uint256 gasPrice,\n address gasToken,\n address payable refundReceiver,\n bytes memory signatures\n ) public payable override returns (bool) {\n bytes memory additionalInfo;\n {\n additionalInfo = abi.encode(nonce, msg.sender, threshold);\n }\n emit SafeMultiSigTransaction(\n to,\n value,\n data,\n operation,\n safeTxGas,\n baseGas,\n gasPrice,\n gasToken,\n refundReceiver,\n signatures,\n additionalInfo\n );\n return super.execTransaction(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, signatures);\n }\n\n /// @dev Allows a Module to execute a Safe transaction without any further confirmations.\n /// @param to Destination address of module transaction.\n /// @param value Ether value of module transaction.\n /// @param data Data payload of module transaction.\n /// @param operation Operation type of module transaction.\n function execTransactionFromModule(\n address to,\n uint256 value,\n bytes memory data,\n Enum.Operation operation\n ) public override returns (bool success) {\n emit SafeModuleTransaction(msg.sender, to, value, data, operation);\n success = super.execTransactionFromModule(to, value, data, operation);\n }\n}\n","imports":["GnosisSafe.sol"],"references":[],"urls":[]},"accessors/SimulateTxAccessor.sol":{"checksum":{"algorithm":"md5","hash":"0x54a9a2ed1655e1b79bfc6651f2e3c032"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../base/Executor.sol\";\n\n/// @title Simulate Transaction Accessor - can be used with StorageAccessible to simulate Safe transactions\n/// @author Richard Meissner - \ncontract SimulateTxAccessor is Executor {\n address private immutable accessorSingleton;\n\n constructor() {\n accessorSingleton = address(this);\n }\n\n modifier onlyDelegateCall() {\n require(address(this) != accessorSingleton, \"SimulateTxAccessor should only be called via delegatecall\");\n _;\n }\n\n function simulate(\n address to,\n uint256 value,\n bytes calldata data,\n Enum.Operation operation\n )\n external\n onlyDelegateCall()\n returns (\n uint256 estimate,\n bool success,\n bytes memory returnData\n )\n {\n uint256 startGas = gasleft();\n success = execute(to, value, data, operation, gasleft());\n estimate = startGas - gasleft();\n // solhint-disable-next-line no-inline-assembly\n assembly {\n // Load free memory location\n let ptr := mload(0x40)\n // We allocate memory for the return data by setting the free memory location to\n // current free memory location + data size + 32 bytes for data size value\n mstore(0x40, add(ptr, add(returndatasize(), 0x20)))\n // Store the size\n mstore(ptr, returndatasize())\n // Store the data\n returndatacopy(add(ptr, 0x20), 0, returndatasize())\n // Point the return data to the correct memory location\n returnData := ptr\n }\n }\n}\n","imports":["base/Executor.sol"],"references":[],"urls":[]},"base/Executor.sol":{"checksum":{"algorithm":"md5","hash":"0x5d439865306475654cf018596f1b1061"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\nimport \"../common/Enum.sol\";\n\n/// @title Executor - A contract that can execute transactions\n/// @author Richard Meissner - \ncontract Executor {\n function execute(\n address to,\n uint256 value,\n bytes memory data,\n Enum.Operation operation,\n uint256 txGas\n ) internal returns (bool success) {\n if (operation == Enum.Operation.DelegateCall) {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)\n }\n } else {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)\n }\n }\n }\n}\n","imports":["common/Enum.sol"],"references":["accessors/SimulateTxAccessor.sol","base/ModuleManager.sol"],"urls":[]},"base/FallbackManager.sol":{"checksum":{"algorithm":"md5","hash":"0xde30ab56ace98b84e2a047164e06d0c5"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../common/SelfAuthorized.sol\";\n\n/// @title Fallback Manager - A contract that manages fallback calls made to this contract\n/// @author Richard Meissner - \ncontract FallbackManager is SelfAuthorized {\n event ChangedFallbackHandler(address handler);\n\n // keccak256(\"fallback_manager.handler.address\")\n bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5;\n\n function internalSetFallbackHandler(address handler) internal {\n bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(slot, handler)\n }\n }\n\n /// @dev Allows to add a contract to handle fallback calls.\n /// Only fallback calls without value and with data will be forwarded.\n /// This can only be done via a Safe transaction.\n /// @param handler contract to handle fallbacks calls.\n function setFallbackHandler(address handler) public authorized {\n internalSetFallbackHandler(handler);\n emit ChangedFallbackHandler(handler);\n }\n\n // solhint-disable-next-line payable-fallback,no-complex-fallback\n fallback() external {\n bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let handler := sload(slot)\n if iszero(handler) {\n return(0, 0)\n }\n calldatacopy(0, 0, calldatasize())\n // The msg.sender address is shifted to the left by 12 bytes to remove the padding\n // Then the address without padding is stored right after the calldata\n mstore(calldatasize(), shl(96, caller()))\n // Add 20 bytes for the address appended add the end\n let success := call(gas(), handler, 0, 0, add(calldatasize(), 20), 0, 0)\n returndatacopy(0, 0, returndatasize())\n if iszero(success) {\n revert(0, returndatasize())\n }\n return(0, returndatasize())\n }\n }\n}\n","imports":["common/SelfAuthorized.sol"],"references":["GnosisSafe.sol"],"urls":[]},"base/GuardManager.sol":{"checksum":{"algorithm":"md5","hash":"0x30b3fe3c67c994d66c5965199d64d86d"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../common/Enum.sol\";\nimport \"../common/SelfAuthorized.sol\";\n\ninterface Guard {\n function checkTransaction(\n address to,\n uint256 value,\n bytes memory data,\n Enum.Operation operation,\n uint256 safeTxGas,\n uint256 baseGas,\n uint256 gasPrice,\n address gasToken,\n address payable refundReceiver,\n bytes memory signatures,\n address msgSender\n ) external;\n\n function checkAfterExecution(bytes32 txHash, bool success) external;\n}\n\n/// @title Fallback Manager - A contract that manages fallback calls made to this contract\n/// @author Richard Meissner - \ncontract GuardManager is SelfAuthorized {\n event ChangedGuard(address guard);\n // keccak256(\"guard_manager.guard.address\")\n bytes32 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8;\n\n /// @dev Set a guard that checks transactions before execution\n /// @param guard The address of the guard to be used or the 0 address to disable the guard\n function setGuard(address guard) external authorized {\n bytes32 slot = GUARD_STORAGE_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(slot, guard)\n }\n emit ChangedGuard(guard);\n }\n\n function getGuard() internal view returns (address guard) {\n bytes32 slot = GUARD_STORAGE_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n guard := sload(slot)\n }\n }\n}\n","imports":["common/Enum.sol","common/SelfAuthorized.sol"],"references":["examples/guards/DebugTransactionGuard.sol","examples/guards/DelegateCallTransactionGuard.sol","GnosisSafe.sol","examples/guards/ReentrancyTransactionGuard.sol"],"urls":[]},"base/ModuleManager.sol":{"checksum":{"algorithm":"md5","hash":"0x90b2541c32a7644991f6ec3510dc3776"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\nimport \"../common/Enum.sol\";\nimport \"../common/SelfAuthorized.sol\";\nimport \"./Executor.sol\";\n\n/// @title Module Manager - A contract that manages modules that can execute transactions via this contract\n/// @author Stefan George - \n/// @author Richard Meissner - \ncontract ModuleManager is SelfAuthorized, Executor {\n event EnabledModule(address module);\n event DisabledModule(address module);\n event ExecutionFromModuleSuccess(address indexed module);\n event ExecutionFromModuleFailure(address indexed module);\n\n address internal constant SENTINEL_MODULES = address(0x1);\n\n mapping(address => address) internal modules;\n\n function setupModules(address to, bytes memory data) internal {\n require(modules[SENTINEL_MODULES] == address(0), \"GS100\");\n modules[SENTINEL_MODULES] = SENTINEL_MODULES;\n if (to != address(0))\n // Setup has to complete successfully or transaction fails.\n require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), \"GS000\");\n }\n\n /// @dev Allows to add a module to the whitelist.\n /// This can only be done via a Safe transaction.\n /// @notice Enables the module `module` for the Safe.\n /// @param module Module to be whitelisted.\n function enableModule(address module) public authorized {\n // Module address cannot be null or sentinel.\n require(module != address(0) && module != SENTINEL_MODULES, \"GS101\");\n // Module cannot be added twice.\n require(modules[module] == address(0), \"GS102\");\n modules[module] = modules[SENTINEL_MODULES];\n modules[SENTINEL_MODULES] = module;\n emit EnabledModule(module);\n }\n\n /// @dev Allows to remove a module from the whitelist.\n /// This can only be done via a Safe transaction.\n /// @notice Disables the module `module` for the Safe.\n /// @param prevModule Module that pointed to the module to be removed in the linked list\n /// @param module Module to be removed.\n function disableModule(address prevModule, address module) public authorized {\n // Validate module address and check that it corresponds to module index.\n require(module != address(0) && module != SENTINEL_MODULES, \"GS101\");\n require(modules[prevModule] == module, \"GS103\");\n modules[prevModule] = modules[module];\n modules[module] = address(0);\n emit DisabledModule(module);\n }\n\n /// @dev Allows a Module to execute a Safe transaction without any further confirmations.\n /// @param to Destination address of module transaction.\n /// @param value Ether value of module transaction.\n /// @param data Data payload of module transaction.\n /// @param operation Operation type of module transaction.\n function execTransactionFromModule(\n address to,\n uint256 value,\n bytes memory data,\n Enum.Operation operation\n ) public virtual returns (bool success) {\n // Only whitelisted modules are allowed.\n require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), \"GS104\");\n // Execute transaction without further confirmations.\n success = execute(to, value, data, operation, gasleft());\n if (success) emit ExecutionFromModuleSuccess(msg.sender);\n else emit ExecutionFromModuleFailure(msg.sender);\n }\n\n /// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data\n /// @param to Destination address of module transaction.\n /// @param value Ether value of module transaction.\n /// @param data Data payload of module transaction.\n /// @param operation Operation type of module transaction.\n function execTransactionFromModuleReturnData(\n address to,\n uint256 value,\n bytes memory data,\n Enum.Operation operation\n ) public returns (bool success, bytes memory returnData) {\n success = execTransactionFromModule(to, value, data, operation);\n // solhint-disable-next-line no-inline-assembly\n assembly {\n // Load free memory location\n let ptr := mload(0x40)\n // We allocate memory for the return data by setting the free memory location to\n // current free memory location + data size + 32 bytes for data size value\n mstore(0x40, add(ptr, add(returndatasize(), 0x20)))\n // Store the size\n mstore(ptr, returndatasize())\n // Store the data\n returndatacopy(add(ptr, 0x20), 0, returndatasize())\n // Point the return data to the correct memory location\n returnData := ptr\n }\n }\n\n /// @dev Returns if an module is enabled\n /// @return True if the module is enabled\n function isModuleEnabled(address module) public view returns (bool) {\n return SENTINEL_MODULES != module && modules[module] != address(0);\n }\n\n /// @dev Returns array of modules.\n /// @param start Start of the page.\n /// @param pageSize Maximum number of modules that should be returned.\n /// @return array Array of modules.\n /// @return next Start of the next page.\n function getModulesPaginated(address start, uint256 pageSize) external view returns (address[] memory array, address next) {\n // Init array with max page size\n array = new address[](pageSize);\n\n // Populate return array\n uint256 moduleCount = 0;\n address currentModule = modules[start];\n while (currentModule != address(0x0) && currentModule != SENTINEL_MODULES && moduleCount < pageSize) {\n array[moduleCount] = currentModule;\n currentModule = modules[currentModule];\n moduleCount++;\n }\n next = currentModule;\n // Set correct size of returned array\n // solhint-disable-next-line no-inline-assembly\n assembly {\n mstore(array, moduleCount)\n }\n }\n}\n","imports":["common/Enum.sol","common/SelfAuthorized.sol","base/Executor.sol"],"references":["GnosisSafe.sol"],"urls":[]},"base/OwnerManager.sol":{"checksum":{"algorithm":"md5","hash":"0x53e497cc090cb4931ca5905d7f671cc5"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\nimport \"../common/SelfAuthorized.sol\";\n\n/// @title OwnerManager - Manages a set of owners and a threshold to perform actions.\n/// @author Stefan George - \n/// @author Richard Meissner - \ncontract OwnerManager is SelfAuthorized {\n event AddedOwner(address owner);\n event RemovedOwner(address owner);\n event ChangedThreshold(uint256 threshold);\n\n address internal constant SENTINEL_OWNERS = address(0x1);\n\n mapping(address => address) internal owners;\n uint256 internal ownerCount;\n uint256 internal threshold;\n\n /// @dev Setup function sets initial storage of contract.\n /// @param _owners List of Safe owners.\n /// @param _threshold Number of required confirmations for a Safe transaction.\n function setupOwners(address[] memory _owners, uint256 _threshold) internal {\n // Threshold can only be 0 at initialization.\n // Check ensures that setup function can only be called once.\n require(threshold == 0, \"GS200\");\n // Validate that threshold is smaller than number of added owners.\n require(_threshold <= _owners.length, \"GS201\");\n // There has to be at least one Safe owner.\n require(_threshold >= 1, \"GS202\");\n // Initializing Safe owners.\n address currentOwner = SENTINEL_OWNERS;\n for (uint256 i = 0; i < _owners.length; i++) {\n // Owner address cannot be null.\n address owner = _owners[i];\n require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, \"GS203\");\n // No duplicate owners allowed.\n require(owners[owner] == address(0), \"GS204\");\n owners[currentOwner] = owner;\n currentOwner = owner;\n }\n owners[currentOwner] = SENTINEL_OWNERS;\n ownerCount = _owners.length;\n threshold = _threshold;\n }\n\n /// @dev Allows to add a new owner to the Safe and update the threshold at the same time.\n /// This can only be done via a Safe transaction.\n /// @notice Adds the owner `owner` to the Safe and updates the threshold to `_threshold`.\n /// @param owner New owner address.\n /// @param _threshold New threshold.\n function addOwnerWithThreshold(address owner, uint256 _threshold) public authorized {\n // Owner address cannot be null, the sentinel or the Safe itself.\n require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), \"GS203\");\n // No duplicate owners allowed.\n require(owners[owner] == address(0), \"GS204\");\n owners[owner] = owners[SENTINEL_OWNERS];\n owners[SENTINEL_OWNERS] = owner;\n ownerCount++;\n emit AddedOwner(owner);\n // Change threshold if threshold was changed.\n if (threshold != _threshold) changeThreshold(_threshold);\n }\n\n /// @dev Allows to remove an owner from the Safe and update the threshold at the same time.\n /// This can only be done via a Safe transaction.\n /// @notice Removes the owner `owner` from the Safe and updates the threshold to `_threshold`.\n /// @param prevOwner Owner that pointed to the owner to be removed in the linked list\n /// @param owner Owner address to be removed.\n /// @param _threshold New threshold.\n function removeOwner(\n address prevOwner,\n address owner,\n uint256 _threshold\n ) public authorized {\n // Only allow to remove an owner, if threshold can still be reached.\n require(ownerCount - 1 >= _threshold, \"GS201\");\n // Validate owner address and check that it corresponds to owner index.\n require(owner != address(0) && owner != SENTINEL_OWNERS, \"GS203\");\n require(owners[prevOwner] == owner, \"GS205\");\n owners[prevOwner] = owners[owner];\n owners[owner] = address(0);\n ownerCount--;\n emit RemovedOwner(owner);\n // Change threshold if threshold was changed.\n if (threshold != _threshold) changeThreshold(_threshold);\n }\n\n /// @dev Allows to swap/replace an owner from the Safe with another address.\n /// This can only be done via a Safe transaction.\n /// @notice Replaces the owner `oldOwner` in the Safe with `newOwner`.\n /// @param prevOwner Owner that pointed to the owner to be replaced in the linked list\n /// @param oldOwner Owner address to be replaced.\n /// @param newOwner New owner address.\n function swapOwner(\n address prevOwner,\n address oldOwner,\n address newOwner\n ) public authorized {\n // Owner address cannot be null, the sentinel or the Safe itself.\n require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), \"GS203\");\n // No duplicate owners allowed.\n require(owners[newOwner] == address(0), \"GS204\");\n // Validate oldOwner address and check that it corresponds to owner index.\n require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, \"GS203\");\n require(owners[prevOwner] == oldOwner, \"GS205\");\n owners[newOwner] = owners[oldOwner];\n owners[prevOwner] = newOwner;\n owners[oldOwner] = address(0);\n emit RemovedOwner(oldOwner);\n emit AddedOwner(newOwner);\n }\n\n /// @dev Allows to update the number of required confirmations by Safe owners.\n /// This can only be done via a Safe transaction.\n /// @notice Changes the threshold of the Safe to `_threshold`.\n /// @param _threshold New threshold.\n function changeThreshold(uint256 _threshold) public authorized {\n // Validate that threshold is smaller than number of owners.\n require(_threshold <= ownerCount, \"GS201\");\n // There has to be at least one Safe owner.\n require(_threshold >= 1, \"GS202\");\n threshold = _threshold;\n emit ChangedThreshold(threshold);\n }\n\n function getThreshold() public view returns (uint256) {\n return threshold;\n }\n\n function isOwner(address owner) public view returns (bool) {\n return owner != SENTINEL_OWNERS && owners[owner] != address(0);\n }\n\n /// @dev Returns array of owners.\n /// @return Array of Safe owners.\n function getOwners() public view returns (address[] memory) {\n address[] memory array = new address[](ownerCount);\n\n // populate return array\n uint256 index = 0;\n address currentOwner = owners[SENTINEL_OWNERS];\n while (currentOwner != SENTINEL_OWNERS) {\n array[index] = currentOwner;\n currentOwner = owners[currentOwner];\n index++;\n }\n return array;\n }\n}\n","imports":["common/SelfAuthorized.sol"],"references":["GnosisSafe.sol"],"urls":[]},"common/Enum.sol":{"checksum":{"algorithm":"md5","hash":"0x70d93d6b3e8b23f419fe3e529a43c17e"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title Enum - Collection of enums\n/// @author Richard Meissner - \ncontract Enum {\n enum Operation {Call, DelegateCall}\n}\n","imports":[],"references":["examples/guards/DebugTransactionGuard.sol","base/ModuleManager.sol","base/GuardManager.sol","examples/guards/DelegateCallTransactionGuard.sol","base/Executor.sol","examples/guards/ReentrancyTransactionGuard.sol"],"urls":[]},"common/EtherPaymentFallback.sol":{"checksum":{"algorithm":"md5","hash":"0x566ab89634daf8e542177b1e7016971e"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title EtherPaymentFallback - A contract that has a fallback to accept ether payments\n/// @author Richard Meissner - \ncontract EtherPaymentFallback {\n event SafeReceived(address indexed sender, uint256 value);\n\n /// @dev Fallback function accepts Ether transactions.\n receive() external payable {\n emit SafeReceived(msg.sender, msg.value);\n }\n}\n","imports":[],"references":["GnosisSafe.sol"],"urls":[]},"common/SecuredTokenTransfer.sol":{"checksum":{"algorithm":"md5","hash":"0x80c88034d9e09f88f6853fc707cf03c6"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title SecuredTokenTransfer - Secure token transfer\n/// @author Richard Meissner - \ncontract SecuredTokenTransfer {\n /// @dev Transfers a token and returns if it was a success\n /// @param token Token that should be transferred\n /// @param receiver Receiver to whom the token should be transferred\n /// @param amount The amount of tokens that should be transferred\n function transferToken(\n address token,\n address receiver,\n uint256 amount\n ) internal returns (bool transferred) {\n // 0xa9059cbb - keccack(\"transfer(address,uint256)\")\n bytes memory data = abi.encodeWithSelector(0xa9059cbb, receiver, amount);\n // solhint-disable-next-line no-inline-assembly\n assembly {\n // We write the return value to scratch space.\n // See https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html#layout-in-memory\n let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n switch returndatasize()\n case 0 {\n transferred := success\n }\n case 0x20 {\n transferred := iszero(or(iszero(success), iszero(mload(0))))\n }\n default {\n transferred := 0\n }\n }\n }\n}\n","imports":[],"references":["GnosisSafe.sol"],"urls":[]},"common/SelfAuthorized.sol":{"checksum":{"algorithm":"md5","hash":"0x4c773e803298d5521ba60d7fc06df0b3"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title SelfAuthorized - authorizes current contract to perform actions\n/// @author Richard Meissner - \ncontract SelfAuthorized {\n function requireSelfCall() private view {\n require(msg.sender == address(this), \"GS031\");\n }\n\n modifier authorized() {\n // This is a function call as it minimized the bytecode size\n requireSelfCall();\n _;\n }\n}\n","imports":[],"references":["base/FallbackManager.sol","base/ModuleManager.sol","base/GuardManager.sol","base/OwnerManager.sol"],"urls":[]},"common/SignatureDecoder.sol":{"checksum":{"algorithm":"md5","hash":"0xc42a4ba77188fde1fff8281ea700f4fa"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title SignatureDecoder - Decodes signatures that a encoded as bytes\n/// @author Richard Meissner - \ncontract SignatureDecoder {\n /// @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`.\n /// @notice Make sure to peform a bounds check for @param pos, to avoid out of bounds access on @param signatures\n /// @param pos which signature to read. A prior bounds check of this parameter should be performed, to avoid out of bounds access\n /// @param signatures concatenated rsv signatures\n function signatureSplit(bytes memory signatures, uint256 pos)\n internal\n pure\n returns (\n uint8 v,\n bytes32 r,\n bytes32 s\n )\n {\n // The signature format is a compact form of:\n // {bytes32 r}{bytes32 s}{uint8 v}\n // Compact means, uint8 is not padded to 32 bytes.\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let signaturePos := mul(0x41, pos)\n r := mload(add(signatures, add(signaturePos, 0x20)))\n s := mload(add(signatures, add(signaturePos, 0x40)))\n // Here we are loading the last 32 bytes, including 31 bytes\n // of 's'. There is no 'mload8' to do this.\n //\n // 'byte' is not working due to the Solidity parser, so lets\n // use the second best option, 'and'\n v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff)\n }\n }\n}\n","imports":[],"references":["GnosisSafe.sol"],"urls":[]},"common/Singleton.sol":{"checksum":{"algorithm":"md5","hash":"0x334ef119459ae9a5487c6bd346752778"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title Singleton - Base for singleton contracts (should always be first super contract)\n/// This contract is tightly coupled to our proxy contract (see `proxies/GnosisSafeProxy.sol`)\n/// @author Richard Meissner - \ncontract Singleton {\n // singleton always needs to be first declared variable, to ensure that it is at the same location as in the Proxy contract.\n // It should also always be ensured that the address is stored alone (uses a full word)\n address private singleton;\n}\n","imports":[],"references":["GnosisSafe.sol"],"urls":[]},"common/StorageAccessible.sol":{"checksum":{"algorithm":"md5","hash":"0xb2008c1f9b2cb3bbda6710f462ae7c1f"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title StorageAccessible - generic base contract that allows callers to access all internal storage.\n/// @notice See https://github.com/gnosis/util-contracts/blob/bb5fe5fb5df6d8400998094fb1b32a178a47c3a1/contracts/StorageAccessible.sol\ncontract StorageAccessible {\n /**\n * @dev Reads `length` bytes of storage in the currents contract\n * @param offset - the offset in the current contract's storage in words to start reading from\n * @param length - the number of words (32 bytes) of data to read\n * @return the bytes that were read.\n */\n function getStorageAt(uint256 offset, uint256 length) public view returns (bytes memory) {\n bytes memory result = new bytes(length * 32);\n for (uint256 index = 0; index < length; index++) {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let word := sload(add(offset, index))\n mstore(add(add(result, 0x20), mul(index, 0x20)), word)\n }\n }\n return result;\n }\n\n /**\n * @dev Performs a delegetecall on a targetContract in the context of self.\n * Internally reverts execution to avoid side effects (making it static).\n *\n * This method reverts with data equal to `abi.encode(bool(success), bytes(response))`.\n * Specifically, the `returndata` after a call to this method will be:\n * `success:bool || response.length:uint256 || response:bytes`.\n *\n * @param targetContract Address of the contract containing the code to execute.\n * @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments).\n */\n function simulateAndRevert(address targetContract, bytes memory calldataPayload) external {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let success := delegatecall(gas(), targetContract, add(calldataPayload, 0x20), mload(calldataPayload), 0, 0)\n\n mstore(0x00, success)\n mstore(0x20, returndatasize())\n returndatacopy(0x40, 0, returndatasize())\n revert(0, add(returndatasize(), 0x40))\n }\n }\n}\n","imports":[],"references":["GnosisSafe.sol"],"urls":[]},"examples/guards/DebugTransactionGuard.sol":{"checksum":{"algorithm":"md5","hash":"0x100835d618745d8fda08dcea0bf8d877"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../../common/Enum.sol\";\nimport \"../../base/GuardManager.sol\";\nimport \"../../GnosisSafe.sol\";\n\n/// @title Debug Transaction Guard - A guard that will emit events with extended information.\n/// @notice This guard is only meant as a development tool and example\n/// @author Richard Meissner - \ncontract DebugTransactionGuard is Guard {\n // solhint-disable-next-line payable-fallback\n fallback() external {\n // We don't revert on fallback to avoid issues in case of a Safe upgrade\n // E.g. The expected check method might change and then the Safe would be locked.\n }\n\n event TransactionDetails(\n address indexed safe,\n bytes32 indexed txHash,\n address to,\n uint256 value,\n bytes data,\n Enum.Operation operation,\n uint256 safeTxGas,\n bool usesRefund,\n uint256 nonce\n );\n\n event GasUsage(address indexed safe, bytes32 indexed txHash, uint256 indexed nonce, bool success);\n\n mapping(bytes32 => uint256) public txNonces;\n\n function checkTransaction(\n address to,\n uint256 value,\n bytes memory data,\n Enum.Operation operation,\n uint256 safeTxGas,\n uint256 baseGas,\n uint256 gasPrice,\n address gasToken,\n // solhint-disable-next-line no-unused-vars\n address payable refundReceiver,\n bytes memory,\n address\n ) external override {\n uint256 nonce;\n bytes32 txHash;\n {\n GnosisSafe safe = GnosisSafe(payable(msg.sender));\n nonce = safe.nonce() - 1;\n txHash = safe.getTransactionHash(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, nonce);\n }\n emit TransactionDetails(msg.sender, txHash, to, value, data, operation, safeTxGas, gasPrice > 0, nonce);\n txNonces[txHash] = nonce;\n }\n\n function checkAfterExecution(bytes32 txHash, bool success) external override {\n uint256 nonce = txNonces[txHash];\n require(nonce != 0, \"Could not get nonce\");\n txNonces[txHash] = 0;\n emit GasUsage(msg.sender, txHash, nonce, success);\n }\n}\n","imports":["base/GuardManager.sol","common/Enum.sol","GnosisSafe.sol"],"references":[],"urls":[]},"examples/guards/DelegateCallTransactionGuard.sol":{"checksum":{"algorithm":"md5","hash":"0xd081de6d5a9f2152aab41c5f352e9fb7"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../../common/Enum.sol\";\nimport \"../../base/GuardManager.sol\";\nimport \"../../GnosisSafe.sol\";\n\ncontract DelegateCallTransactionGuard is Guard {\n address public immutable allowedTarget;\n\n constructor(address target) {\n allowedTarget = target;\n }\n\n // solhint-disable-next-line payable-fallback\n fallback() external {\n // We don't revert on fallback to avoid issues in case of a Safe upgrade\n // E.g. The expected check method might change and then the Safe would be locked.\n }\n\n function checkTransaction(\n address to,\n uint256,\n bytes memory,\n Enum.Operation operation,\n uint256,\n uint256,\n uint256,\n address,\n // solhint-disable-next-line no-unused-vars\n address payable,\n bytes memory,\n address\n ) external view override {\n require(operation != Enum.Operation.DelegateCall || to == allowedTarget, \"This call is restricted\");\n }\n\n function checkAfterExecution(bytes32, bool) external view override {}\n}\n","imports":["base/GuardManager.sol","common/Enum.sol","GnosisSafe.sol"],"references":[],"urls":[]},"examples/guards/ReentrancyTransactionGuard.sol":{"checksum":{"algorithm":"md5","hash":"0xfe44bce284ac28c4c0c4006b79279d33"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../../common/Enum.sol\";\nimport \"../../base/GuardManager.sol\";\nimport \"../../GnosisSafe.sol\";\n\ncontract ReentrancyTransactionGuard is Guard {\n bytes32 internal constant GUARD_STORAGE_SLOT = keccak256(\"reentrancy_guard.guard.struct\");\n\n struct GuardValue {\n bool active;\n }\n\n // solhint-disable-next-line payable-fallback\n fallback() external {\n // We don't revert on fallback to avoid issues in case of a Safe upgrade\n // E.g. The expected check method might change and then the Safe would be locked.\n }\n\n function getGuard() internal pure returns (GuardValue storage guard) {\n bytes32 slot = GUARD_STORAGE_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n guard.slot := slot\n }\n }\n\n function checkTransaction(\n address,\n uint256,\n bytes memory,\n Enum.Operation,\n uint256,\n uint256,\n uint256,\n address,\n // solhint-disable-next-line no-unused-vars\n address payable,\n bytes memory,\n address\n ) external override {\n GuardValue storage guard = getGuard();\n require(!guard.active, \"Reentrancy detected\");\n guard.active = true;\n }\n\n function checkAfterExecution(bytes32, bool) external override {\n getGuard().active = false;\n }\n}\n","imports":["base/GuardManager.sol","common/Enum.sol","GnosisSafe.sol"],"references":[],"urls":[]},"examples/libraries/GnosisSafeStorage.sol":{"checksum":{"algorithm":"md5","hash":"0x62d516f9db15e3e52638095ba56f2fed"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title GnosisSafeStorage - Storage layout of the Safe contracts to be used in libraries\n/// @author Richard Meissner - \ncontract GnosisSafeStorage {\n // From /common/Singleton.sol\n address internal singleton;\n // From /common/ModuleManager.sol\n mapping(address => address) internal modules;\n // From /common/OwnerManager.sol\n mapping(address => address) internal owners;\n uint256 internal ownerCount;\n uint256 internal threshold;\n\n // From /GnosisSafe.sol\n bytes32 internal nonce;\n bytes32 internal domainSeparator;\n mapping(bytes32 => uint256) internal signedMessages;\n mapping(address => mapping(bytes32 => uint256)) internal approvedHashes;\n}\n","imports":[],"references":["examples/libraries/SignMessage.sol","examples/libraries/Migrate_1_3_0_to_1_2_0.sol"],"urls":[]},"examples/libraries/Migrate_1_3_0_to_1_2_0.sol":{"checksum":{"algorithm":"md5","hash":"0x8439c558ce05905726ab5e23e0feb1ef"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\nimport \"./GnosisSafeStorage.sol\";\n\n/// @title Migration - migrates a Safe contract from 1.3.0 to 1.2.0\n/// @author Richard Meissner - \ncontract Migration is GnosisSafeStorage {\n bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749;\n\n address public immutable migrationSingleton;\n address public immutable safe120Singleton;\n\n constructor(address targetSingleton) {\n require(targetSingleton != address(0), \"Invalid singleton address provided\");\n safe120Singleton = targetSingleton;\n migrationSingleton = address(this);\n }\n\n event ChangedMasterCopy(address singleton);\n\n bytes32 private guard;\n\n /// @dev Allows to migrate the contract. This can only be called via a delegatecall.\n function migrate() public {\n require(address(this) != migrationSingleton, \"Migration should only be called via delegatecall\");\n // Master copy address cannot be null.\n singleton = safe120Singleton;\n domainSeparator = keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, this));\n emit ChangedMasterCopy(singleton);\n }\n}\n","imports":["examples/libraries/GnosisSafeStorage.sol"],"references":[],"urls":[]},"examples/libraries/SignMessage.sol":{"checksum":{"algorithm":"md5","hash":"0x38e750ef832fbfbe0f135e2bc16f0210"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"./GnosisSafeStorage.sol\";\nimport \"../../GnosisSafe.sol\";\n\n/// @title SignMessageLib - Allows to set an entry in the signedMessages\n/// @author Richard Meissner - \ncontract SignMessageLib is GnosisSafeStorage {\n //keccak256(\n // \"SafeMessage(bytes message)\"\n //);\n bytes32 private constant SAFE_MSG_TYPEHASH = 0x60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca;\n\n event SignMsg(bytes32 indexed msgHash);\n\n /// @dev Marks a message as signed, so that it can be used with EIP-1271\n /// @notice Marks a message (`_data`) as signed.\n /// @param _data Arbitrary length data that should be marked as signed on the behalf of address(this)\n function signMessage(bytes calldata _data) external {\n bytes32 msgHash = getMessageHash(_data);\n signedMessages[msgHash] = 1;\n emit SignMsg(msgHash);\n }\n\n /// @dev Returns hash of a message that can be signed by owners.\n /// @param message Message that should be hashed\n /// @return Message hash.\n function getMessageHash(bytes memory message) public view returns (bytes32) {\n bytes32 safeMessageHash = keccak256(abi.encode(SAFE_MSG_TYPEHASH, keccak256(message)));\n return\n keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), GnosisSafe(payable(address(this))).domainSeparator(), safeMessageHash));\n }\n}\n","imports":["GnosisSafe.sol","examples/libraries/GnosisSafeStorage.sol"],"references":[],"urls":[]},"external/GnosisSafeMath.sol":{"checksum":{"algorithm":"md5","hash":"0x0b98e5ff6cd364731017082744cf5706"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/**\n * @title GnosisSafeMath\n * @dev Math operations with safety checks that revert on error\n * Renamed from SafeMath to GnosisSafeMath to avoid conflicts\n * TODO: remove once open zeppelin update to solc 0.5.0\n */\nlibrary GnosisSafeMath {\n /**\n * @dev Multiplies two numbers, reverts on overflow.\n */\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\n // benefit is lost if 'b' is also tested.\n // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522\n if (a == 0) {\n return 0;\n }\n\n uint256 c = a * b;\n require(c / a == b);\n\n return c;\n }\n\n /**\n * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).\n */\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n require(b <= a);\n uint256 c = a - b;\n\n return c;\n }\n\n /**\n * @dev Adds two numbers, reverts on overflow.\n */\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 c = a + b;\n require(c >= a);\n\n return c;\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a >= b ? a : b;\n }\n}\n","imports":[],"references":["test/ERC1155Token.sol","GnosisSafe.sol"],"urls":[]},"handler/CompatibilityFallbackHandler.sol":{"checksum":{"algorithm":"md5","hash":"0x0663cc338351b228ee368891a754198a"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"./DefaultCallbackHandler.sol\";\nimport \"../interfaces/ISignatureValidator.sol\";\nimport \"../GnosisSafe.sol\";\n\n/// @title Compatibility Fallback Handler - fallback handler to provider compatibility between pre 1.3.0 and 1.3.0+ Safe contracts\n/// @author Richard Meissner - \ncontract CompatibilityFallbackHandler is DefaultCallbackHandler, ISignatureValidator {\n //keccak256(\n // \"SafeMessage(bytes message)\"\n //);\n bytes32 private constant SAFE_MSG_TYPEHASH = 0x60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca;\n\n bytes4 internal constant SIMULATE_SELECTOR = bytes4(keccak256(\"simulate(address,bytes)\"));\n\n address internal constant SENTINEL_MODULES = address(0x1);\n bytes4 internal constant UPDATED_MAGIC_VALUE = 0x1626ba7e;\n\n /**\n * Implementation of ISignatureValidator (see `interfaces/ISignatureValidator.sol`)\n * @dev Should return whether the signature provided is valid for the provided data.\n * @param _data Arbitrary length data signed on the behalf of address(msg.sender)\n * @param _signature Signature byte array associated with _data\n * @return a bool upon valid or invalid signature with corresponding _data\n */\n function isValidSignature(bytes calldata _data, bytes calldata _signature) public view override returns (bytes4) {\n // Caller should be a Safe\n GnosisSafe safe = GnosisSafe(payable(msg.sender));\n bytes32 messageHash = getMessageHashForSafe(safe, _data);\n if (_signature.length == 0) {\n require(safe.signedMessages(messageHash) != 0, \"Hash not approved\");\n } else {\n safe.checkSignatures(messageHash, _data, _signature);\n }\n return EIP1271_MAGIC_VALUE;\n }\n\n /// @dev Returns hash of a message that can be signed by owners.\n /// @param message Message that should be hashed\n /// @return Message hash.\n function getMessageHash(bytes memory message) public view returns (bytes32) {\n return getMessageHashForSafe(GnosisSafe(payable(msg.sender)), message);\n }\n\n /// @dev Returns hash of a message that can be signed by owners.\n /// @param safe Safe to which the message is targeted\n /// @param message Message that should be hashed\n /// @return Message hash.\n function getMessageHashForSafe(GnosisSafe safe, bytes memory message) public view returns (bytes32) {\n bytes32 safeMessageHash = keccak256(abi.encode(SAFE_MSG_TYPEHASH, keccak256(message)));\n return keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), safe.domainSeparator(), safeMessageHash));\n }\n\n /**\n * Implementation of updated EIP-1271\n * @dev Should return whether the signature provided is valid for the provided data.\n * The save does not implement the interface since `checkSignatures` is not a view method.\n * The method will not perform any state changes (see parameters of `checkSignatures`)\n * @param _dataHash Hash of the data signed on the behalf of address(msg.sender)\n * @param _signature Signature byte array associated with _dataHash\n * @return a bool upon valid or invalid signature with corresponding _dataHash\n * @notice See https://github.com/gnosis/util-contracts/blob/bb5fe5fb5df6d8400998094fb1b32a178a47c3a1/contracts/StorageAccessible.sol\n */\n function isValidSignature(bytes32 _dataHash, bytes calldata _signature) external view returns (bytes4) {\n ISignatureValidator validator = ISignatureValidator(msg.sender);\n bytes4 value = validator.isValidSignature(abi.encode(_dataHash), _signature);\n return (value == EIP1271_MAGIC_VALUE) ? UPDATED_MAGIC_VALUE : bytes4(0);\n }\n\n /// @dev Returns array of first 10 modules.\n /// @return Array of modules.\n function getModules() external view returns (address[] memory) {\n // Caller should be a Safe\n GnosisSafe safe = GnosisSafe(payable(msg.sender));\n (address[] memory array, ) = safe.getModulesPaginated(SENTINEL_MODULES, 10);\n return array;\n }\n\n /**\n * @dev Performs a delegetecall on a targetContract in the context of self.\n * Internally reverts execution to avoid side effects (making it static). Catches revert and returns encoded result as bytes.\n * @param targetContract Address of the contract containing the code to execute.\n * @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments).\n */\n function simulate(address targetContract, bytes calldata calldataPayload) external returns (bytes memory response) {\n // Suppress compiler warnings about not using parameters, while allowing\n // parameters to keep names for documentation purposes. This does not\n // generate code.\n targetContract;\n calldataPayload;\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let internalCalldata := mload(0x40)\n // Store `simulateAndRevert.selector`.\n // String representation is used to force right padding\n mstore(internalCalldata, \"\\xb4\\xfa\\xba\\x09\")\n // Abuse the fact that both this and the internal methods have the\n // same signature, and differ only in symbol name (and therefore,\n // selector) and copy calldata directly. This saves us approximately\n // 250 bytes of code and 300 gas at runtime over the\n // `abi.encodeWithSelector` builtin.\n calldatacopy(add(internalCalldata, 0x04), 0x04, sub(calldatasize(), 0x04))\n\n // `pop` is required here by the compiler, as top level expressions\n // can't have return values in inline assembly. `call` typically\n // returns a 0 or 1 value indicated whether or not it reverted, but\n // since we know it will always revert, we can safely ignore it.\n pop(\n call(\n gas(),\n // address() has been changed to caller() to use the implementation of the Safe\n caller(),\n 0,\n internalCalldata,\n calldatasize(),\n // The `simulateAndRevert` call always reverts, and\n // instead encodes whether or not it was successful in the return\n // data. The first 32-byte word of the return data contains the\n // `success` value, so write it to memory address 0x00 (which is\n // reserved Solidity scratch space and OK to use).\n 0x00,\n 0x20\n )\n )\n\n // Allocate and copy the response bytes, making sure to increment\n // the free memory pointer accordingly (in case this method is\n // called as an internal function). The remaining `returndata[0x20:]`\n // contains the ABI encoded response bytes, so we can just write it\n // as is to memory.\n let responseSize := sub(returndatasize(), 0x20)\n response := mload(0x40)\n mstore(0x40, add(response, responseSize))\n returndatacopy(response, 0x20, responseSize)\n\n if iszero(mload(0x00)) {\n revert(add(response, 0x20), mload(response))\n }\n }\n }\n}\n","imports":["interfaces/ISignatureValidator.sol","GnosisSafe.sol","handler/DefaultCallbackHandler.sol"],"references":[],"urls":[]},"handler/DefaultCallbackHandler.sol":{"checksum":{"algorithm":"md5","hash":"0x5a4fbd56521d514530c7f6206e8eca89"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../interfaces/ERC1155TokenReceiver.sol\";\nimport \"../interfaces/ERC721TokenReceiver.sol\";\nimport \"../interfaces/ERC777TokensRecipient.sol\";\nimport \"../interfaces/IERC165.sol\";\n\n/// @title Default Callback Handler - returns true for known token callbacks\n/// @author Richard Meissner - \ncontract DefaultCallbackHandler is ERC1155TokenReceiver, ERC777TokensRecipient, ERC721TokenReceiver, IERC165 {\n string public constant NAME = \"Default Callback Handler\";\n string public constant VERSION = \"1.0.0\";\n\n function onERC1155Received(\n address,\n address,\n uint256,\n uint256,\n bytes calldata\n ) external pure override returns (bytes4) {\n return 0xf23a6e61;\n }\n\n function onERC1155BatchReceived(\n address,\n address,\n uint256[] calldata,\n uint256[] calldata,\n bytes calldata\n ) external pure override returns (bytes4) {\n return 0xbc197c81;\n }\n\n function onERC721Received(\n address,\n address,\n uint256,\n bytes calldata\n ) external pure override returns (bytes4) {\n return 0x150b7a02;\n }\n\n function tokensReceived(\n address,\n address,\n address,\n uint256,\n bytes calldata,\n bytes calldata\n ) external pure override {\n // We implement this for completeness, doesn't really have any value\n }\n\n function supportsInterface(bytes4 interfaceId) external view virtual override returns (bool) {\n return\n interfaceId == type(ERC1155TokenReceiver).interfaceId ||\n interfaceId == type(ERC721TokenReceiver).interfaceId ||\n interfaceId == type(IERC165).interfaceId;\n }\n}\n","imports":["interfaces/IERC165.sol","interfaces/ERC777TokensRecipient.sol","interfaces/ERC1155TokenReceiver.sol","interfaces/ERC721TokenReceiver.sol"],"references":["handler/CompatibilityFallbackHandler.sol"],"urls":[]},"handler/HandlerContext.sol":{"checksum":{"algorithm":"md5","hash":"0x600738543f914c11e793b0fef5e705c8"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title Handler Context - allows to extract calling context\n/// @author Richard Meissner - \n/// @notice based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f8cc8b844a9f92f63dc55aa581f7d643a1bc5ac1/contracts/metatx/ERC2771Context.sol\ncontract HandlerContext {\n // This function does not rely on a trusted forwarder. Use the returned value only to check information against the calling manager.\n /// @notice This is only reliable in combination with a FallbackManager that supports this (e.g. Safe contract >=1.3.0).\n /// When using this functionality make sure that the linked _manager (aka msg.sender) supports this.\n function _msgSender() internal pure returns (address sender) {\n // The assembly code is more direct than the Solidity version using `abi.decode`.\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sender := shr(96, calldataload(sub(calldatasize(), 20)))\n }\n }\n\n // Function do differentiate more clearly between msg.sender and the calling manager\n function _manager() internal view returns (address) {\n return msg.sender;\n }\n}\n","imports":[],"references":["test/TestHandler.sol"],"urls":[]},"interfaces/ERC1155TokenReceiver.sol":{"checksum":{"algorithm":"md5","hash":"0x1d63defb083613ac870a97490f3635ba"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/**\n Note: The ERC-165 identifier for this interface is 0x4e2312e0.\n*/\ninterface ERC1155TokenReceiver {\n /**\n @notice Handle the receipt of a single ERC1155 token type.\n @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. \n This function MUST return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))` (i.e. 0xf23a6e61) if it accepts the transfer.\n This function MUST revert if it rejects the transfer.\n Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n @param _operator The address which initiated the transfer (i.e. msg.sender)\n @param _from The address which previously owned the token\n @param _id The ID of the token being transferred\n @param _value The amount of tokens being transferred\n @param _data Additional data with no specified format\n @return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))`\n */\n function onERC1155Received(\n address _operator,\n address _from,\n uint256 _id,\n uint256 _value,\n bytes calldata _data\n ) external returns (bytes4);\n\n /**\n @notice Handle the receipt of multiple ERC1155 token types.\n @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. \n This function MUST return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))` (i.e. 0xbc197c81) if it accepts the transfer(s).\n This function MUST revert if it rejects the transfer(s).\n Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n @param _operator The address which initiated the batch transfer (i.e. msg.sender)\n @param _from The address which previously owned the token\n @param _ids An array containing ids of each token being transferred (order and length must match _values array)\n @param _values An array containing amounts of each token being transferred (order and length must match _ids array)\n @param _data Additional data with no specified format\n @return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))`\n */\n function onERC1155BatchReceived(\n address _operator,\n address _from,\n uint256[] calldata _ids,\n uint256[] calldata _values,\n bytes calldata _data\n ) external returns (bytes4);\n}\n","imports":[],"references":["test/ERC1155Token.sol","handler/DefaultCallbackHandler.sol"],"urls":[]},"interfaces/ERC721TokenReceiver.sol":{"checksum":{"algorithm":"md5","hash":"0xffac82dd785c225e65d296c1c6596d5a"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.\ninterface ERC721TokenReceiver {\n /// @notice Handle the receipt of an NFT\n /// @dev The ERC721 smart contract calls this function on the recipient\n /// after a `transfer`. This function MAY throw to revert and reject the\n /// transfer. Return of other than the magic value MUST result in the\n /// transaction being reverted.\n /// Note: the contract address is always the message sender.\n /// @param _operator The address which called `safeTransferFrom` function\n /// @param _from The address which previously owned the token\n /// @param _tokenId The NFT identifier which is being transferred\n /// @param _data Additional data with no specified format\n /// @return `bytes4(keccak256(\"onERC721Received(address,address,uint256,bytes)\"))`\n /// unless throwing\n function onERC721Received(\n address _operator,\n address _from,\n uint256 _tokenId,\n bytes calldata _data\n ) external returns (bytes4);\n}\n","imports":[],"references":["handler/DefaultCallbackHandler.sol"],"urls":[]},"interfaces/ERC777TokensRecipient.sol":{"checksum":{"algorithm":"md5","hash":"0x38601f7aa7b8c19d689915aeda071ef0"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\ninterface ERC777TokensRecipient {\n function tokensReceived(\n address operator,\n address from,\n address to,\n uint256 amount,\n bytes calldata data,\n bytes calldata operatorData\n ) external;\n}\n","imports":[],"references":["handler/DefaultCallbackHandler.sol"],"urls":[]},"interfaces/IERC165.sol":{"checksum":{"algorithm":"md5","hash":"0x2d418698dff7323e4f199ff8633d1a3a"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @notice More details at https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/introspection/IERC165.sol\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n","imports":[],"references":["handler/DefaultCallbackHandler.sol"],"urls":[]},"interfaces/ISignatureValidator.sol":{"checksum":{"algorithm":"md5","hash":"0x45be433b79d00dd9f3b8047489c3ff80"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\ncontract ISignatureValidatorConstants {\n // bytes4(keccak256(\"isValidSignature(bytes,bytes)\")\n bytes4 internal constant EIP1271_MAGIC_VALUE = 0x20c13b0b;\n}\n\nabstract contract ISignatureValidator is ISignatureValidatorConstants {\n /**\n * @dev Should return whether the signature provided is valid for the provided data\n * @param _data Arbitrary length data signed on the behalf of address(this)\n * @param _signature Signature byte array associated with _data\n *\n * MUST return the bytes4 magic value 0x20c13b0b when function passes.\n * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)\n * MUST allow external calls\n */\n function isValidSignature(bytes memory _data, bytes memory _signature) public view virtual returns (bytes4);\n}\n","imports":[],"references":["handler/CompatibilityFallbackHandler.sol","GnosisSafe.sol"],"urls":[]},"interfaces/ViewStorageAccessible.sol":{"checksum":{"algorithm":"md5","hash":"0x37eefaaae55281b6b8ef1a856078b00c"},"content":"pragma solidity >=0.5.0 <0.6.0;\n\n/// @title ViewStorageAccessible - Interface on top of StorageAccessible base class to allow simulations from view functions\n/// @notice Adjusted version of https://github.com/gnosis/util-contracts/blob/3db1e531cb243a48ea91c60a800d537c1000612a/contracts/StorageAccessible.sol\ninterface ViewStorageAccessible {\n /**\n * @dev Same as `simulate` on StorageAccessible. Marked as view so that it can be called from external contracts\n * that want to run simulations from within view functions. Will revert if the invoked simulation attempts to change state.\n */\n function simulate(address targetContract, bytes calldata calldataPayload) external view returns (bytes memory);\n}\n","imports":[],"references":[],"urls":[]},"libraries/CreateCall.sol":{"checksum":{"algorithm":"md5","hash":"0x5b39e17ce475af78bc0a7cbdc1472c32"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title Create Call - Allows to use the different create opcodes to deploy a contract\n/// @author Richard Meissner - \ncontract CreateCall {\n event ContractCreation(address newContract);\n\n function performCreate2(\n uint256 value,\n bytes memory deploymentData,\n bytes32 salt\n ) public returns (address newContract) {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n newContract := create2(value, add(0x20, deploymentData), mload(deploymentData), salt)\n }\n require(newContract != address(0), \"Could not deploy contract\");\n emit ContractCreation(newContract);\n }\n\n function performCreate(uint256 value, bytes memory deploymentData) public returns (address newContract) {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n newContract := create(value, add(deploymentData, 0x20), mload(deploymentData))\n }\n require(newContract != address(0), \"Could not deploy contract\");\n emit ContractCreation(newContract);\n }\n}\n","imports":[],"references":[],"urls":[]},"libraries/MultiSend.sol":{"checksum":{"algorithm":"md5","hash":"0xa7b41ac2932ddd3c1d11c7076ace270d"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title Multi Send - Allows to batch multiple transactions into one.\n/// @author Nick Dodson - \n/// @author Gon\u00e7alo S\u00e1 - \n/// @author Stefan George - \n/// @author Richard Meissner - \ncontract MultiSend {\n address private immutable multisendSingleton;\n\n constructor() {\n multisendSingleton = address(this);\n }\n\n /// @dev Sends multiple transactions and reverts all if one fails.\n /// @param transactions Encoded transactions. Each transaction is encoded as a packed bytes of\n /// operation as a uint8 with 0 for a call or 1 for a delegatecall (=> 1 byte),\n /// to as a address (=> 20 bytes),\n /// value as a uint256 (=> 32 bytes),\n /// data length as a uint256 (=> 32 bytes),\n /// data as bytes.\n /// see abi.encodePacked for more information on packed encoding\n /// @notice This method is payable as delegatecalls keep the msg.value from the previous call\n /// If the calling method (e.g. execTransaction) received ETH this would revert otherwise\n function multiSend(bytes memory transactions) public payable {\n require(address(this) != multisendSingleton, \"MultiSend should only be called via delegatecall\");\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let length := mload(transactions)\n let i := 0x20\n for {\n // Pre block is not used in \"while mode\"\n } lt(i, length) {\n // Post block is not used in \"while mode\"\n } {\n // First byte of the data is the operation.\n // We shift by 248 bits (256 - 8 [operation byte]) it right since mload will always load 32 bytes (a word).\n // This will also zero out unused data.\n let operation := shr(0xf8, mload(add(transactions, i)))\n // We offset the load address by 1 byte (operation byte)\n // We shift it right by 96 bits (256 - 160 [20 address bytes]) to right-align the data and zero out unused data.\n let to := shr(0x60, mload(add(transactions, add(i, 0x01))))\n // We offset the load address by 21 byte (operation byte + 20 address bytes)\n let value := mload(add(transactions, add(i, 0x15)))\n // We offset the load address by 53 byte (operation byte + 20 address bytes + 32 value bytes)\n let dataLength := mload(add(transactions, add(i, 0x35)))\n // We offset the load address by 85 byte (operation byte + 20 address bytes + 32 value bytes + 32 data length bytes)\n let data := add(transactions, add(i, 0x55))\n let success := 0\n switch operation\n case 0 {\n success := call(gas(), to, value, data, dataLength, 0, 0)\n }\n case 1 {\n success := delegatecall(gas(), to, data, dataLength, 0, 0)\n }\n if eq(success, 0) {\n revert(0, 0)\n }\n // Next entry starts at 85 byte + data length\n i := add(i, add(0x55, dataLength))\n }\n }\n }\n}\n","imports":[],"references":[],"urls":[]},"libraries/MultiSendCallOnly.sol":{"checksum":{"algorithm":"md5","hash":"0x121202ac209e67a2a6f0cb22f0133a0a"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title Multi Send Call Only - Allows to batch multiple transactions into one, but only calls\n/// @author Stefan George - \n/// @author Richard Meissner - \n/// @notice The guard logic is not required here as this contract doesn't support nested delegate calls\ncontract MultiSendCallOnly {\n /// @dev Sends multiple transactions and reverts all if one fails.\n /// @param transactions Encoded transactions. Each transaction is encoded as a packed bytes of\n /// operation has to be uint8(0) in this version (=> 1 byte),\n /// to as a address (=> 20 bytes),\n /// value as a uint256 (=> 32 bytes),\n /// data length as a uint256 (=> 32 bytes),\n /// data as bytes.\n /// see abi.encodePacked for more information on packed encoding\n /// @notice The code is for most part the same as the normal MultiSend (to keep compatibility),\n /// but reverts if a transaction tries to use a delegatecall.\n /// @notice This method is payable as delegatecalls keep the msg.value from the previous call\n /// If the calling method (e.g. execTransaction) received ETH this would revert otherwise\n function multiSend(bytes memory transactions) public payable {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let length := mload(transactions)\n let i := 0x20\n for {\n // Pre block is not used in \"while mode\"\n } lt(i, length) {\n // Post block is not used in \"while mode\"\n } {\n // First byte of the data is the operation.\n // We shift by 248 bits (256 - 8 [operation byte]) it right since mload will always load 32 bytes (a word).\n // This will also zero out unused data.\n let operation := shr(0xf8, mload(add(transactions, i)))\n // We offset the load address by 1 byte (operation byte)\n // We shift it right by 96 bits (256 - 160 [20 address bytes]) to right-align the data and zero out unused data.\n let to := shr(0x60, mload(add(transactions, add(i, 0x01))))\n // We offset the load address by 21 byte (operation byte + 20 address bytes)\n let value := mload(add(transactions, add(i, 0x15)))\n // We offset the load address by 53 byte (operation byte + 20 address bytes + 32 value bytes)\n let dataLength := mload(add(transactions, add(i, 0x35)))\n // We offset the load address by 85 byte (operation byte + 20 address bytes + 32 value bytes + 32 data length bytes)\n let data := add(transactions, add(i, 0x55))\n let success := 0\n switch operation\n case 0 {\n success := call(gas(), to, value, data, dataLength, 0, 0)\n }\n // This version does not allow delegatecalls\n case 1 {\n revert(0, 0)\n }\n if eq(success, 0) {\n revert(0, 0)\n }\n // Next entry starts at 85 byte + data length\n i := add(i, add(0x55, dataLength))\n }\n }\n }\n}\n","imports":[],"references":[],"urls":[]},"proxies/GnosisSafeProxy.sol":{"checksum":{"algorithm":"md5","hash":"0xeba113f6899fe142a2081f63aded8c83"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\n/// @title IProxy - Helper interface to access masterCopy of the Proxy on-chain\n/// @author Richard Meissner - \ninterface IProxy {\n function masterCopy() external view returns (address);\n}\n\n/// @title GnosisSafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract.\n/// @author Stefan George - \n/// @author Richard Meissner - \ncontract GnosisSafeProxy {\n // singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated.\n // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt`\n address internal singleton;\n\n /// @dev Constructor function sets address of singleton contract.\n /// @param _singleton Singleton address.\n constructor(address _singleton) {\n require(_singleton != address(0), \"Invalid singleton address provided\");\n singleton = _singleton;\n }\n\n /// @dev Fallback function forwards all transactions and returns all received return data.\n fallback() external payable {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)\n // 0xa619486e == keccak(\"masterCopy()\"). The value is right padded to 32-bytes with 0s\n if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) {\n mstore(0, _singleton)\n return(0, 0x20)\n }\n calldatacopy(0, 0, calldatasize())\n let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)\n returndatacopy(0, 0, returndatasize())\n if eq(success, 0) {\n revert(0, returndatasize())\n }\n return(0, returndatasize())\n }\n }\n}\n","imports":[],"references":["proxies/GnosisSafeProxyFactory.sol","proxies/IProxyCreationCallback.sol"],"urls":[]},"proxies/GnosisSafeProxyFactory.sol":{"checksum":{"algorithm":"md5","hash":"0x1c6f2708f7097b32396e492b6d545f07"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"./GnosisSafeProxy.sol\";\nimport \"./IProxyCreationCallback.sol\";\n\n/// @title Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction.\n/// @author Stefan George - \ncontract GnosisSafeProxyFactory {\n event ProxyCreation(GnosisSafeProxy proxy, address singleton);\n\n /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.\n /// @param singleton Address of singleton contract.\n /// @param data Payload for message call sent to new proxy contract.\n function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy) {\n proxy = new GnosisSafeProxy(singleton);\n if (data.length > 0)\n // solhint-disable-next-line no-inline-assembly\n assembly {\n if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) {\n revert(0, 0)\n }\n }\n emit ProxyCreation(proxy, singleton);\n }\n\n /// @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed.\n function proxyRuntimeCode() public pure returns (bytes memory) {\n return type(GnosisSafeProxy).runtimeCode;\n }\n\n /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address.\n function proxyCreationCode() public pure returns (bytes memory) {\n return type(GnosisSafeProxy).creationCode;\n }\n\n /// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer.\n /// This method is only meant as an utility to be called from other methods\n /// @param _singleton Address of singleton contract.\n /// @param initializer Payload for message call sent to new proxy contract.\n /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.\n function deployProxyWithNonce(\n address _singleton,\n bytes memory initializer,\n uint256 saltNonce\n ) internal returns (GnosisSafeProxy proxy) {\n // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it\n bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));\n bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton)));\n // solhint-disable-next-line no-inline-assembly\n assembly {\n proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)\n }\n require(address(proxy) != address(0), \"Create2 call failed\");\n }\n\n /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.\n /// @param _singleton Address of singleton contract.\n /// @param initializer Payload for message call sent to new proxy contract.\n /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.\n function createProxyWithNonce(\n address _singleton,\n bytes memory initializer,\n uint256 saltNonce\n ) public returns (GnosisSafeProxy proxy) {\n proxy = deployProxyWithNonce(_singleton, initializer, saltNonce);\n if (initializer.length > 0)\n // solhint-disable-next-line no-inline-assembly\n assembly {\n if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) {\n revert(0, 0)\n }\n }\n emit ProxyCreation(proxy, _singleton);\n }\n\n /// @dev Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction\n /// @param _singleton Address of singleton contract.\n /// @param initializer Payload for message call sent to new proxy contract.\n /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.\n /// @param callback Callback that will be invoced after the new proxy contract has been successfully deployed and initialized.\n function createProxyWithCallback(\n address _singleton,\n bytes memory initializer,\n uint256 saltNonce,\n IProxyCreationCallback callback\n ) public returns (GnosisSafeProxy proxy) {\n uint256 saltNonceWithCallback = uint256(keccak256(abi.encodePacked(saltNonce, callback)));\n proxy = createProxyWithNonce(_singleton, initializer, saltNonceWithCallback);\n if (address(callback) != address(0)) callback.proxyCreated(proxy, _singleton, initializer, saltNonce);\n }\n\n /// @dev Allows to get the address for a new proxy contact created via `createProxyWithNonce`\n /// This method is only meant for address calculation purpose when you use an initializer that would revert,\n /// therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory.\n /// @param _singleton Address of singleton contract.\n /// @param initializer Payload for message call sent to new proxy contract.\n /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.\n function calculateCreateProxyWithNonceAddress(\n address _singleton,\n bytes calldata initializer,\n uint256 saltNonce\n ) external returns (GnosisSafeProxy proxy) {\n proxy = deployProxyWithNonce(_singleton, initializer, saltNonce);\n revert(string(abi.encodePacked(proxy)));\n }\n}\n","imports":["proxies/GnosisSafeProxy.sol","proxies/IProxyCreationCallback.sol"],"references":[],"urls":[]},"proxies/IProxyCreationCallback.sol":{"checksum":{"algorithm":"md5","hash":"0xe73c0b8a985873c817469f5c6038e233"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\nimport \"./GnosisSafeProxy.sol\";\n\ninterface IProxyCreationCallback {\n function proxyCreated(\n GnosisSafeProxy proxy,\n address _singleton,\n bytes calldata initializer,\n uint256 saltNonce\n ) external;\n}\n","imports":["proxies/GnosisSafeProxy.sol"],"references":["proxies/GnosisSafeProxyFactory.sol"],"urls":[]},"test/ERC1155Token.sol":{"checksum":{"algorithm":"md5","hash":"0xab71494dc9c4c081d1b731d67d1c07de"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../interfaces/ERC1155TokenReceiver.sol\";\nimport \"../external/GnosisSafeMath.sol\";\n\ncontract ERC1155Token {\n using GnosisSafeMath for uint256;\n\n // Mapping from token ID to owner balances\n mapping(uint256 => mapping(address => uint256)) private _balances;\n\n // Mapping from owner to operator approvals\n mapping(address => mapping(address => bool)) private _operatorApprovals;\n\n /**\n @dev Get the specified address' balance for token with specified ID.\n @param owner The address of the token holder\n @param id ID of the token\n @return The owner's balance of the token type requested\n */\n function balanceOf(address owner, uint256 id) public view returns (uint256) {\n require(owner != address(0), \"ERC1155: balance query for the zero address\");\n return _balances[id][owner];\n }\n\n /**\n @dev Transfers `value` amount of an `id` from the `from` address to the `to` address specified.\n Caller must be approved to manage the tokens being transferred out of the `from` account.\n If `to` is a smart contract, will call `onERC1155Received` on `to` and act appropriately.\n @param from Source address\n @param to Target address\n @param id ID of the token type\n @param value Transfer amount\n @param data Data forwarded to `onERC1155Received` if `to` is a contract receiver\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n uint256 value,\n bytes calldata data\n ) external {\n require(to != address(0), \"ERC1155: target address must be non-zero\");\n require(\n from == msg.sender || _operatorApprovals[from][msg.sender] == true,\n \"ERC1155: need operator approval for 3rd party transfers.\"\n );\n\n _balances[id][from] = _balances[id][from] - value;\n _balances[id][to] = value + _balances[id][to];\n\n _doSafeTransferAcceptanceCheck(msg.sender, from, to, id, value, data);\n }\n\n /**\n * @dev Test function to mint an amount of a token with the given ID\n * @param to The address that will own the minted token\n * @param id ID of the token to be minted\n * @param value Amount of the token to be minted\n * @param data Data forwarded to `onERC1155Received` if `to` is a contract receiver\n */\n function mint(\n address to,\n uint256 id,\n uint256 value,\n bytes calldata data\n ) external {\n require(to != address(0), \"ERC1155: mint to the zero address\");\n\n _balances[id][to] = value + _balances[id][to];\n\n _doSafeTransferAcceptanceCheck(msg.sender, address(0), to, id, value, data);\n }\n\n function isContract(address account) internal view returns (bool) {\n // This method relies in extcodesize, which returns 0 for contracts in\n // construction, since the code is only stored at the end of the\n // constructor execution.\n\n uint256 size;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n function _doSafeTransferAcceptanceCheck(\n address operator,\n address from,\n address to,\n uint256 id,\n uint256 value,\n bytes memory data\n ) internal {\n if (isContract(to)) {\n require(\n ERC1155TokenReceiver(to).onERC1155Received(operator, from, id, value, data) ==\n ERC1155TokenReceiver(to).onERC1155Received.selector,\n \"ERC1155: got unknown value from onERC1155Received\"\n );\n }\n }\n}\n","imports":["external/GnosisSafeMath.sol","interfaces/ERC1155TokenReceiver.sol"],"references":[],"urls":[]},"test/ERC20Token.sol":{"checksum":{"algorithm":"md5","hash":"0xd696ead3d7cae3917e9961513be18079"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.6.0 <0.8.0;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract ERC20Token is ERC20 {\n constructor() public ERC20(\"TestToken\", \"TT\") {\n _mint(msg.sender, 1000000000000000);\n }\n}\n","imports":["test/@openzeppelin/contracts/token/ERC20/ERC20.sol"],"references":[],"urls":[]},"test/TestHandler.sol":{"checksum":{"algorithm":"md5","hash":"0xb69208087c530257ebe2fc6832af3397"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.7.0 <0.9.0;\n\nimport \"../handler/HandlerContext.sol\";\n\ncontract TestHandler is HandlerContext {\n function dudududu() external view returns (address sender, address manager) {\n return (_msgSender(), _manager());\n }\n}\n","imports":["handler/HandlerContext.sol"],"references":[],"urls":[]},"test/Token.sol":{"checksum":{"algorithm":"md5","hash":"0x1a836e35f25b5167e4789431bf7029e7"},"content":"// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity >=0.6.0 <0.7.0;\nimport \"@gnosis.pm/mock-contract/contracts/MockContract.sol\";\n\ninterface Token {\n function transfer(address _to, uint256 value) external returns (bool);\n}\n","imports":["test/@gnosis.pm/mock-contract/contracts/MockContract.sol"],"references":[],"urls":[]}}} \ No newline at end of file diff --git a/setup.py b/setup.py index feb6d38..e58621d 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ "hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer "ape-alchemy", # Needed for testing in a forked network "ape-foundry", # Needed for forked-network features + "ape-solidity", # Needed for compiling the Safe contracts ], "lint": [ "black>=23.10.1,<24", # Auto-formatter and linter @@ -27,7 +28,7 @@ ], "dev": [ "commitizen", # Manage commits and publishing releases - "pre-commit", # Ensure that linters are run prior to commiting + "pre-commit", # Ensure that linters are run prior to committing "pytest-watch", # `ptw` test watcher/runner "IPython", # Console for interacting "ipdb", # Debugger (Must use `export PYTHONBREAKPOINT=ipdb.set_trace`) diff --git a/tests/ape-config.yaml b/tests/ape-config.yaml deleted file mode 100644 index 0db7509..0000000 --- a/tests/ape-config.yaml +++ /dev/null @@ -1,18 +0,0 @@ -ethereum: - mainnnet: - default_provider: alchemy - local: - default_provider: foundry - -foundry: - fork: - ethereum: - mainnet: - upstream_provider: alchemy - block_number: 15776634 - goerli: - upstream_provider: alchemy - block_number: 7849922 - sepolia: - upstream_provider: alchemy - block_number: 3091950 diff --git a/tests/conftest.py b/tests/conftest.py index aac7886..e583398 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,15 @@ from ape_safe.accounts import SafeAccount contracts_directory = Path(__file__).parent / "contracts" +TESTS_DIR = Path(__file__).parent.absolute() + + +# @pytest.fixture(autouse=True) +# def project(): +# # This is needed for processing the Safe dependency. +# with config.using_project(TESTS_DIR) as proj: +# yield proj +# @pytest.fixture(scope="session") From 5c340c7669b09d841d5dbd1f6a7b96a2e230473b Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Fri, 3 Nov 2023 09:37:59 -0500 Subject: [PATCH 075/134] chore: bump ape --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e58621d..5fac471 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ url="https://github.com/banteg/ape-safe", include_package_data=True, install_requires=[ - "eth-ape>=0.6.11,<0.7.0", + "eth-ape>=0.6.23,<0.7.0", "eip712>=0.2.0,<0.3.0", "requests>=2.31.0,<3", "click", # Use same version as eth-ape From dec9009ae482a303c349962182fb1c0ddc8af360 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Fri, 3 Nov 2023 09:39:39 -0500 Subject: [PATCH 076/134] chore: delete hacks --- compile-safe.sh | 32 -------------------------------- requirements.txt | 2 -- 2 files changed, 34 deletions(-) delete mode 100644 compile-safe.sh delete mode 100644 requirements.txt diff --git a/compile-safe.sh b/compile-safe.sh deleted file mode 100644 index 11fc101..0000000 --- a/compile-safe.sh +++ /dev/null @@ -1,32 +0,0 @@ -# Working with NPM dependencies is broken: https://github.com/ApeWorX/ape/issues/1327 -# And also the settings are weird: https://github.com/ApeWorX/ape/issues/1221 -# This is what I did to build the `safe-contracts` dependency -# And then put manifest at `~/.ape/packages/safe-contracts/v1.3.0/safe-contracts.json` - -git clone https://github.com/safe-global/safe-contracts -cd safe-contracts -git checkout v1.3.0 -rm -rf contracts/test -rm -f contracts/interfaces/ViewStorageAccessible.sol -cat< Date: Fri, 3 Nov 2023 09:51:00 -0500 Subject: [PATCH 077/134] test: call with cls --- tests/conftest.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e583398..c7abfc2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -130,6 +130,5 @@ def foundry(networks): @pytest.fixture def multisend(): - ms = MultiSend() - ms.inject() - return ms + MultiSend.inject() + return MultiSend() From e7617f0731d6a862913a12c8f1f73d9525cceea6 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Fri, 3 Nov 2023 09:53:36 -0500 Subject: [PATCH 078/134] chore: del stuff --- tests/conftest.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c7abfc2..8b1374f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,14 +14,6 @@ TESTS_DIR = Path(__file__).parent.absolute() -# @pytest.fixture(autouse=True) -# def project(): -# # This is needed for processing the Safe dependency. -# with config.using_project(TESTS_DIR) as proj: -# yield proj -# - - @pytest.fixture(scope="session") def deployer(accounts): return accounts[-1] @@ -38,7 +30,7 @@ def SafeSingleton(project, VERSION): @pytest.fixture -def singleton(deployer, SafeSingleton): +def singleton(deployer: SafeAccount, SafeSingleton): return deployer.deploy(SafeSingleton) @@ -111,13 +103,13 @@ def safe(safe_data_file): @pytest.fixture -def token(deployer): +def token(deployer: SafeAccount): contract = ContractType.parse_file(contracts_directory / "Token.json") return deployer.deploy(ContractContainer(contract)) @pytest.fixture -def vault(deployer, token): +def vault(deployer: SafeAccount, token): vault = ContractContainer(ContractType.parse_file(contracts_directory / "VyperVault.json")) return deployer.deploy(vault, token) From cd31696e908ca73dbcdbbd0ee5a5f64fc46fa408 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Fri, 3 Nov 2023 09:56:31 -0500 Subject: [PATCH 079/134] test: del alchemy --- .github/workflows/test.yaml | 2 -- setup.py | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e9aadcb..0c9be52 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -97,5 +97,3 @@ jobs: - name: Run Tests run: pytest -n 0 -s --cov - env: - WEB3_ALCHEMY_PROJECT_ID: ${{ secrets.WEB3_ALCHEMY_PROJECT_ID }} diff --git a/setup.py b/setup.py index 5fac471..318a74e 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,7 @@ "pytest-xdist", # multi-process runner "pytest-cov", # Coverage analyzer plugin "hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer - "ape-alchemy", # Needed for testing in a forked network - "ape-foundry", # Needed for forked-network features + "ape-foundry", # Used as the testing provider "ape-solidity", # Needed for compiling the Safe contracts ], "lint": [ From 1ffd54383a6f65eceddb50a65f3a831d021a9981 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Fri, 3 Nov 2023 10:22:51 -0500 Subject: [PATCH 080/134] refactor: more submitter callback --- ape_safe/_cli.py | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py index 52c9375..0d7512a 100644 --- a/ape_safe/_cli.py +++ b/ape_safe/_cli.py @@ -133,6 +133,7 @@ def _handle_execute_cli_arg(ctx, param, val): # If it is determined in `pending` that a tx can execute, # the user will get prompted. # Avoid this by always doing `--execute false`. + return val elif val in ctx.obj.account_manager.aliases: @@ -144,7 +145,11 @@ def _handle_execute_cli_arg(ctx, param, val): # Saying "yes, execute". Use first "local signer". elif val.lower() in ("true", "t", "1"): - return True + safe = ctx.obj.account_manager.load(ctx.params["alias"]) + if not safe.local_signers: + ctx.obj.abort("Cannot use `--execute TRUE` without a local signer.") + + return get_user_selected_account(account_type=safe.local_signers) # Saying "no, do not execute", even if we could. elif val.lower() in ("false", "f", "0"): @@ -168,21 +173,7 @@ def pending(cli_ctx: SafeCliContext, network, sign_with_local_signers, execute, _ = network # Needed for NetworkBoundCommand safe = cli_ctx.account_manager.load(alias) - submitter: Optional[AccountAPI] = None - - if execute is True: - if not safe.local_signers: - cli_ctx.abort("Cannot use `--execute TRUE` without a local signer.") - - submitter = get_user_selected_account(account_type=safe.local_signers) - - elif isinstance(execute, AccountAPI): - # The callback handler loaded the local account. - submitter = execute - - # NOTE: --execute is only None when not specified at all. - # In this case, for any found executable txns, the user will be prompted. - execute_cli_arg_specified = execute is not None + submitter: Optional[AccountAPI] = execute if isinstance(execute, AccountAPI) else None for safe_tx, confirmations in safe.pending_transactions(): click.echo( @@ -190,14 +181,29 @@ def pending(cli_ctx: SafeCliContext, network, sign_with_local_signers, execute, ) # Add signatures, if was requested to do so. - if sign_with_local_signers and len(confirmations) < safe.confirmations_required - 1: - safe.add_signatures(safe_tx, confirmations) - cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") + + if sign_with_local_signers: + threshold = safe.confirmations_required - 1 + num_confirmations = len(confirmations) + if num_confirmations == threshold: + proceed = click.prompt("Additional signatures not required. Proceed?") + if proceed: + safe.add_signatures(safe_tx, confirmations) + cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") + + elif num_confirmations < threshold: + safe.add_signatures(safe_tx, confirmations) + cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") + + else: + cli_ctx.logger.error("Unable to add signatures. Transaction fully signed.") # NOTE: Lazily check signatures. signatures = None - if not execute_cli_arg_specified: + # The user did provider a value for `--execute` however we are able to + # So we prompt them. + if execute is None and submitter is None: # Check if we _can_ execute and ask the user. signatures = safe.get_api_confirmations(safe_tx) do_execute = ( From b91bac47e2433e4fd39529c774861a41e58985c8 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 27 Nov 2023 15:40:37 -0600 Subject: [PATCH 081/134] feat: wip --- ape_safe/_cli.py | 270 ---------------------- ape_safe/_cli/__init__.py | 19 ++ ape_safe/_cli/click_ext.py | 36 +++ ape_safe/_cli/pending.py | 156 +++++++++++++ ape_safe/_cli/safe_mgmt.py | 121 ++++++++++ ape_safe/accounts.py | 10 +- ape_safe/client.py | 437 ------------------------------------ ape_safe/client/__init__.py | 190 ++++++++++++++++ ape_safe/client/base.py | 86 +++++++ ape_safe/client/mock.py | 100 +++++++++ ape_safe/client/models.py | 123 ++++++++++ 11 files changed, 836 insertions(+), 712 deletions(-) delete mode 100644 ape_safe/_cli.py create mode 100644 ape_safe/_cli/__init__.py create mode 100644 ape_safe/_cli/click_ext.py create mode 100644 ape_safe/_cli/pending.py create mode 100644 ape_safe/_cli/safe_mgmt.py create mode 100644 ape_safe/client/__init__.py create mode 100644 ape_safe/client/base.py create mode 100644 ape_safe/client/mock.py create mode 100644 ape_safe/client/models.py diff --git a/ape_safe/_cli.py b/ape_safe/_cli.py deleted file mode 100644 index 0d7512a..0000000 --- a/ape_safe/_cli.py +++ /dev/null @@ -1,270 +0,0 @@ -from typing import Optional - -import click -from ape.api import AccountAPI -from ape.cli import ( - ApeCliContextObject, - NetworkBoundCommand, - ape_cli_context, - existing_alias_argument, - get_user_selected_account, - network_option, - non_existing_alias_argument, -) -from ape.exceptions import ChainError -from ape.types import AddressType -from click import BadArgumentUsage, BadOptionUsage - -from ape_safe.accounts import SafeAccount, SafeContainer -from ape_safe.client import ExecutedTxData - - -class SafeCliContext(ApeCliContextObject): - @property - def safes(self) -> SafeContainer: - # NOTE: Would only happen in local development of this plugin. - assert "safe" in self.account_manager.containers, "Are all API methods implemented?" - return self.account_manager.containers["safe"] - - -safe_cli_ctx = ape_cli_context(obj_type=SafeCliContext) - - -@click.group(short_help="Manage Safe accounts and view Safe API data") -def cli(): - """ - Command-line helper for managing Safes. You can add Safes to your local accounts, - or view data from any Safe using the Safe API client. - """ - - -@cli.command(name="list", cls=NetworkBoundCommand) -@safe_cli_ctx -@network_option() -def _list(cli_ctx: SafeCliContext, network): - """ - Show locally-tracked Safes - """ - - _ = network # Needed for NetworkBoundCommand - number_of_safes = len(cli_ctx.safes) - - if number_of_safes == 0: - cli_ctx.logger.warning("No Safes found.") - return - - header = f"Found {number_of_safes} Safe" - header += "s:" if number_of_safes > 1 else ":" - click.echo(header) - - for account in cli_ctx.safes: - extras = [] - if account.alias: - extras.append(f"alias: '{account.alias}'") - - try: - extras.append(f"version: '{account.version}'") - except ChainError: - # Not connected to the network where safe is deployed - extras.append("version: (not connected)") - - extras_display = f" ({', '.join(extras)})" if extras else "" - click.echo(f" {account.address}{extras_display}") - - -@cli.command(cls=NetworkBoundCommand) -@safe_cli_ctx -@network_option() -@click.argument("address", type=AddressType) -@non_existing_alias_argument() -def add(cli_ctx: SafeCliContext, network, address, alias): - """ - Add a Safe to locally tracked Safes - """ - - _ = network # Needed for NetworkBoundCommand - address = cli_ctx.conversion_manager.convert(address, AddressType) - safe_contract = cli_ctx.chain_manager.contracts.instance_at(address) - version_display = safe_contract.VERSION() - required_confirmations = safe_contract.getThreshold() - signers_display = "\n - ".join(safe_contract.getOwners()) - - cli_ctx.logger.info( - f"""Safe Found - network: {network} - address: {safe_contract.address} - version: {version_display} - required confirmations: {required_confirmations} - signers: - - {signers_display} - """ - ) - - if click.confirm("Add safe"): - cli_ctx.safes.save_account(alias, address) - cli_ctx.logger.success(f"Safe '{address}' ({alias}) added.") - - -@cli.command() -@safe_cli_ctx -@existing_alias_argument() -def remove(cli_ctx: SafeCliContext, alias): - """ - Stop tracking a locally-tracked Safe - """ - - if alias not in cli_ctx.safes.aliases: - raise BadArgumentUsage(f"There is no safe with the alias `{alias}`.") - - address = cli_ctx.safes.load_account(alias).address - if click.confirm(f"Remove safe {address} ({alias})"): - cli_ctx.safes.delete_account(alias) - - cli_ctx.logger.success(f"Safe '{address}' ({alias}) removed.") - - -# NOTE: The handling of the `--execute` flag in the `pending` CLI -# all happens here EXCEPT if a pending tx is executable and no -# value of `--execute` was provided. -def _handle_execute_cli_arg(ctx, param, val): - # Account alias - execute using this account. - if val is None: - # Was not given any value. - # If it is determined in `pending` that a tx can execute, - # the user will get prompted. - # Avoid this by always doing `--execute false`. - - return val - - elif val in ctx.obj.account_manager.aliases: - return ctx.obj.account_manager.load(val) - - # Account address - execute using this account. - elif val in ctx.obj.account_manager: - return ctx.obj.account_manager[val] - - # Saying "yes, execute". Use first "local signer". - elif val.lower() in ("true", "t", "1"): - safe = ctx.obj.account_manager.load(ctx.params["alias"]) - if not safe.local_signers: - ctx.obj.abort("Cannot use `--execute TRUE` without a local signer.") - - return get_user_selected_account(account_type=safe.local_signers) - - # Saying "no, do not execute", even if we could. - elif val.lower() in ("false", "f", "0"): - return False - - raise BadOptionUsage( - "--execute", f"`--execute` value '{val}` not a boolean or account identifier." - ) - - -@cli.command(cls=NetworkBoundCommand) -@safe_cli_ctx -@network_option() -@click.option("sign_with_local_signers", "--sign", is_flag=True) -@click.option("--execute", callback=_handle_execute_cli_arg) -@existing_alias_argument(account_type=SafeAccount) -def pending(cli_ctx: SafeCliContext, network, sign_with_local_signers, execute, alias) -> None: - """ - View pending transactions for a Safe - """ - - _ = network # Needed for NetworkBoundCommand - safe = cli_ctx.account_manager.load(alias) - submitter: Optional[AccountAPI] = execute if isinstance(execute, AccountAPI) else None - - for safe_tx, confirmations in safe.pending_transactions(): - click.echo( - f"Transaction {safe_tx.nonce}: ({len(confirmations)}/{safe.confirmations_required})" - ) - - # Add signatures, if was requested to do so. - - if sign_with_local_signers: - threshold = safe.confirmations_required - 1 - num_confirmations = len(confirmations) - if num_confirmations == threshold: - proceed = click.prompt("Additional signatures not required. Proceed?") - if proceed: - safe.add_signatures(safe_tx, confirmations) - cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") - - elif num_confirmations < threshold: - safe.add_signatures(safe_tx, confirmations) - cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") - - else: - cli_ctx.logger.error("Unable to add signatures. Transaction fully signed.") - - # NOTE: Lazily check signatures. - signatures = None - - # The user did provider a value for `--execute` however we are able to - # So we prompt them. - if execute is None and submitter is None: - # Check if we _can_ execute and ask the user. - signatures = safe.get_api_confirmations(safe_tx) - do_execute = ( - len(safe.local_signers) > 0 - and len(signatures) >= safe.confirmations_required - and click.confirm(f"Submit Transaction {safe_tx.nonce}") - ) - if do_execute: - submitter = get_user_selected_account(account_type=safe.local_signers) - - if submitter: - # NOTE: Signatures may have gotten set above already. - signatures = signatures or safe.get_api_confirmations(safe_tx) - - exc_tx = safe.create_execute_transaction(safe_tx, signatures) - submitter.call(exc_tx) - - -@cli.command(cls=NetworkBoundCommand) -@safe_cli_ctx -@network_option() -@existing_alias_argument(account_type=SafeAccount) -@click.argument("txn-ids", type=int, nargs=-1) -def reject(cli_ctx: SafeCliContext, network, alias, txn_ids): - """ - Reject one or more pending transactions - """ - - _ = network # Needed for NetworkBoundCommand - safe = cli_ctx.account_manager.load(alias) - pending_transactions = safe.client.get_transactions(starting_nonce=safe.next_nonce) - - for txn_id in txn_ids: - try: - txn = next(txn for txn in pending_transactions if txn_id == txn.nonce) - except StopIteration: - # NOTE: Not a pending transaction. - continue - - if click.confirm(f"{txn}\nCancel Transaction?"): - safe.transfer(safe, "0 ether", nonce=txn_id, submit_transaction=False) - - -@cli.command(cls=NetworkBoundCommand) -@safe_cli_ctx -@network_option() -@click.argument("address", type=AddressType) -@click.option("--confirmed", is_flag=True, default=None) -def all_txns(cli_ctx: SafeCliContext, network, address, confirmed): - """ - View and filter all transactions for a given Safe using Safe API - """ - - _ = network # Needed for NetworkBoundCommand - client = cli_ctx.safes._get_client(address) - - for txn in client.get_transactions(confirmed=confirmed): - if isinstance(txn, ExecutedTxData): - success_str = "success" if txn.isSuccessful else "revert" - click.echo(f"Txn {txn.nonce}: {success_str} @ {txn.executionDate}") - else: - click.echo( - f"Txn {txn.nonce}: pending ({len(txn.confirmations)}/{txn.confirmations_required})" - ) diff --git a/ape_safe/_cli/__init__.py b/ape_safe/_cli/__init__.py new file mode 100644 index 0000000..92ce15d --- /dev/null +++ b/ape_safe/_cli/__init__.py @@ -0,0 +1,19 @@ +import click + +from ape_safe._cli.pending import pending +from ape_safe._cli.safe_mgmt import _list, add, all_txns, remove + + +@click.group(short_help="Manage Safe accounts and view Safe API data") +def cli(): + """ + Command-line helper for managing Safes. You can add Safes to your local accounts, + or view data from any Safe using the Safe API client. + """ + + +cli.add_command(_list) # type: ignore +cli.add_command(add) # type: ignore +cli.add_command(remove) # type: ignore +cli.add_command(all_txns) # type: ignore +cli.add_command(pending) # type: ignore diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py new file mode 100644 index 0000000..608c13a --- /dev/null +++ b/ape_safe/_cli/click_ext.py @@ -0,0 +1,36 @@ +from ape.cli import ApeCliContextObject, ape_cli_context, existing_alias_argument +from click import MissingParameter + +from ape import accounts +from ape_safe.accounts import SafeAccount, SafeContainer + + +class SafeCliContext(ApeCliContextObject): + @property + def safes(self) -> SafeContainer: + # NOTE: Would only happen in local development of this plugin. + assert "safe" in self.account_manager.containers, "Are all API methods implemented?" + return self.account_manager.containers["safe"] + + +safe_cli_ctx = ape_cli_context(obj_type=SafeCliContext) + + +def _safe_alias_callback(ctx, param, value): + # NOTE: For some reason, the Cli CTX object is not the SafeCliCtx yet at this point. + safes = accounts.containers["safe"] + if value is None: + # If there is only 1 safe, just use that. + if len(safes) == 1: + return next(safes.accounts) + + options = ", ".join(safes.aliases) + raise MissingParameter(message=f"Must specify safe to use (one of '{options}').") + + else: + return accounts.load(value) + + +safe_alias_argument = existing_alias_argument( + account_type=SafeAccount, callback=_safe_alias_callback, required=False +) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py new file mode 100644 index 0000000..1ec77c5 --- /dev/null +++ b/ape_safe/_cli/pending.py @@ -0,0 +1,156 @@ +from typing import Optional + +import click +from ape.api import AccountAPI +from ape.cli import ( + NetworkBoundCommand, + existing_alias_argument, + get_user_selected_account, + network_option, +) +from click.exceptions import BadOptionUsage + +from ape_safe._cli.click_ext import SafeCliContext, safe_alias_argument, safe_cli_ctx +from ape_safe.accounts import SafeAccount + + +@click.group() +def pending(): + """ + Commands for handling pending transactions + """ + + +@pending.command("list", cls=NetworkBoundCommand) +@safe_cli_ctx +@network_option() +@safe_alias_argument +def _list(cli_ctx: SafeCliContext, network, alias) -> None: + """ + View pending transactions for a Safe + """ + + _ = network # Needed for NetworkBoundCommand + safe = alias # Handled in callback + + for safe_tx, confirmations in safe.pending_transactions(): + click.echo( + f"Transaction {safe_tx.nonce}: ({len(confirmations)}/{safe.confirmations_required})" + ) + + +# NOTE: The handling of the `--execute` flag in the `pending` CLI +# all happens here EXCEPT if a pending tx is executable and no +# value of `--execute` was provided. +def _handle_execute_cli_arg(ctx, param, val): + # Account alias - execute using this account. + if val is None: + # Was not given any value. + # If it is determined in `pending` that a tx can execute, + # the user will get prompted. + # Avoid this by always doing `--execute false`. + + return val + + elif val in ctx.obj.account_manager.aliases: + return ctx.obj.account_manager.load(val) + + # Account address - execute using this account. + elif val in ctx.obj.account_manager: + return ctx.obj.account_manager[val] + + # Saying "yes, execute". Use first "local signer". + elif val.lower() in ("true", "t", "1"): + safe = ctx.obj.account_manager.load(ctx.params["alias"]) + if not safe.local_signers: + ctx.obj.abort("Cannot use `--execute TRUE` without a local signer.") + + return get_user_selected_account(account_type=safe.local_signers) + + # Saying "no, do not execute", even if we could. + elif val.lower() in ("false", "f", "0"): + return False + + raise BadOptionUsage( + "--execute", f"`--execute` value '{val}` not a boolean or account identifier." + ) + + +@pending.command(cls=NetworkBoundCommand) +@safe_cli_ctx +@network_option() +@safe_alias_argument +@click.argument("txn_id") +@click.option("--execute", callback=_handle_execute_cli_arg) +def approve(cli_ctx: SafeCliContext, network, alias, txn_id, execute): + _ = network # Needed for NetworkBoundCommand + safe = alias # Handled in callback + submitter: Optional[AccountAPI] = execute if isinstance(execute, AccountAPI) else None + txn = safe.pending_transactions + + # + # # Add signatures, if was requested to do so. + # + # if sign_with_local_signers: + # threshold = safe.confirmations_required - 1 + # num_confirmations = len(confirmations) + # if num_confirmations == threshold: + # proceed = click.prompt("Additional signatures not required. Proceed?") + # if proceed: + # safe.add_signatures(safe_tx, confirmations) + # cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") + # + # elif num_confirmations < threshold: + # safe.add_signatures(safe_tx, confirmations) + # cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") + # + # else: + # cli_ctx.logger.error("Unable to add signatures. Transaction fully signed.") + # + # # NOTE: Lazily check signatures. + # signatures = None + # + # # The user did provider a value for `--execute` however we are able to + # # So we prompt them. + # if execute is None and submitter is None: + # # Check if we _can_ execute and ask the user. + # signatures = safe.get_api_confirmations(safe_tx) + # do_execute = ( + # len(safe.local_signers) > 0 + # and len(signatures) >= safe.confirmations_required + # and click.confirm(f"Submit Transaction {safe_tx.nonce}") + # ) + # if do_execute: + # submitter = get_user_selected_account(account_type=safe.local_signers) + # + # if submitter: + # # NOTE: Signatures may have gotten set above already. + # signatures = signatures or safe.get_api_confirmations(safe_tx) + # + # exc_tx = safe.create_execute_transaction(safe_tx, signatures) + # submitter.call(exc_tx) + + +@pending.command(cls=NetworkBoundCommand) +@safe_cli_ctx +@network_option() +@safe_alias_argument +@click.argument("txn-ids", type=int, nargs=-1) +def reject(cli_ctx: SafeCliContext, network, alias, txn_ids): + """ + Reject one or more pending transactions + """ + + _ = network # Needed for NetworkBoundCommand + safe = cli_ctx.account_manager.load(alias) + pending_transactions = safe.client.get_transactions(starting_nonce=safe.next_nonce) + + for txn_id in txn_ids: + try: + txn = next(txn for txn in pending_transactions if txn_id == txn.nonce) + except StopIteration: + # NOTE: Not a pending transaction. + continue + + if click.confirm(f"{txn}\nCancel Transaction?"): + safe.transfer(safe, "0 ether", nonce=txn_id, submit_transaction=False) diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py new file mode 100644 index 0000000..b42f7e5 --- /dev/null +++ b/ape_safe/_cli/safe_mgmt.py @@ -0,0 +1,121 @@ +import click +from ape.cli import ( + NetworkBoundCommand, + existing_alias_argument, + network_option, + non_existing_alias_argument, +) +from ape.exceptions import ChainError +from ape.types import AddressType +from click import BadArgumentUsage + +from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx +from ape_safe.client import ExecutedTxData + + +@click.command(name="list", cls=NetworkBoundCommand) +@safe_cli_ctx +@network_option() +def _list(cli_ctx: SafeCliContext, network): + """ + Show locally-tracked Safes + """ + + _ = network # Needed for NetworkBoundCommand + number_of_safes = len(cli_ctx.safes) + + if number_of_safes == 0: + cli_ctx.logger.warning("No Safes found.") + return + + header = f"Found {number_of_safes} Safe" + header += "s:" if number_of_safes > 1 else ":" + click.echo(header) + + for account in cli_ctx.safes: + extras = [] + if account.alias: + extras.append(f"alias: '{account.alias}'") + + try: + extras.append(f"version: '{account.version}'") + except ChainError: + # Not connected to the network where safe is deployed + extras.append("version: (not connected)") + + extras_display = f" ({', '.join(extras)})" if extras else "" + click.echo(f" {account.address}{extras_display}") + + +@click.command(cls=NetworkBoundCommand) +@safe_cli_ctx +@network_option() +@click.argument("address", type=AddressType) +@non_existing_alias_argument() +def add(cli_ctx: SafeCliContext, network, address, alias): + """ + Add a Safe to locally tracked Safes + """ + + _ = network # Needed for NetworkBoundCommand + address = cli_ctx.conversion_manager.convert(address, AddressType) + safe_contract = cli_ctx.chain_manager.contracts.instance_at(address) + version_display = safe_contract.VERSION() + required_confirmations = safe_contract.getThreshold() + signers_display = "\n - ".join(safe_contract.getOwners()) + + cli_ctx.logger.info( + f"""Safe Found + network: {network} + address: {safe_contract.address} + version: {version_display} + required confirmations: {required_confirmations} + signers: + - {signers_display} + """ + ) + + if click.confirm("Add safe"): + cli_ctx.safes.save_account(alias, address) + cli_ctx.logger.success(f"Safe '{address}' ({alias}) added.") + + +@click.command() +@safe_cli_ctx +@existing_alias_argument() +def remove(cli_ctx: SafeCliContext, alias): + """ + Stop tracking a locally-tracked Safe + """ + + if alias not in cli_ctx.safes.aliases: + raise BadArgumentUsage(f"There is no safe with the alias `{alias}`.") + + address = cli_ctx.safes.load_account(alias).address + if click.confirm(f"Remove safe {address} ({alias})"): + cli_ctx.safes.delete_account(alias) + + cli_ctx.logger.success(f"Safe '{address}' ({alias}) removed.") + + +@click.command(cls=NetworkBoundCommand) +@safe_cli_ctx +@network_option() +@click.argument("address", type=AddressType) +@click.option("--confirmed", is_flag=True, default=None) +def all_txns(cli_ctx: SafeCliContext, network, address, confirmed): + """ + View and filter all transactions for a given Safe using Safe API + """ + + _ = network # Needed for NetworkBoundCommand + client = cli_ctx.safes._get_client(address) + + for txn in client.get_transactions(confirmed=confirmed): + if isinstance(txn, ExecutedTxData): + success_str = "success" if txn.is_successful else "revert" + click.echo(f"Txn {txn.nonce}: {success_str} @ {txn.execution_date}") + else: + click.echo( + f"Txn {txn.nonce}: pending ({len(txn.confirmations)}/{txn.confirmations_required})" + ) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 85427bd..3ffd6d3 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -236,10 +236,7 @@ def client(self) -> BaseSafeClient: address=self.address, chain_id=self.provider.network.upstream_chain_id ) - elif ( - self.provider.network.name == LOCAL_NETWORK_NAME - or self.provider.network.name.endswith("-fork") - ): + elif self.provider.network.is_dev: return MockSafeClient(contract=self.contract) return SafeClient(address=self.address, chain_id=self.provider.chain_id) @@ -309,6 +306,9 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) safe_tx = {**safe_tx, **{k: v for k, v in safe_tx_kwargs.items() if k in safe_tx}} return self.safe_tx_def(**safe_tx) + def get_transaction(self, txn_id: SafeTxID): + return self.client.get_transactions() + def pending_transactions(self) -> Iterator[Tuple[SafeTx, List[SafeTxConfirmation]]]: for executed_tx in self.client.get_transactions(confirmed=False): yield self.create_safe_tx(**executed_tx.dict()), executed_tx.confirmations @@ -654,7 +654,7 @@ def add_signatures(self, safe_tx: SafeTx, confirmations: List[SafeTxConfirmation ls for ls in self.local_signers if ls.address not in [c.owner for c in confirmations] ] for signer in signers: - if not (tx_hash_result := next((c.transactionHash for c in confirmations), None)): + if not (tx_hash_result := next((c.transaction_hash for c in confirmations), None)): tx_hash_result = self.contract.getTransactionHash(*safe_tx) if tx_hash_result is None: diff --git a/ape_safe/client.py b/ape_safe/client.py index 86e8c85..e69de29 100644 --- a/ape_safe/client.py +++ b/ape_safe/client.py @@ -1,437 +0,0 @@ -from abc import ABC, abstractmethod -from datetime import datetime, timezone -from enum import Enum -from functools import reduce -from typing import Dict, Iterator, List, NewType, Optional, Set, Union - -import requests -from ape.contracts import ContractInstance -from ape.types import AddressType, HexBytes, MessageSignature -from ape.utils import ZERO_ADDRESS, ManagerAccessMixin -from eip712.common import SafeTxV1, SafeTxV2 -from eip712.messages import hash_eip712_message -from eth_utils import keccak -from pydantic import BaseModel, Field - -from ape_safe.exceptions import ( - ClientResponseError, - ClientUnsupportedChainError, - MultisigTransactionNotFoundError, -) -from ape_safe.utils import order_by_signer - -SafeTx = Union[SafeTxV1, SafeTxV2] -SafeTxID = NewType("SafeTxID", str) - -TRANSACTION_SERVICE_URL = { - # NOTE: If URLs need to be updated, a list of available service URLs can be found at - # https://docs.safe.global/safe-core-api/available-services. - # NOTE: There should be no trailing slashes at the end of the URL. - 1: "https://safe-transaction-mainnet.safe.global", - 5: "https://safe-transaction-goerli.safe.global", - 10: "https://safe-transaction-optimism.safe.global", - 56: "https://safe-transaction-bsc.safe.global", - 100: "https://safe-transaction-gnosis-chain.safe.global", - 137: "https://safe-transaction-polygon.safe.global", - 250: "https://safe-txservice.fantom.network", - 288: "https://safe-transaction.mainnet.boba.network", - 8453: "https://safe-transaction-base.safe.global", - 42161: "https://safe-transaction-arbitrum.safe.global", - 43114: "https://safe-transaction-avalanche.safe.global", - 84531: "https://safe-transaction-base-testnet.safe.global", -} - - -class SafeDetails(BaseModel): - address: AddressType - nonce: int - threshold: int - owners: List[AddressType] - masterCopy: AddressType - modules: List[AddressType] - fallbackHandler: AddressType - guard: AddressType - version: str - - -class SignatureType(str, Enum): - APPROVED_HASH = "APPROVED_HASH" - EOA = "EOA" - ETH_SIGN = "ETH_SIGN" - - -class SafeTxConfirmation(BaseModel): - owner: AddressType - submissionDate: datetime - transactionHash: Optional[HexBytes] = None - signature: HexBytes - signatureType: SignatureType - - -class OperationType(int, Enum): - CALL = 0 - DELEGATECALL = 1 - - -class UnexecutedTxData(BaseModel): - safe: AddressType - to: AddressType - value: int - data: Optional[HexBytes] = None - operation: OperationType - gas_token: AddressType = Field(alias="gasToken") - safe_tx_gas: int = Field(alias="safeTxGas") - base_gas: int = Field(alias="baseGas") - gas_price: int = Field(alias="gasPrice") - refund_receiver: AddressType = Field(alias="refundReceiver") - nonce: int - submission_date: datetime = Field(alias="submissionDate") - modified: datetime - safe_tx_hash: SafeTxID = Field(alias="safeTxHash") - confirmations_required: int = Field(alias="confirmationsRequired") - confirmations: List[SafeTxConfirmation] = [] - trusted: bool = True - signatures: Optional[HexBytes] = None - - @classmethod - def from_safe_tx(cls, safe_tx: SafeTx, confirmations_required: int) -> "UnexecutedTxData": - return cls( - safe=safe_tx._verifyingContract_, - submissionDate=datetime.now(timezone.utc), - modified=datetime.now(timezone.utc), - confirmationsRequired=confirmations_required, - safeTxHash=hash_eip712_message(safe_tx).hex(), - **safe_tx._body_["message"], - ) - - @property - def base_tx_dict(self) -> Dict: - return { - "to": self.to, - "value": self.value, - "data": self.data, - "operation": self.operation, - "safeTxGas": self.safe_tx_gas, - "baseGas": self.base_gas, - "gasPrice": self.gas_price, - "gasToken": self.gas_token, - "refundReceiver": self.refund_receiver, - "nonce": self.nonce, - } - - def __str__(self) -> str: - # TODO: Decode data - data_hex = self.data.hex() if self.data else "" - if len(data_hex) > 40: - data_hex = f"{data_hex[:18]}....{data_hex[-18:]}" - - # TODO: Handle MultiSend contract differently - return f"""Tx ID {self.nonce} - type: {self.operation._name_} - from: {self.safe} - to: {self.to} - value: {self.value / 1e18} ether - data: 0x{data_hex} -""" - - -class ExecutedTxData(UnexecutedTxData): - executionDate: datetime - blockNumber: int - transactionHash: HexBytes - executor: AddressType - isExecuted: bool - isSuccessful: bool - ethGasPrice: int - maxFeePerGas: Optional[int] = None - maxPriorityFeePerGas: Optional[int] = None - gasUsed: int - fee: int - origin: str - dataDecoded: Optional[dict] = None - - -SafeApiTxData = Union[ExecutedTxData, UnexecutedTxData] - - -class BaseSafeClient(ABC): - @property - @abstractmethod - def safe_details(self) -> SafeDetails: - ... - - @abstractmethod - def get_next_nonce(self) -> int: - ... - - @abstractmethod - def _all_transactions(self) -> Iterator[SafeApiTxData]: - ... - - def get_transactions( - self, - confirmed: Optional[bool] = None, - starting_nonce: int = 0, - filter_by_ids: Optional[Set[SafeTxID]] = None, - filter_by_missing_signers: Optional[Set[AddressType]] = None, - ) -> Iterator[SafeApiTxData]: - """ - confirmed: Confirmed if True, not confirmed if False, both if None - """ - next_nonce = self.get_next_nonce() - - for txn in self._all_transactions(): - if txn.nonce < starting_nonce: - break # NOTE: order is largest nonce to smallest, so safe to break here - - is_confirmed = len(txn.confirmations) >= txn.confirmations_required - - if confirmed is not None: - if not confirmed and isinstance(txn, ExecutedTxData): - break # NOTE: Break at the first executed transaction - - elif confirmed and not is_confirmed: - continue # NOTE: Skip not confirmed transactions - - if txn.nonce < next_nonce and isinstance(txn, UnexecutedTxData): - continue # NOTE: Skip orphaned transactions - - if filter_by_ids and txn.safe_tx_hash not in filter_by_ids: - continue # NOTE: Skip transactions not in the filter - - if filter_by_missing_signers and filter_by_missing_signers.issubset( - set(conf.owner for conf in txn.confirmations) - ): - # NOTE: Skip if all signers from `filter_by_missing_signers` - # are in `txn.confirmations` - continue - - yield txn - - @abstractmethod - def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: - ... - - @abstractmethod - def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): - ... - - @abstractmethod - def post_signature( - self, - safe_tx_or_hash: Union[SafeTx, SafeTxID], - signer: AddressType, - signature: MessageSignature, - ): - ... - - -class SafeClient(BaseSafeClient): - def __init__( - self, - address: AddressType, - override_url: Optional[str] = None, - chain_id: Optional[int] = None, - ) -> None: - self.address = address - - if override_url: - self.transaction_service_url = override_url - - elif chain_id: - if chain_id not in TRANSACTION_SERVICE_URL: - raise ClientUnsupportedChainError(chain_id) - - self.transaction_service_url = TRANSACTION_SERVICE_URL.get( # type: ignore[assignment] - chain_id - ) - - else: - raise ValueError("Must provide one of chain_id or override_url.") - - @property - def safe_details(self) -> SafeDetails: - url = f"{self.transaction_service_url}/api/v1/safes/{self.address}" - response = requests.get(url) - if not response.ok: - raise ClientResponseError(url, response) - - return SafeDetails.parse_obj(response.json()) - - def get_next_nonce(self) -> int: - return self.safe_details.nonce - - def _all_transactions(self) -> Iterator[SafeApiTxData]: - """ - confirmed: Confirmed if True, not confirmed if False, both if None - """ - - url = f"{self.transaction_service_url}/api/v1/safes/{self.address}/transactions" - while url: - response = requests.get(url) - if not response.ok: - raise ClientResponseError(url, response) - - data = response.json() - - for txn in data.get("results"): - if "isExecuted" in txn and txn["isExecuted"]: - yield ExecutedTxData.parse_obj(txn) - - else: - yield UnexecutedTxData.parse_obj(txn) - - url = data.get("next") - - def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: - url = ( - f"{self.transaction_service_url}/api" - f"/v1/multisig-transactions/{str(safe_tx_hash)}/confirmations" - ) - while url: - response = requests.get(url) - if not response.ok: - raise ClientResponseError(url, response) - - data = response.json() - yield from map(SafeTxConfirmation.parse_obj, data.get("results")) - url = data.get("next") - - def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): - tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) - tx_data.signatures = HexBytes( - reduce( - lambda raw_sig, next_sig: raw_sig + next_sig.encode_rsv(), - order_by_signer(sigs), - b"", - ) - ) - post_dict: Dict = {} - for key, value in tx_data.dict().items(): - if isinstance(value, HexBytes): - post_dict[key] = value.hex() - elif isinstance(value, OperationType): - post_dict[key] = int(value) - elif isinstance(value, datetime): - # not needed - continue - else: - post_dict[key] = value - - url = f"{self.transaction_service_url}/api/v1/safes/{tx_data.safe}/multisig-transactions" - json_data = {"origin": "ApeWorX/ape-safe", **post_dict} - response = requests.post(url, json=json_data) - - if not response.ok: - raise ClientResponseError(url, response) - - def post_signature( - self, - safe_tx_or_hash: Union[SafeTx, SafeTxID], - signer: AddressType, - signature: MessageSignature, - ): - if isinstance(safe_tx_or_hash, (SafeTxV1, SafeTxV2)): - safe_tx = safe_tx_or_hash - safe_tx_hash = hash_eip712_message(safe_tx).hex() - else: - safe_tx_hash = safe_tx_or_hash - - if not isinstance(safe_tx_hash, str): - raise TypeError("Expecting str-like type for 'safe_tx_hash'.") - - url = ( - f"{self.transaction_service_url}/api" - f"/v1/multisig-transactions/{safe_tx_hash}/confirmations" - ) - response = requests.post( - url, json={"origin": "ApeWorX/ape-safe", "signature": signature.encode_rsv().hex()} - ) - - if not response.ok: - if "The requested resource was not found on this server" in response.text: - raise MultisigTransactionNotFoundError(safe_tx_hash, url, response) - - raise ClientResponseError(url, response) - - -class MockSafeClient(BaseSafeClient, ManagerAccessMixin): - def __init__(self, contract: ContractInstance): - self.contract = contract - self.transactions: Dict[Union[SafeTx, SafeTxID], SafeApiTxData] = {} - self.transactions_by_nonce: Dict[int, List[SafeTxID]] = {} - - @property - def safe_details(self) -> SafeDetails: - slot = keccak(text="fallback_manager.handler.address") - value = self.provider.get_storage_at(self.contract.address, slot) - fallback_address = self.network_manager.ecosystem.decode_address(value[-20:]) - - return SafeDetails( - address=self.contract.address, - nonce=self.get_next_nonce(), - threshold=self.contract.getThreshold(), - owners=self.contract.getOwners(), - masterCopy=self.contract.masterCopy(), - modules=self.modules, - # TODO: Add fallback handler getter - fallbackHandler=fallback_address, - guard=self.guard, - version=self.contract.VERSION(), - ) - - @property - def guard(self) -> AddressType: - return ( - self.contract.getGuard() if "getGuard" in self.contract._view_methods_ else ZERO_ADDRESS - ) - - @property - def modules(self) -> List[AddressType]: - return self.contract.getModules() if "getModules" in self.contract._view_methods_ else [] - - def get_next_nonce(self) -> int: - return self.contract._view_methods_["nonce"]() - - def _all_transactions( - self, - ) -> Iterator[SafeApiTxData]: - for nonce in sorted(self.transactions_by_nonce.keys(), reverse=True): - yield from map(self.transactions.get, self.transactions_by_nonce[nonce]) - - def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: - if safe_tx_data := self.transactions.get(safe_tx_hash): - yield from safe_tx_data.confirmations - - def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): - safe_tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) - safe_tx_data.confirmations.extend( - SafeTxConfirmation( - owner=signer, - submissionDate=datetime.now(timezone.utc), - signature=sig.encode_rsv(), - signatureType=SignatureType.EOA, - ) - for signer, sig in sigs.items() - ) - self.transactions[safe_tx_data.safe_tx_hash] = safe_tx_data - - if safe_tx_data.nonce in self.transactions_by_nonce: - self.transactions_by_nonce[safe_tx_data.nonce].append(safe_tx_data.safe_tx_hash) - else: - self.transactions_by_nonce[safe_tx_data.nonce] = [safe_tx_data.safe_tx_hash] - - def post_signature( - self, - safe_tx_or_hash: Union[SafeTx, SafeTxID], - signer: AddressType, - signature: MessageSignature, - ): - self.transactions[safe_tx_or_hash].confirmations.append( - SafeTxConfirmation( - owner=signer, - submissionDate=datetime.now(timezone.utc), - signature=signature.encode_rsv(), - signatureType=SignatureType.EOA, - ) - ) diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py new file mode 100644 index 0000000..3dd37eb --- /dev/null +++ b/ape_safe/client/__init__.py @@ -0,0 +1,190 @@ +from datetime import datetime +from functools import reduce +from typing import Dict, Iterator, Optional, Union + +import requests +from ape.types import AddressType, HexBytes, MessageSignature +from eip712.common import SafeTxV1, SafeTxV2 +from eip712.messages import hash_eip712_message + +from ape_safe.client.base import BaseSafeClient +from ape_safe.client.mock import MockSafeClient +from ape_safe.client.models import ( + ExecutedTxData, + OperationType, + SafeApiTxData, + SafeDetails, + SafeTx, + SafeTxConfirmation, + SafeTxID, + SignatureType, + UnexecutedTxData, +) +from ape_safe.exceptions import ( + ClientResponseError, + ClientUnsupportedChainError, + MultisigTransactionNotFoundError, +) +from ape_safe.utils import order_by_signer + +TRANSACTION_SERVICE_URL = { + # NOTE: If URLs need to be updated, a list of available service URLs can be found at + # https://docs.safe.global/safe-core-api/available-services. + # NOTE: There should be no trailing slashes at the end of the URL. + 1: "https://safe-transaction-mainnet.safe.global", + 5: "https://safe-transaction-goerli.safe.global", + 10: "https://safe-transaction-optimism.safe.global", + 56: "https://safe-transaction-bsc.safe.global", + 100: "https://safe-transaction-gnosis-chain.safe.global", + 137: "https://safe-transaction-polygon.safe.global", + 250: "https://safe-txservice.fantom.network", + 288: "https://safe-transaction.mainnet.boba.network", + 8453: "https://safe-transaction-base.safe.global", + 42161: "https://safe-transaction-arbitrum.safe.global", + 43114: "https://safe-transaction-avalanche.safe.global", + 84531: "https://safe-transaction-base-testnet.safe.global", +} + + +class SafeClient(BaseSafeClient): + def __init__( + self, + address: AddressType, + override_url: Optional[str] = None, + chain_id: Optional[int] = None, + ) -> None: + self.address = address + + if override_url: + self.transaction_service_url = override_url + + elif chain_id: + if chain_id not in TRANSACTION_SERVICE_URL: + raise ClientUnsupportedChainError(chain_id) + + self.transaction_service_url = TRANSACTION_SERVICE_URL.get( # type: ignore[assignment] + chain_id + ) + + else: + raise ValueError("Must provide one of chain_id or override_url.") + + @property + def safe_details(self) -> SafeDetails: + url = f"{self.transaction_service_url}/api/v1/safes/{self.address}" + response = requests.get(url) + if not response.ok: + raise ClientResponseError(url, response) + + return SafeDetails.parse_obj(response.json()) + + def get_next_nonce(self) -> int: + return self.safe_details.nonce + + def _all_transactions(self) -> Iterator[SafeApiTxData]: + """ + confirmed: Confirmed if True, not confirmed if False, both if None + """ + + url = f"{self.transaction_service_url}/api/v1/safes/{self.address}/transactions" + while url: + response = requests.get(url) + if not response.ok: + raise ClientResponseError(url, response) + + data = response.json() + + for txn in data.get("results"): + if "isExecuted" in txn and txn["isExecuted"]: + yield ExecutedTxData.parse_obj(txn) + + else: + yield UnexecutedTxData.parse_obj(txn) + + url = data.get("next") + + def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: + url = ( + f"{self.transaction_service_url}/api" + f"/v1/multisig-transactions/{str(safe_tx_hash)}/confirmations" + ) + while url: + response = requests.get(url) + if not response.ok: + raise ClientResponseError(url, response) + + data = response.json() + yield from map(SafeTxConfirmation.parse_obj, data.get("results")) + url = data.get("next") + + def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): + tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) + tx_data.signatures = HexBytes( + reduce( + lambda raw_sig, next_sig: raw_sig + next_sig.encode_rsv(), + order_by_signer(sigs), + b"", + ) + ) + post_dict: Dict = {} + for key, value in tx_data.dict().items(): + if isinstance(value, HexBytes): + post_dict[key] = value.hex() + elif isinstance(value, OperationType): + post_dict[key] = int(value) + elif isinstance(value, datetime): + # not needed + continue + else: + post_dict[key] = value + + url = f"{self.transaction_service_url}/api/v1/safes/{tx_data.safe}/multisig-transactions" + json_data = {"origin": "ApeWorX/ape-safe", **post_dict} + response = requests.post(url, json=json_data) + + if not response.ok: + raise ClientResponseError(url, response) + + def post_signature( + self, + safe_tx_or_hash: Union[SafeTx, SafeTxID], + signer: AddressType, + signature: MessageSignature, + ): + if isinstance(safe_tx_or_hash, (SafeTxV1, SafeTxV2)): + safe_tx = safe_tx_or_hash + safe_tx_hash = hash_eip712_message(safe_tx).hex() + else: + safe_tx_hash = safe_tx_or_hash + + if not isinstance(safe_tx_hash, str): + raise TypeError("Expecting str-like type for 'safe_tx_hash'.") + + url = ( + f"{self.transaction_service_url}/api" + f"/v1/multisig-transactions/{safe_tx_hash}/confirmations" + ) + response = requests.post( + url, json={"origin": "ApeWorX/ape-safe", "signature": signature.encode_rsv().hex()} + ) + + if not response.ok: + if "The requested resource was not found on this server" in response.text: + raise MultisigTransactionNotFoundError(safe_tx_hash, url, response) + + raise ClientResponseError(url, response) + + +__all__ = [ + "ExecutedTxData", + "MockSafeClient", + "OperationType", + "SafeApiTxData", + "SafeClient", + "SafeDetails", + "SafeTx", + "SafeTxConfirmation", + "SafeTxV2", + "SignatureType", + "UnexecutedTxData", +] diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py new file mode 100644 index 0000000..1748b59 --- /dev/null +++ b/ape_safe/client/base.py @@ -0,0 +1,86 @@ +from abc import ABC, abstractmethod +from typing import Dict, Iterator, Optional, Set, Union + +from ape.types import AddressType, MessageSignature + +from ape_safe.client.models import ( + ExecutedTxData, + SafeApiTxData, + SafeDetails, + SafeTx, + SafeTxConfirmation, + SafeTxID, + UnexecutedTxData, +) + + +class BaseSafeClient(ABC): + @property + @abstractmethod + def safe_details(self) -> SafeDetails: + ... + + @abstractmethod + def get_next_nonce(self) -> int: + ... + + @abstractmethod + def _all_transactions(self) -> Iterator[SafeApiTxData]: + ... + + def get_transactions( + self, + confirmed: Optional[bool] = None, + starting_nonce: int = 0, + filter_by_ids: Optional[Set[SafeTxID]] = None, + filter_by_missing_signers: Optional[Set[AddressType]] = None, + ) -> Iterator[SafeApiTxData]: + """ + confirmed: Confirmed if True, not confirmed if False, both if None + """ + next_nonce = self.get_next_nonce() + + for txn in self._all_transactions(): + if txn.nonce < starting_nonce: + break # NOTE: order is largest nonce to smallest, so safe to break here + + is_confirmed = len(txn.confirmations) >= txn.confirmations_required + + if confirmed is not None: + if not confirmed and isinstance(txn, ExecutedTxData): + break # NOTE: Break at the first executed transaction + + elif confirmed and not is_confirmed: + continue # NOTE: Skip not confirmed transactions + + if txn.nonce < next_nonce and isinstance(txn, UnexecutedTxData): + continue # NOTE: Skip orphaned transactions + + if filter_by_ids and txn.safe_tx_hash not in filter_by_ids: + continue # NOTE: Skip transactions not in the filter + + if filter_by_missing_signers and filter_by_missing_signers.issubset( + set(conf.owner for conf in txn.confirmations) + ): + # NOTE: Skip if all signers from `filter_by_missing_signers` + # are in `txn.confirmations` + continue + + yield txn + + @abstractmethod + def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: + ... + + @abstractmethod + def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): + ... + + @abstractmethod + def post_signature( + self, + safe_tx_or_hash: Union[SafeTx, SafeTxID], + signer: AddressType, + signature: MessageSignature, + ): + ... diff --git a/ape_safe/client/mock.py b/ape_safe/client/mock.py new file mode 100644 index 0000000..a78d015 --- /dev/null +++ b/ape_safe/client/mock.py @@ -0,0 +1,100 @@ +from datetime import datetime, timezone +from typing import Dict, Iterator, List, Union + +from ape.contracts import ContractInstance +from ape.types import AddressType, MessageSignature +from ape.utils import ZERO_ADDRESS, ManagerAccessMixin +from eth_utils import keccak + +from ape_safe.client.base import BaseSafeClient +from ape_safe.client.models import ( + SafeApiTxData, + SafeDetails, + SafeTx, + SafeTxConfirmation, + SafeTxID, + SignatureType, + UnexecutedTxData, +) + + +class MockSafeClient(BaseSafeClient, ManagerAccessMixin): + def __init__(self, contract: ContractInstance): + self.contract = contract + self.transactions: Dict[Union[SafeTx, SafeTxID], SafeApiTxData] = {} + self.transactions_by_nonce: Dict[int, List[SafeTxID]] = {} + + @property + def safe_details(self) -> SafeDetails: + slot = keccak(text="fallback_manager.handler.address") + value = self.provider.get_storage_at(self.contract.address, slot) + fallback_address = self.network_manager.ecosystem.decode_address(value[-20:]) + + return SafeDetails( + address=self.contract.address, + nonce=self.get_next_nonce(), + threshold=self.contract.getThreshold(), + owners=self.contract.getOwners(), + masterCopy=self.contract.masterCopy(), + modules=self.modules, + # TODO: Add fallback handler getter + fallbackHandler=fallback_address, + guard=self.guard, + version=self.contract.VERSION(), + ) + + @property + def guard(self) -> AddressType: + return ( + self.contract.getGuard() if "getGuard" in self.contract._view_methods_ else ZERO_ADDRESS + ) + + @property + def modules(self) -> List[AddressType]: + return self.contract.getModules() if "getModules" in self.contract._view_methods_ else [] + + def get_next_nonce(self) -> int: + return self.contract._view_methods_["nonce"]() + + def _all_transactions( + self, + ) -> Iterator[SafeApiTxData]: + for nonce in sorted(self.transactions_by_nonce.keys(), reverse=True): + yield from map(self.transactions.get, self.transactions_by_nonce[nonce]) + + def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: + if safe_tx_data := self.transactions.get(safe_tx_hash): + yield from safe_tx_data.confirmations + + def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): + safe_tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) + safe_tx_data.confirmations.extend( + SafeTxConfirmation( + owner=signer, + submissionDate=datetime.now(timezone.utc), + signature=sig.encode_rsv(), + signatureType=SignatureType.EOA, + ) + for signer, sig in sigs.items() + ) + self.transactions[safe_tx_data.safe_tx_hash] = safe_tx_data + + if safe_tx_data.nonce in self.transactions_by_nonce: + self.transactions_by_nonce[safe_tx_data.nonce].append(safe_tx_data.safe_tx_hash) + else: + self.transactions_by_nonce[safe_tx_data.nonce] = [safe_tx_data.safe_tx_hash] + + def post_signature( + self, + safe_tx_or_hash: Union[SafeTx, SafeTxID], + signer: AddressType, + signature: MessageSignature, + ): + self.transactions[safe_tx_or_hash].confirmations.append( + SafeTxConfirmation( + owner=signer, + submissionDate=datetime.now(timezone.utc), + signature=signature.encode_rsv(), + signatureType=SignatureType.EOA, + ) + ) diff --git a/ape_safe/client/models.py b/ape_safe/client/models.py new file mode 100644 index 0000000..409a243 --- /dev/null +++ b/ape_safe/client/models.py @@ -0,0 +1,123 @@ +from datetime import datetime, timezone +from enum import Enum +from typing import Dict, List, NewType, Optional, Union + +from ape.types import AddressType, HexBytes +from eip712.common import SafeTxV1, SafeTxV2 +from eip712.messages import hash_eip712_message +from pydantic import BaseModel, Field + +SafeTx = Union[SafeTxV1, SafeTxV2] +SafeTxID = NewType("SafeTxID", str) + + +class SafeDetails(BaseModel): + address: AddressType + nonce: int + threshold: int + owners: List[AddressType] + master_copy: AddressType = Field(alias="masterCopy") + modules: List[AddressType] + fallback_handler: AddressType = Field(alias="fallbackHandler") + guard: AddressType + version: str + + +class SignatureType(str, Enum): + APPROVED_HASH = "APPROVED_HASH" + EOA = "EOA" + ETH_SIGN = "ETH_SIGN" + + +class SafeTxConfirmation(BaseModel): + owner: AddressType + submission_date: datetime = Field(alias="submissionDate") + transaction_hash: Optional[HexBytes] = Field(None, alias="transactionHash") + signature: HexBytes + signature_type: Optional[SignatureType] = Field(None, alias="signatureType") + + +class OperationType(int, Enum): + CALL = 0 + DELEGATECALL = 1 + + +class UnexecutedTxData(BaseModel): + safe: AddressType + to: AddressType + value: int + data: Optional[HexBytes] = None + operation: OperationType + gas_token: AddressType = Field(alias="gasToken") + safe_tx_gas: int = Field(alias="safeTxGas") + base_gas: int = Field(alias="baseGas") + gas_price: int = Field(alias="gasPrice") + refund_receiver: AddressType = Field(alias="refundReceiver") + nonce: int + submission_date: datetime = Field(alias="submissionDate") + modified: datetime + safe_tx_hash: SafeTxID = Field(alias="safeTxHash") + confirmations_required: int = Field(alias="confirmationsRequired") + confirmations: List[SafeTxConfirmation] = [] + trusted: bool = True + signatures: Optional[HexBytes] = None + + @classmethod + def from_safe_tx(cls, safe_tx: SafeTx, confirmations_required: int) -> "UnexecutedTxData": + return cls( + safe=safe_tx._verifyingContract_, + submissionDate=datetime.now(timezone.utc), + modified=datetime.now(timezone.utc), + confirmationsRequired=confirmations_required, + safeTxHash=hash_eip712_message(safe_tx).hex(), + **safe_tx._body_["message"], + ) + + @property + def base_tx_dict(self) -> Dict: + return { + "to": self.to, + "value": self.value, + "data": self.data, + "operation": self.operation, + "safeTxGas": self.safe_tx_gas, + "baseGas": self.base_gas, + "gasPrice": self.gas_price, + "gasToken": self.gas_token, + "refundReceiver": self.refund_receiver, + "nonce": self.nonce, + } + + def __str__(self) -> str: + # TODO: Decode data + data_hex = self.data.hex() if self.data else "" + if len(data_hex) > 40: + data_hex = f"{data_hex[:18]}....{data_hex[-18:]}" + + # TODO: Handle MultiSend contract differently + return f"""Tx ID {self.nonce} + type: {self.operation._name_} + from: {self.safe} + to: {self.to} + value: {self.value / 1e18} ether + data: 0x{data_hex} +""" + + +class ExecutedTxData(UnexecutedTxData): + execution_date: datetime = Field(alias="executionDate") + block_number: int = Field(alias="blockNumber") + transaction_hash: HexBytes = Field(alias="transactionHash") + executor: AddressType + is_executed: bool = Field(alias="isExecuted") + is_successful: bool = Field(alias="isSuccessful") + eth_gas_price: int = Field(alias="ethGasPrice") + max_fee_per_gas: Optional[int] = Field(alias="maxFeePerGas") + max_priority_fee_per_gas: Optional[int] = Field(alias="maxPriorityFeePerGas") + gas_used: int = Field(alias="gasUsed") + fee: int + origin: str + data_decoded: Optional[dict] = Field(alias="dataDecoded") + + +SafeApiTxData = Union[ExecutedTxData, UnexecutedTxData] From aded29dba1291798b33e516d3944cbd3c5f5d31d Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 28 Nov 2023 12:53:37 -0600 Subject: [PATCH 082/134] refactor: and fix --- ape_safe/_cli/__init__.py | 10 +-- ape_safe/_cli/click_ext.py | 13 ++-- ape_safe/_cli/pending.py | 141 +++++++++++++++++++----------------- ape_safe/_cli/safe_mgmt.py | 29 +++----- ape_safe/accounts.py | 38 +++++++--- ape_safe/client.py | 0 ape_safe/client/__init__.py | 62 ++++++---------- ape_safe/client/base.py | 54 ++++++++++---- ape_safe/utils.py | 7 +- 9 files changed, 186 insertions(+), 168 deletions(-) delete mode 100644 ape_safe/client.py diff --git a/ape_safe/_cli/__init__.py b/ape_safe/_cli/__init__.py index 92ce15d..58cd73a 100644 --- a/ape_safe/_cli/__init__.py +++ b/ape_safe/_cli/__init__.py @@ -12,8 +12,8 @@ def cli(): """ -cli.add_command(_list) # type: ignore -cli.add_command(add) # type: ignore -cli.add_command(remove) # type: ignore -cli.add_command(all_txns) # type: ignore -cli.add_command(pending) # type: ignore +cli.add_command(_list) +cli.add_command(add) +cli.add_command(remove) +cli.add_command(all_txns) +cli.add_command(pending) diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index 608c13a..9f99273 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -1,8 +1,9 @@ -from ape.cli import ApeCliContextObject, ape_cli_context, existing_alias_argument +import click +from ape import accounts +from ape.cli import ApeCliContextObject, ape_cli_context from click import MissingParameter -from ape import accounts -from ape_safe.accounts import SafeAccount, SafeContainer +from ape_safe.accounts import SafeContainer class SafeCliContext(ApeCliContextObject): @@ -16,7 +17,7 @@ def safes(self) -> SafeContainer: safe_cli_ctx = ape_cli_context(obj_type=SafeCliContext) -def _safe_alias_callback(ctx, param, value): +def _safe_callback(ctx, param, value): # NOTE: For some reason, the Cli CTX object is not the SafeCliCtx yet at this point. safes = accounts.containers["safe"] if value is None: @@ -31,6 +32,4 @@ def _safe_alias_callback(ctx, param, value): return accounts.load(value) -safe_alias_argument = existing_alias_argument( - account_type=SafeAccount, callback=_safe_alias_callback, required=False -) +safe_option = click.option("--safe", callback=_safe_callback) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 1ec77c5..c61b083 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -1,17 +1,12 @@ from typing import Optional import click +import rich from ape.api import AccountAPI -from ape.cli import ( - NetworkBoundCommand, - existing_alias_argument, - get_user_selected_account, - network_option, -) +from ape.cli import NetworkBoundCommand, get_user_selected_account, network_option from click.exceptions import BadOptionUsage -from ape_safe._cli.click_ext import SafeCliContext, safe_alias_argument, safe_cli_ctx -from ape_safe.accounts import SafeAccount +from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option @click.group() @@ -24,18 +19,18 @@ def pending(): @pending.command("list", cls=NetworkBoundCommand) @safe_cli_ctx @network_option() -@safe_alias_argument -def _list(cli_ctx: SafeCliContext, network, alias) -> None: +@safe_option +def _list(cli_ctx: SafeCliContext, network, safe) -> None: """ View pending transactions for a Safe """ _ = network # Needed for NetworkBoundCommand - safe = alias # Handled in callback - - for safe_tx, confirmations in safe.pending_transactions(): - click.echo( - f"Transaction {safe_tx.nonce}: ({len(confirmations)}/{safe.confirmations_required})" + for safe_tx in safe.client.get_transactions(confirmed=False): + rich.print( + f"Transaction {safe_tx.nonce}: " + f"({len(safe_tx.confirmations)}/{safe.confirmations_required}) " + f"safe_tx_hash={safe_tx.safe_tx_hash}" ) @@ -43,14 +38,16 @@ def _list(cli_ctx: SafeCliContext, network, alias) -> None: # all happens here EXCEPT if a pending tx is executable and no # value of `--execute` was provided. def _handle_execute_cli_arg(ctx, param, val): - # Account alias - execute using this account. + """ + Either returns the account or ``False`` meaning don't execute + """ + if val is None: # Was not given any value. # If it is determined in `pending` that a tx can execute, # the user will get prompted. # Avoid this by always doing `--execute false`. - - return val + return None elif val in ctx.obj.account_manager.aliases: return ctx.obj.account_manager.load(val) @@ -79,70 +76,59 @@ def _handle_execute_cli_arg(ctx, param, val): @pending.command(cls=NetworkBoundCommand) @safe_cli_ctx @network_option() -@safe_alias_argument -@click.argument("txn_id") +@safe_option +@click.argument("nonce", type=int) @click.option("--execute", callback=_handle_execute_cli_arg) -def approve(cli_ctx: SafeCliContext, network, alias, txn_id, execute): +def approve(cli_ctx: SafeCliContext, network, safe, nonce, execute): _ = network # Needed for NetworkBoundCommand - safe = alias # Handled in callback submitter: Optional[AccountAPI] = execute if isinstance(execute, AccountAPI) else None - txn = safe.pending_transactions - - # - # # Add signatures, if was requested to do so. - # - # if sign_with_local_signers: - # threshold = safe.confirmations_required - 1 - # num_confirmations = len(confirmations) - # if num_confirmations == threshold: - # proceed = click.prompt("Additional signatures not required. Proceed?") - # if proceed: - # safe.add_signatures(safe_tx, confirmations) - # cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") - # - # elif num_confirmations < threshold: - # safe.add_signatures(safe_tx, confirmations) - # cli_ctx.logger.success(f"Signature added to 'Transaction {safe_tx.nonce}'.") - # - # else: - # cli_ctx.logger.error("Unable to add signatures. Transaction fully signed.") - # - # # NOTE: Lazily check signatures. - # signatures = None - # - # # The user did provider a value for `--execute` however we are able to - # # So we prompt them. - # if execute is None and submitter is None: - # # Check if we _can_ execute and ask the user. - # signatures = safe.get_api_confirmations(safe_tx) - # do_execute = ( - # len(safe.local_signers) > 0 - # and len(signatures) >= safe.confirmations_required - # and click.confirm(f"Submit Transaction {safe_tx.nonce}") - # ) - # if do_execute: - # submitter = get_user_selected_account(account_type=safe.local_signers) - # - # if submitter: - # # NOTE: Signatures may have gotten set above already. - # signatures = signatures or safe.get_api_confirmations(safe_tx) - # - # exc_tx = safe.create_execute_transaction(safe_tx, signatures) - # submitter.call(exc_tx) + txn = next(safe.client.get_transactions(confirmed=False, starting_nonce=nonce), None) + if not txn: + cli_ctx.abort(f"Pending transaction '{nonce}' not found.") + + safe_tx = safe.create_safe_tx(**txn.dict()) + num_confirmations = len(txn.confirmations) + signatures_added = {} + + if num_confirmations < safe.confirmations_required: + signatures_added = safe.add_signatures(safe_tx, txn.confirmations) + accounts_used_str = ", ".join(list(signatures_added.keys())) + cli_ctx.logger.success( + f"Signatures added to transaction '{safe_tx.nonce}' " + f"using accounts '{accounts_used_str}'." + ) + num_confirmations += len(signatures_added) + + if execute is None and submitter is None: + # Check if we _can_ execute and ask the user. + do_execute = ( + len(safe.local_signers) > 0 + and num_confirmations >= safe.confirmations_required + and click.confirm(f"Submit transaction '{safe_tx.nonce}'") + ) + if do_execute: + # The user did provider a value for `--execute` however we are able to + # So we prompt them. + submitter = get_user_selected_account(account_type=safe.local_signers) + + if submitter: + signatures = {c.owner: c.signature for c in txn.confirmations} + signatures = {**signatures, **signatures_added} + exc_tx = safe.create_execute_transaction(safe_tx, signatures) + submitter.call(exc_tx) @pending.command(cls=NetworkBoundCommand) @safe_cli_ctx @network_option() -@safe_alias_argument +@safe_option @click.argument("txn-ids", type=int, nargs=-1) -def reject(cli_ctx: SafeCliContext, network, alias, txn_ids): +def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): """ Reject one or more pending transactions """ _ = network # Needed for NetworkBoundCommand - safe = cli_ctx.account_manager.load(alias) pending_transactions = safe.client.get_transactions(starting_nonce=safe.next_nonce) for txn_id in txn_ids: @@ -154,3 +140,22 @@ def reject(cli_ctx: SafeCliContext, network, alias, txn_ids): if click.confirm(f"{txn}\nCancel Transaction?"): safe.transfer(safe, "0 ether", nonce=txn_id, submit_transaction=False) + + +@pending.command(cls=NetworkBoundCommand) +@safe_cli_ctx +@network_option() +@safe_option +@click.argument("nonce", type=int) +def show_confs(cli_ctx, network, safe, nonce): + """ + Show existing confirmations + """ + _ = network # Needed for NetworkBoundCommand + txn = next(safe.client.get_transactions(confirmed=False, starting_nonce=nonce), None) + if not txn: + cli_ctx.abort(f"Pending transaction '{nonce}' not found.") + + rich.print(f"Showing confirmations for transaction '{txn.nonce}'") + for idx, conf in enumerate(txn.confirmations): + rich.print(f"Confirmation {idx + 1} owner={conf.owner}") diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py index b42f7e5..0d78026 100644 --- a/ape_safe/_cli/safe_mgmt.py +++ b/ape_safe/_cli/safe_mgmt.py @@ -1,15 +1,9 @@ import click -from ape.cli import ( - NetworkBoundCommand, - existing_alias_argument, - network_option, - non_existing_alias_argument, -) +from ape.cli import NetworkBoundCommand, network_option, non_existing_alias_argument from ape.exceptions import ChainError from ape.types import AddressType -from click import BadArgumentUsage -from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx +from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option from ape_safe.client import ExecutedTxData @@ -82,20 +76,15 @@ def add(cli_ctx: SafeCliContext, network, address, alias): @click.command() @safe_cli_ctx -@existing_alias_argument() -def remove(cli_ctx: SafeCliContext, alias): +@safe_option +def remove(cli_ctx: SafeCliContext, safe): """ Stop tracking a locally-tracked Safe """ - if alias not in cli_ctx.safes.aliases: - raise BadArgumentUsage(f"There is no safe with the alias `{alias}`.") - - address = cli_ctx.safes.load_account(alias).address - if click.confirm(f"Remove safe {address} ({alias})"): - cli_ctx.safes.delete_account(alias) - - cli_ctx.logger.success(f"Safe '{address}' ({alias}) removed.") + if click.confirm(f"Remove safe {safe.address} ({safe.alias})"): + cli_ctx.safes.delete_account(safe.alias) + cli_ctx.logger.success(f"Safe '{safe.address}' ({safe.alias}) removed.") @click.command(cls=NetworkBoundCommand) @@ -109,7 +98,9 @@ def all_txns(cli_ctx: SafeCliContext, network, address, confirmed): """ _ = network # Needed for NetworkBoundCommand - client = cli_ctx.safes._get_client(address) + + # NOTE: Create a client to support non-local safes. + client = cli_ctx.safes.create_client(address) for txn in client.get_transactions(confirmed=confirmed): if isinstance(txn, ExecutedTxData): diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 3ffd6d3..58cafa4 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -1,7 +1,7 @@ import json from itertools import islice from pathlib import Path -from typing import Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union, cast +from typing import Dict, Iterable, Iterator, List, Mapping, Optional, Tuple, Type, Union, cast from ape.api import AccountAPI, AccountContainerAPI, ReceiptAPI, TransactionAPI from ape.api.address import BaseAddress @@ -133,7 +133,7 @@ def delete_account(self, alias: str): """ self._get_path(alias).unlink(missing_ok=True) - def _get_client(self, key: str) -> BaseSafeClient: + def create_client(self, key: str) -> BaseSafeClient: if key in self.aliases: safe = self.load_account(key) return safe.client @@ -306,9 +306,6 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) safe_tx = {**safe_tx, **{k: v for k, v in safe_tx_kwargs.items() if k in safe_tx}} return self.safe_tx_def(**safe_tx) - def get_transaction(self, txn_id: SafeTxID): - return self.client.get_transactions() - def pending_transactions(self) -> Iterator[Tuple[SafeTx, List[SafeTxConfirmation]]]: for executed_tx in self.client.get_transactions(confirmed=False): yield self.create_safe_tx(**executed_tx.dict()), executed_tx.confirmations @@ -334,12 +331,15 @@ def local_signers(self) -> List[AccountAPI]: def create_execute_transaction( self, safe_tx: SafeTx, - signatures: Dict[AddressType, MessageSignature], + signatures: Mapping[AddressType, Union[MessageSignature, HexBytes]], **txn_options, ) -> TransactionAPI: exec_args = list(safe_tx._body_["message"].values())[:-1] # NOTE: Skip `nonce` encoded_signatures = HexBytes( - b"".join(sig.encode_rsv() for sig in order_by_signer(signatures)) + b"".join( + sig.encode_rsv() if isinstance(sig, MessageSignature) else sig + for sig in order_by_signer(signatures) + ) ) # NOTE: executes a `ProviderAPI.prepare_transaction`, which may produce `ContractLogicError` @@ -431,7 +431,12 @@ def _impersonated_call(self, txn: TransactionAPI, **safe_tx_and_call_kwargs) -> ) return self.contract.execTransaction( *safe_tx_exec_args[:-1], # NOTE: Skip nonce - HexBytes(b"".join(sig.encode_rsv() for sig in order_by_signer(signatures))), + HexBytes( + b"".join( + sig.encode_rsv() if isinstance(sig, MessageSignature) else sig + for sig in order_by_signer(signatures) + ) + ), **safe_tx_and_call_kwargs, ) @@ -455,6 +460,7 @@ def call( # type: ignore[override] return super().call(txn, **call_kwargs) def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: + # TODO: This signature is wrong. safe_tx.to = safe_tx.to or ZERO_ADDRESS safe_tx_hash = hash_eip712_message(safe_tx).hex() try: @@ -469,7 +475,7 @@ def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSig for conf in client_confirmations } - def _contract_approvals(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: + def _contract_approvals(self, safe_tx: SafeTx) -> Mapping[AddressType, MessageSignature]: safe_tx_exec_args = _safe_tx_exec_args(safe_tx) safe_tx_hash = self.contract.getTransactionHash(*safe_tx_exec_args) @@ -646,13 +652,18 @@ def skip_signer(signer: AccountAPI): # Return None so that Ape does not try to submit the transaction. return None - def add_signatures(self, safe_tx: SafeTx, confirmations: List[SafeTxConfirmation]): + def add_signatures( + self, safe_tx: SafeTx, confirmations: List[SafeTxConfirmation] + ) -> Dict[AddressType, Union[MessageSignature, HexBytes]]: if not self.local_signers: raise ApeSafeError("Cannot sign without local signers.") + amount_needed = self.confirmations_required - len(confirmations) signers = [ - ls for ls in self.local_signers if ls.address not in [c.owner for c in confirmations] - ] + acc for acc in self.local_signers if acc.address not in [c.owner for c in confirmations] + ][:amount_needed] + + signatures: Dict[AddressType, Union[MessageSignature, HexBytes]] = {} for signer in signers: if not (tx_hash_result := next((c.transaction_hash for c in confirmations), None)): tx_hash_result = self.contract.getTransactionHash(*safe_tx) @@ -664,3 +675,6 @@ def add_signatures(self, safe_tx: SafeTx, confirmations: List[SafeTxConfirmation signature = signer.sign_message(safe_tx.signable_message) if signature: self.client.post_signature(cast(SafeTxID, tx_hash), signer.address, signature) + signatures[signer.address] = signature + + return signatures diff --git a/ape_safe/client.py b/ape_safe/client.py deleted file mode 100644 index e69de29..0000000 diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index 3dd37eb..1be6bb5 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -2,7 +2,6 @@ from functools import reduce from typing import Dict, Iterator, Optional, Union -import requests from ape.types import AddressType, HexBytes, MessageSignature from eip712.common import SafeTxV1, SafeTxV2 from eip712.messages import hash_eip712_message @@ -56,26 +55,22 @@ def __init__( self.address = address if override_url: - self.transaction_service_url = override_url + tx_service_url = override_url elif chain_id: if chain_id not in TRANSACTION_SERVICE_URL: raise ClientUnsupportedChainError(chain_id) - self.transaction_service_url = TRANSACTION_SERVICE_URL.get( # type: ignore[assignment] - chain_id - ) + tx_service_url = TRANSACTION_SERVICE_URL.get(chain_id) # type: ignore[assignment] else: raise ValueError("Must provide one of chain_id or override_url.") + super().__init__(tx_service_url) + @property def safe_details(self) -> SafeDetails: - url = f"{self.transaction_service_url}/api/v1/safes/{self.address}" - response = requests.get(url) - if not response.ok: - raise ClientResponseError(url, response) - + response = self._get(f"safes/{self.address}") return SafeDetails.parse_obj(response.json()) def get_next_nonce(self) -> int: @@ -86,12 +81,9 @@ def _all_transactions(self) -> Iterator[SafeApiTxData]: confirmed: Confirmed if True, not confirmed if False, both if None """ - url = f"{self.transaction_service_url}/api/v1/safes/{self.address}/transactions" + url = f"safes/{self.address}/transactions" while url: - response = requests.get(url) - if not response.ok: - raise ClientResponseError(url, response) - + response = self._get(url) data = response.json() for txn in data.get("results"): @@ -104,15 +96,9 @@ def _all_transactions(self) -> Iterator[SafeApiTxData]: url = data.get("next") def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: - url = ( - f"{self.transaction_service_url}/api" - f"/v1/multisig-transactions/{str(safe_tx_hash)}/confirmations" - ) + url = f"multisig-transactions/{str(safe_tx_hash.replace('8', '0'))}/confirmations" while url: - response = requests.get(url) - if not response.ok: - raise ClientResponseError(url, response) - + response = self._get(url) data = response.json() yield from map(SafeTxConfirmation.parse_obj, data.get("results")) url = data.get("next") @@ -121,7 +107,8 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) tx_data.signatures = HexBytes( reduce( - lambda raw_sig, next_sig: raw_sig + next_sig.encode_rsv(), + lambda raw_sig, next_sig: raw_sig + + (next_sig.encode_rsv() if isinstance(next_sig, MessageSignature) else next_sig), order_by_signer(sigs), b"", ) @@ -138,12 +125,9 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna else: post_dict[key] = value - url = f"{self.transaction_service_url}/api/v1/safes/{tx_data.safe}/multisig-transactions" + url = f"safes/{tx_data.safe}/multisig-transactions" json_data = {"origin": "ApeWorX/ape-safe", **post_dict} - response = requests.post(url, json=json_data) - - if not response.ok: - raise ClientResponseError(url, response) + self._post(url, json=json_data) def post_signature( self, @@ -160,19 +144,15 @@ def post_signature( if not isinstance(safe_tx_hash, str): raise TypeError("Expecting str-like type for 'safe_tx_hash'.") - url = ( - f"{self.transaction_service_url}/api" - f"/v1/multisig-transactions/{safe_tx_hash}/confirmations" - ) - response = requests.post( - url, json={"origin": "ApeWorX/ape-safe", "signature": signature.encode_rsv().hex()} - ) - - if not response.ok: - if "The requested resource was not found on this server" in response.text: - raise MultisigTransactionNotFoundError(safe_tx_hash, url, response) + url = f"multisig-transactions/{safe_tx_hash}/confirmations" + json_data = {"origin": "ApeWorX/ape-safe", "signature": signature.encode_rsv().hex()} + try: + self._post(url, json=json_data) + except ClientResponseError as err: + if "The requested resource was not found on this server" in err.response.text: + raise MultisigTransactionNotFoundError(safe_tx_hash, url, err.response) from err - raise ClientResponseError(url, response) + raise # The error from BaseClient we are already raising (no changes) __all__ = [ diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py index 1748b59..09b2fd9 100644 --- a/ape_safe/client/base.py +++ b/ape_safe/client/base.py @@ -1,7 +1,9 @@ from abc import ABC, abstractmethod from typing import Dict, Iterator, Optional, Set, Union +import requests from ape.types import AddressType, MessageSignature +from requests import Response from ape_safe.client.models import ( ExecutedTxData, @@ -12,9 +14,15 @@ SafeTxID, UnexecutedTxData, ) +from ape_safe.exceptions import ClientResponseError class BaseSafeClient(ABC): + def __init__(self, transaction_service_url: str): + self.transaction_service_url = transaction_service_url + + """Abstract methods""" + @property @abstractmethod def safe_details(self) -> SafeDetails: @@ -28,6 +36,25 @@ def get_next_nonce(self) -> int: def _all_transactions(self) -> Iterator[SafeApiTxData]: ... + @abstractmethod + def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: + ... + + @abstractmethod + def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): + ... + + @abstractmethod + def post_signature( + self, + safe_tx_or_hash: Union[SafeTx, SafeTxID], + signer: AddressType, + signature: MessageSignature, + ): + ... + + """Shared methods""" + def get_transactions( self, confirmed: Optional[bool] = None, @@ -68,19 +95,18 @@ def get_transactions( yield txn - @abstractmethod - def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: - ... + """Request methods""" - @abstractmethod - def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): - ... + def _get(self, url: str) -> Response: + return self._request("GET", url) - @abstractmethod - def post_signature( - self, - safe_tx_or_hash: Union[SafeTx, SafeTxID], - signer: AddressType, - signature: MessageSignature, - ): - ... + def _post(self, url: str, json: Dict) -> Response: + return self._request("POST", url, json=json) + + def _request(self, method: str, url: str, json: Optional[Dict] = None) -> Response: + api_url = f"{self.transaction_service_url}/api/v1/{url}" + response = requests.request(method, api_url, json=json) + if not response.ok: + raise ClientResponseError(api_url, response) + + return response diff --git a/ape_safe/utils.py b/ape_safe/utils.py index dfdff50..a2a671d 100644 --- a/ape_safe/utils.py +++ b/ape_safe/utils.py @@ -1,10 +1,13 @@ -from typing import Dict, List +from typing import List, Mapping, Union from ape.types import AddressType, MessageSignature from eth_utils import to_int +from hexbytes import HexBytes -def order_by_signer(signatures: Dict[AddressType, MessageSignature]) -> List[MessageSignature]: +def order_by_signer( + signatures: Mapping[AddressType, Union[MessageSignature, HexBytes]] +) -> List[Union[MessageSignature, HexBytes]]: # NOTE: Must order signatures in ascending order of signer address (converted to int) def addr_to_int(a: AddressType) -> int: return to_int(hexstr=a) From 0b2421f0477ee86d988ca131de3d57153b0f606b Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 28 Nov 2023 13:06:31 -0600 Subject: [PATCH 083/134] docs: update docs --- .pre-commit-config.yaml | 4 +-- README.md | 31 +++++++++------------- ape_safe/_cli/safe_mgmt.py | 53 ++++++++++++++++++++++---------------- setup.py | 4 +-- 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f209910..42e2495 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: isort - repo: https://github.com/psf/black - rev: 23.10.1 + rev: 23.11.0 hooks: - id: black name: black @@ -21,7 +21,7 @@ repos: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.6.1 + rev: v1.7.1 hooks: - id: mypy additional_dependencies: [types-requests, types-setuptools, pydantic] diff --git a/README.md b/README.md index ce97fa0..54b660a 100644 --- a/README.md +++ b/README.md @@ -39,25 +39,18 @@ $ python3 setup.py install To use the plugin, first use the CLI extension to add a safe you created: ```bash -# Add the safe located at "my-safe.eth" ENS on the ethereum mainnet network -$ ape safe add --network ethereum:mainnet "my-safe.eth" my-safe -Safe Found - network: ethereum:mainnet - address: 0x1234....AbCd - version: 1.3.0 - required_confirmations: 2 - signers: - - 0x2345....BcDe - - 0x3456....CdEf - - 0x4567....DeFg - -Add safe [y/N]: y +ape safe add --network ethereum:mainnet "my-safe.eth" my-safe ``` -Once you've added the safe, you can use the multisig inside any of your ape scripts or the console: +If you only add 1 safe, you will not have to specify in future commands. +Otherwise, for most commands, specify the safe using the `--safe` option by alias. + +Once you've added a safe, you manage pending transactions: ```python from ape_safe import multisend +from ape import accounts +from ape_tokens import tokens safe = accounts.load("my-safe") @@ -76,13 +69,13 @@ txn.add(vault.deposit, amount) txn(sender=safe) ``` -You can then use the CLI extension to view and sign for pending transactions: +You can use the CLI extension to view and sign for pending transactions: ```bash -$ ape safe pending --network ethereum:mainnet my-safe -Local Signer(s) detected! -Do you want to sign unconfirmed transactions [y/N]: y -... # Sign with any local signers that have not confirmed yet +ape safe pending list --network ethereum:goerli:alchemy +ape safe pending show-confs 2 --network ethereum:goerli:alchemy +ape safe pending approve 3 --network ethereum:goerli:alchemy +ape safe pending reject 4 --network ethereum:goerli:alchemy ``` ## Development diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py index 0d78026..203855d 100644 --- a/ape_safe/_cli/safe_mgmt.py +++ b/ape_safe/_cli/safe_mgmt.py @@ -1,44 +1,53 @@ import click from ape.cli import NetworkBoundCommand, network_option, non_existing_alias_argument -from ape.exceptions import ChainError +from ape.exceptions import ChainError, ProviderNotConnectedError from ape.types import AddressType from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option from ape_safe.client import ExecutedTxData -@click.command(name="list", cls=NetworkBoundCommand) +@click.command(name="list") @safe_cli_ctx -@network_option() +@network_option(default=None) def _list(cli_ctx: SafeCliContext, network): """ Show locally-tracked Safes """ - _ = network # Needed for NetworkBoundCommand - number_of_safes = len(cli_ctx.safes) + network_ctx = None + if network is not None: + network_ctx = cli_ctx.network_manager.parse_network_choice(network) + network_ctx.__enter__() + + try: + number_of_safes = len(cli_ctx.safes) + + if number_of_safes == 0: + cli_ctx.logger.warning("No Safes found.") + return - if number_of_safes == 0: - cli_ctx.logger.warning("No Safes found.") - return + header = f"Found {number_of_safes} Safe" + header += "s:" if number_of_safes > 1 else ":" + click.echo(header) - header = f"Found {number_of_safes} Safe" - header += "s:" if number_of_safes > 1 else ":" - click.echo(header) + for account in cli_ctx.safes: + extras = [] + if account.alias: + extras.append(f"alias: '{account.alias}'") - for account in cli_ctx.safes: - extras = [] - if account.alias: - extras.append(f"alias: '{account.alias}'") + try: + extras.append(f"version: '{account.version}'") + except (ChainError, ProviderNotConnectedError): + # Not connected to the network where safe is deployed + extras.append("version: (not connected)") - try: - extras.append(f"version: '{account.version}'") - except ChainError: - # Not connected to the network where safe is deployed - extras.append("version: (not connected)") + extras_display = f" ({', '.join(extras)})" if extras else "" + click.echo(f" {account.address}{extras_display}") - extras_display = f" ({', '.join(extras)})" if extras else "" - click.echo(f" {account.address}{extras_display}") + finally: + if network_ctx: + network_ctx.__exit__() @click.command(cls=NetworkBoundCommand) diff --git a/setup.py b/setup.py index 318a74e..85c8c24 100644 --- a/setup.py +++ b/setup.py @@ -12,8 +12,8 @@ "ape-solidity", # Needed for compiling the Safe contracts ], "lint": [ - "black>=23.10.1,<24", # Auto-formatter and linter - "mypy>=1.6.1,<2", # Static type analyzer + "black>=23.11.0,<24", # Auto-formatter and linter + "mypy>=1.7.1,<2", # Static type analyzer "types-requests", # Needed for mypy type shed "types-setuptools", # Needed for mypy type shed "flake8>=6.1.0,<7", # Style linter From 70840ba8b9d0aba0278efab560377e8459218825 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 29 Nov 2023 13:50:19 -0600 Subject: [PATCH 084/134] fix: part of hash fix --- README.md | 12 ++++++++++++ ape_safe/_cli/click_ext.py | 7 +++++-- ape_safe/_cli/pending.py | 10 +++++----- ape_safe/_cli/safe_mgmt.py | 9 ++++++--- ape_safe/accounts.py | 22 ++++++++++++++++------ ape_safe/client/__init__.py | 2 +- 6 files changed, 45 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 54b660a..376fe06 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,18 @@ txn.add(vault.deposit, amount) txn(sender=safe) ``` +Propose a simple transaction by using `submit=False` as a transaction kwargs so it can await signatures from other signers: + +```python +from ape import accounts + +safe = accounts.load("my-safe") +other = accounts.load("other") +safe.transfer(other, "1 wei", submit=False) +``` + +**NOTE**: It may error saying the transaction was not fully signed but that is ok. + You can use the CLI extension to view and sign for pending transactions: ```bash diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index 9f99273..3c0badd 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -1,7 +1,7 @@ import click from ape import accounts from ape.cli import ApeCliContextObject, ape_cli_context -from click import MissingParameter +from click import MissingParameter, BadOptionUsage from ape_safe.accounts import SafeContainer @@ -28,8 +28,11 @@ def _safe_callback(ctx, param, value): options = ", ".join(safes.aliases) raise MissingParameter(message=f"Must specify safe to use (one of '{options}').") - else: + elif value in safes.aliases: return accounts.load(value) + else: + raise BadOptionUsage("--safe", f"No safe with alias '{value}'") + safe_option = click.option("--safe", callback=_safe_callback) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index c61b083..a3a4219 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -26,11 +26,11 @@ def _list(cli_ctx: SafeCliContext, network, safe) -> None: """ _ = network # Needed for NetworkBoundCommand - for safe_tx in safe.client.get_transactions(confirmed=False): + for tx in safe.client.get_transactions(confirmed=False): rich.print( - f"Transaction {safe_tx.nonce}: " - f"({len(safe_tx.confirmations)}/{safe.confirmations_required}) " - f"safe_tx_hash={safe_tx.safe_tx_hash}" + f"Transaction {tx.nonce}: " + f"({len(tx.confirmations)}/{safe.confirmations_required}) " + f"safe_tx_hash={tx.safe_tx_hash}" ) @@ -86,7 +86,7 @@ def approve(cli_ctx: SafeCliContext, network, safe, nonce, execute): if not txn: cli_ctx.abort(f"Pending transaction '{nonce}' not found.") - safe_tx = safe.create_safe_tx(**txn.dict()) + safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) num_confirmations = len(txn.confirmations) signatures_added = {} diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py index 203855d..df9d18d 100644 --- a/ape_safe/_cli/safe_mgmt.py +++ b/ape_safe/_cli/safe_mgmt.py @@ -91,9 +91,12 @@ def remove(cli_ctx: SafeCliContext, safe): Stop tracking a locally-tracked Safe """ - if click.confirm(f"Remove safe {safe.address} ({safe.alias})"): - cli_ctx.safes.delete_account(safe.alias) - cli_ctx.logger.success(f"Safe '{safe.address}' ({safe.alias}) removed.") + alias = safe.alias + address = safe.address + + if click.confirm(f"Remove safe {address} ({alias})"): + cli_ctx.safes.delete_account(alias) + cli_ctx.logger.success(f"Safe '{address}' ({alias}) removed.") @click.command(cls=NetworkBoundCommand) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 58cafa4..db8d041 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -7,7 +7,7 @@ from ape.api.address import BaseAddress from ape.api.networks import LOCAL_NETWORK_NAME, ForkedNetworkAPI from ape.contracts import ContractInstance -from ape.exceptions import ProviderNotConnectedError +from ape.exceptions import ProviderNotConnectedError, SignatureError from ape.logging import logger from ape.managers.accounts import AccountManager, TestAccountManager from ape.types import AddressType, HexBytes, MessageSignature, SignableMessage @@ -169,6 +169,12 @@ def _safe_tx_exec_args(safe_tx: SafeTx) -> List: return list(safe_tx._body_["message"].values()) +def hash_transaction(safe_tx: SafeTx) -> str: + safe_tx.to = safe_tx.to or ZERO_ADDRESS + safe_tx.data = safe_tx.data or b"" + return hash_eip712_message(safe_tx).hex() + + class SafeAccount(AccountAPI): account_file_path: Path # NOTE: Cache any relevant data here @@ -303,12 +309,13 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) "gasToken": ZERO_ADDRESS, "refundReceiver": ZERO_ADDRESS, } + safe_tx = {**safe_tx, **{k: v for k, v in safe_tx_kwargs.items() if k in safe_tx}} return self.safe_tx_def(**safe_tx) def pending_transactions(self) -> Iterator[Tuple[SafeTx, List[SafeTxConfirmation]]]: for executed_tx in self.client.get_transactions(confirmed=False): - yield self.create_safe_tx(**executed_tx.dict()), executed_tx.confirmations + yield self.create_safe_tx(**executed_tx.dict(by_alias=True)), executed_tx.confirmations @property def local_signers(self) -> List[AccountAPI]: @@ -457,12 +464,14 @@ def call( # type: ignore[override] if impersonate: return self._impersonated_call(txn, **call_kwargs) - return super().call(txn, **call_kwargs) + try: + return super().call(txn, **call_kwargs) + except SignatureError: + # TODO: Create an intermediate receipt object + return None # type: ignore def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: - # TODO: This signature is wrong. - safe_tx.to = safe_tx.to or ZERO_ADDRESS - safe_tx_hash = hash_eip712_message(safe_tx).hex() + safe_tx_hash = hash_transaction(safe_tx) try: client_confirmations = self.client.get_confirmations(safe_tx_hash) except SafeClientException: @@ -487,6 +496,7 @@ def _contract_approvals(self, safe_tx: SafeTx) -> Mapping[AddressType, MessageSi def _all_approvals(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: approvals = self.get_api_confirmations(safe_tx) + # NOTE: Do this last because it should take precedence approvals.update(self._contract_approvals(safe_tx)) return approvals diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index 1be6bb5..653826c 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -114,7 +114,7 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna ) ) post_dict: Dict = {} - for key, value in tx_data.dict().items(): + for key, value in tx_data.dict(by_alias=True).items(): if isinstance(value, HexBytes): post_dict[key] = value.hex() elif isinstance(value, OperationType): From b8d3cbb6d01b90163ed45d66ae8283b8a6ad0949 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 30 Nov 2023 13:36:03 -0600 Subject: [PATCH 085/134] feat: execute cli --- README.md | 1 + ape_safe/_cli/click_ext.py | 2 +- ape_safe/_cli/pending.py | 43 +++++++++++++++++++++++++++++++------- ape_safe/_cli/safe_mgmt.py | 2 +- ape_safe/accounts.py | 25 +++++++++++----------- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 376fe06..64e4070 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ ape safe pending list --network ethereum:goerli:alchemy ape safe pending show-confs 2 --network ethereum:goerli:alchemy ape safe pending approve 3 --network ethereum:goerli:alchemy ape safe pending reject 4 --network ethereum:goerli:alchemy +ape safe pending execute 4 --network ethereum:goerli:alchemy --submitter metamask0 ``` ## Development diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index 3c0badd..8f1ab96 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -1,7 +1,7 @@ import click from ape import accounts from ape.cli import ApeCliContextObject, ape_cli_context -from click import MissingParameter, BadOptionUsage +from click import BadOptionUsage, MissingParameter from ape_safe.accounts import SafeContainer diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index a3a4219..79d3e7e 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -49,7 +49,20 @@ def _handle_execute_cli_arg(ctx, param, val): # Avoid this by always doing `--execute false`. return None - elif val in ctx.obj.account_manager.aliases: + elif submitter := _load_submitter(ctx, param, val): + return submitter + + # Saying "no, do not execute", even if we could. + elif val.lower() in ("false", "f", "0"): + return False + + raise BadOptionUsage( + "--execute", f"`--execute` value '{val}` not a boolean or account identifier." + ) + + +def _load_submitter(ctx, param, val): + if val in ctx.obj.account_manager.aliases: return ctx.obj.account_manager.load(val) # Account address - execute using this account. @@ -64,13 +77,7 @@ def _handle_execute_cli_arg(ctx, param, val): return get_user_selected_account(account_type=safe.local_signers) - # Saying "no, do not execute", even if we could. - elif val.lower() in ("false", "f", "0"): - return False - - raise BadOptionUsage( - "--execute", f"`--execute` value '{val}` not a boolean or account identifier." - ) + return None @pending.command(cls=NetworkBoundCommand) @@ -118,6 +125,26 @@ def approve(cli_ctx: SafeCliContext, network, safe, nonce, execute): submitter.call(exc_tx) +@pending.command(cls=NetworkBoundCommand) +@safe_cli_ctx +@network_option() +@safe_option +@click.argument("nonce", type=int) +@click.option("--submitter", callback=_load_submitter) +def execute(cli_ctx, network, safe, nonce, submitter): + """ + Execute a transaction + """ + txn = next(safe.client.get_transactions(confirmed=False, starting_nonce=nonce), None) + if not txn: + cli_ctx.abort(f"Pending transaction '{nonce}' not found.") + + safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) + signatures = {c.owner: c.signature for c in txn.confirmations} + exc_tx = safe.create_execute_transaction(safe_tx, signatures) + submitter.call(exc_tx) + + @pending.command(cls=NetworkBoundCommand) @safe_cli_ctx @network_option() diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py index df9d18d..62d9ac5 100644 --- a/ape_safe/_cli/safe_mgmt.py +++ b/ape_safe/_cli/safe_mgmt.py @@ -47,7 +47,7 @@ def _list(cli_ctx: SafeCliContext, network): finally: if network_ctx: - network_ctx.__exit__() + network_ctx.__exit__(None) @click.command(cls=NetworkBoundCommand) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index db8d041..08d31bc 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -14,7 +14,6 @@ from ape.utils import ZERO_ADDRESS, cached_property from ape_ethereum.transactions import TransactionType from eip712.common import create_safe_tx_def -from eip712.messages import hash_eip712_message from eth_utils import keccak, to_bytes, to_int from ethpm_types import ContractType @@ -169,12 +168,6 @@ def _safe_tx_exec_args(safe_tx: SafeTx) -> List: return list(safe_tx._body_["message"].values()) -def hash_transaction(safe_tx: SafeTx) -> str: - safe_tx.to = safe_tx.to or ZERO_ADDRESS - safe_tx.data = safe_tx.data or b"" - return hash_eip712_message(safe_tx).hex() - - class SafeAccount(AccountAPI): account_file_path: Path # NOTE: Cache any relevant data here @@ -301,7 +294,7 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) safe_tx = { "to": txn.receiver if txn else self.address, # Self-call, e.g. rejection "value": txn.value if txn else 0, - "data": txn.data if txn else b"", + "data": (txn.data or b"") if txn else b"", "nonce": self.next_nonce, "operation": 0, "safeTxGas": 0, @@ -309,8 +302,10 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) "gasToken": ZERO_ADDRESS, "refundReceiver": ZERO_ADDRESS, } - - safe_tx = {**safe_tx, **{k: v for k, v in safe_tx_kwargs.items() if k in safe_tx}} + safe_tx = { + **safe_tx, + **{k: v for k, v in safe_tx_kwargs.items() if k in safe_tx and v is not None}, + } return self.safe_tx_def(**safe_tx) def pending_transactions(self) -> Iterator[Tuple[SafeTx, List[SafeTxConfirmation]]]: @@ -471,9 +466,9 @@ def call( # type: ignore[override] return None # type: ignore def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: - safe_tx_hash = hash_transaction(safe_tx) + safe_tx_id = get_safe_tx_hash(safe_tx) try: - client_confirmations = self.client.get_confirmations(safe_tx_hash) + client_confirmations = self.client.get_confirmations(safe_tx_id) except SafeClientException: return {} @@ -688,3 +683,9 @@ def add_signatures( signatures[signer.address] = signature return signatures + + +def get_safe_tx_hash(safe_tx) -> SafeTxID: + return cast( + SafeTxID, HexBytes(keccak(b"".join([bytes.fromhex("19"), *safe_tx.signable_message]))) + ) From 31e04dac7e514a9e5d7d456ff028e24276a4315c Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 30 Nov 2023 13:37:18 -0600 Subject: [PATCH 086/134] chore: print when no tx --- ape_safe/_cli/pending.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 79d3e7e..ce03a84 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -26,6 +26,7 @@ def _list(cli_ctx: SafeCliContext, network, safe) -> None: """ _ = network # Needed for NetworkBoundCommand + tx = None for tx in safe.client.get_transactions(confirmed=False): rich.print( f"Transaction {tx.nonce}: " @@ -33,6 +34,9 @@ def _list(cli_ctx: SafeCliContext, network, safe) -> None: f"safe_tx_hash={tx.safe_tx_hash}" ) + if tx is None: + rich.print("There are no pending transactions.") + # NOTE: The handling of the `--execute` flag in the `pending` CLI # all happens here EXCEPT if a pending tx is executable and no From a512399d69815f432534361b9d0f3d0de752fa88 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Fri, 1 Dec 2023 20:24:53 -0600 Subject: [PATCH 087/134] refactor: allow x sigs --- ape_safe/_cli/pending.py | 21 ++++++++---- ape_safe/accounts.py | 43 ++++++++++++++----------- ape_safe/client/__init__.py | 33 ++++++++++--------- ape_safe/client/base.py | 13 +++++--- ape_safe/client/mock.py | 22 ++++++------- ape_safe/client/{models.py => types.py} | 0 ape_safe/utils.py | 17 +++++++--- docs/detailed.rst | 2 +- 8 files changed, 89 insertions(+), 62 deletions(-) rename ape_safe/client/{models.py => types.py} (100%) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index ce03a84..f04b4ad 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -5,6 +5,7 @@ from ape.api import AccountAPI from ape.cli import NetworkBoundCommand, get_user_selected_account, network_option from click.exceptions import BadOptionUsage +from hexbytes import HexBytes from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option @@ -103,12 +104,13 @@ def approve(cli_ctx: SafeCliContext, network, safe, nonce, execute): if num_confirmations < safe.confirmations_required: signatures_added = safe.add_signatures(safe_tx, txn.confirmations) - accounts_used_str = ", ".join(list(signatures_added.keys())) - cli_ctx.logger.success( - f"Signatures added to transaction '{safe_tx.nonce}' " - f"using accounts '{accounts_used_str}'." - ) - num_confirmations += len(signatures_added) + if signatures_added: + accounts_used_str = ", ".join(list(signatures_added.keys())) + cli_ctx.logger.success( + f"Signatures added to transaction '{safe_tx.nonce}' " + f"using accounts '{accounts_used_str}'." + ) + num_confirmations += len(signatures_added) if execute is None and submitter is None: # Check if we _can_ execute and ask the user. @@ -188,5 +190,10 @@ def show_confs(cli_ctx, network, safe, nonce): cli_ctx.abort(f"Pending transaction '{nonce}' not found.") rich.print(f"Showing confirmations for transaction '{txn.nonce}'") + length = len(txn.confirmations) for idx, conf in enumerate(txn.confirmations): - rich.print(f"Confirmation {idx + 1} owner={conf.owner}") + rich.print( + f"Confirmation {idx + 1} owner={conf.owner} signature={HexBytes(conf.signature).hex()}" + ) + if idx < length - 1: + click.echo() diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 08d31bc..0c0c2bc 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -1,4 +1,5 @@ import json +import os from itertools import islice from pathlib import Path from typing import Dict, Iterable, Iterator, List, Mapping, Optional, Tuple, Type, Union, cast @@ -33,7 +34,7 @@ SafeClientException, handle_safe_logic_error, ) -from ape_safe.utils import order_by_signer +from ape_safe.utils import get_safe_tx_hash, order_by_signer class SafeContainer(AccountContainerAPI): @@ -219,12 +220,15 @@ def fallback_handler(self) -> Optional[ContractInstance]: @cached_property def client(self) -> BaseSafeClient: chain_id = self.provider.chain_id + override_url = os.environ.get("SAFE_TRANSACTION_SERVICE_URL") if self.provider.network.name == LOCAL_NETWORK_NAME: return MockSafeClient(contract=self.contract) elif chain_id in self.account_file["deployed_chain_ids"]: - return SafeClient(address=self.address, chain_id=self.provider.chain_id) + return SafeClient( + address=self.address, chain_id=self.provider.chain_id, override_url=override_url + ) elif ( self.provider.network.name.endswith("-fork") @@ -232,7 +236,9 @@ def client(self) -> BaseSafeClient: and self.provider.network.upstream_chain_id in self.account_file["deployed_chain_ids"] ): return SafeClient( - address=self.address, chain_id=self.provider.network.upstream_chain_id + address=self.address, + chain_id=self.provider.network.upstream_chain_id, + override_url=override_url, ) elif self.provider.network.is_dev: @@ -333,7 +339,7 @@ def local_signers(self) -> List[AccountAPI]: def create_execute_transaction( self, safe_tx: SafeTx, - signatures: Mapping[AddressType, Union[MessageSignature, HexBytes]], + signatures: Mapping[AddressType, MessageSignature], **txn_options, ) -> TransactionAPI: exec_args = list(safe_tx._body_["message"].values())[:-1] # NOTE: Skip `nonce` @@ -659,7 +665,7 @@ def skip_signer(signer: AccountAPI): def add_signatures( self, safe_tx: SafeTx, confirmations: List[SafeTxConfirmation] - ) -> Dict[AddressType, Union[MessageSignature, HexBytes]]: + ) -> Dict[AddressType, MessageSignature]: if not self.local_signers: raise ApeSafeError("Cannot sign without local signers.") @@ -668,24 +674,25 @@ def add_signatures( acc for acc in self.local_signers if acc.address not in [c.owner for c in confirmations] ][:amount_needed] - signatures: Dict[AddressType, Union[MessageSignature, HexBytes]] = {} - for signer in signers: - if not (tx_hash_result := next((c.transaction_hash for c in confirmations), None)): - tx_hash_result = self.contract.getTransactionHash(*safe_tx) - - if tx_hash_result is None: - raise ApeSafeError("Failed to get transaction hash.") + signatures: Dict[AddressType, MessageSignature] = {} + safe_tx_hash = _get_safe_tx_id(safe_tx, confirmations) - tx_hash = HexBytes(tx_hash_result).hex() + for signer in signers: signature = signer.sign_message(safe_tx.signable_message) if signature: - self.client.post_signature(cast(SafeTxID, tx_hash), signer.address, signature) signatures[signer.address] = signature + if signatures: + self.client.post_signatures(safe_tx_hash, signatures) + return signatures -def get_safe_tx_hash(safe_tx) -> SafeTxID: - return cast( - SafeTxID, HexBytes(keccak(b"".join([bytes.fromhex("19"), *safe_tx.signable_message]))) - ) +def _get_safe_tx_id(safe_tx: SafeTx, confirmations: List[SafeTxConfirmation]) -> SafeTxID: + if tx_hash_result := next((c.transaction_hash for c in confirmations), None): + return cast(SafeTxID, tx_hash_result) + + elif value := get_safe_tx_hash(safe_tx): + return value + + raise ApeSafeError("Failed to get transaction hash.") diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index 653826c..1131f8d 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -2,13 +2,13 @@ from functools import reduce from typing import Dict, Iterator, Optional, Union +from ape.exceptions import SignatureError from ape.types import AddressType, HexBytes, MessageSignature from eip712.common import SafeTxV1, SafeTxV2 -from eip712.messages import hash_eip712_message from ape_safe.client.base import BaseSafeClient from ape_safe.client.mock import MockSafeClient -from ape_safe.client.models import ( +from ape_safe.client.types import ( ExecutedTxData, OperationType, SafeApiTxData, @@ -24,7 +24,7 @@ ClientUnsupportedChainError, MultisigTransactionNotFoundError, ) -from ape_safe.utils import order_by_signer +from ape_safe.utils import get_safe_tx_hash, order_by_signer TRANSACTION_SERVICE_URL = { # NOTE: If URLs need to be updated, a list of available service URLs can be found at @@ -126,34 +126,38 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna post_dict[key] = value url = f"safes/{tx_data.safe}/multisig-transactions" - json_data = {"origin": "ApeWorX/ape-safe", **post_dict} - self._post(url, json=json_data) + self._post(url, json=post_dict) - def post_signature( + def post_signatures( self, safe_tx_or_hash: Union[SafeTx, SafeTxID], - signer: AddressType, - signature: MessageSignature, + signatures: Dict[AddressType, MessageSignature], ): if isinstance(safe_tx_or_hash, (SafeTxV1, SafeTxV2)): safe_tx = safe_tx_or_hash - safe_tx_hash = hash_eip712_message(safe_tx).hex() + safe_tx_hash = get_safe_tx_hash(safe_tx) else: safe_tx_hash = safe_tx_or_hash - if not isinstance(safe_tx_hash, str): - raise TypeError("Expecting str-like type for 'safe_tx_hash'.") - + safe_tx_hash = HexBytes(safe_tx_hash).hex() url = f"multisig-transactions/{safe_tx_hash}/confirmations" - json_data = {"origin": "ApeWorX/ape-safe", "signature": signature.encode_rsv().hex()} + signature_bytes = HexBytes( + b"".join([x.encode_rsv() for x in order_by_signer(signatures)]) + ).hex() try: - self._post(url, json=json_data) + result = self._post(url, json={"signature": signature_bytes}) except ClientResponseError as err: if "The requested resource was not found on this server" in err.response.text: raise MultisigTransactionNotFoundError(safe_tx_hash, url, err.response) from err raise # The error from BaseClient we are already raising (no changes) + data = result.json() + + result_signatures = [x["signature"] for x in data.get("results", [])] + if not all(s in result_signatures for s in signatures.values()): + raise SignatureError("Failed to add signature to safe transaction") + __all__ = [ "ExecutedTxData", @@ -164,7 +168,6 @@ def post_signature( "SafeDetails", "SafeTx", "SafeTxConfirmation", - "SafeTxV2", "SignatureType", "UnexecutedTxData", ] diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py index 09b2fd9..cd7d45d 100644 --- a/ape_safe/client/base.py +++ b/ape_safe/client/base.py @@ -5,7 +5,7 @@ from ape.types import AddressType, MessageSignature from requests import Response -from ape_safe.client.models import ( +from ape_safe.client.types import ( ExecutedTxData, SafeApiTxData, SafeDetails, @@ -45,11 +45,10 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna ... @abstractmethod - def post_signature( + def post_signatures( self, safe_tx_or_hash: Union[SafeTx, SafeTxID], - signer: AddressType, - signature: MessageSignature, + signatures: Dict[AddressType, MessageSignature], ): ... @@ -100,7 +99,11 @@ def get_transactions( def _get(self, url: str) -> Response: return self._request("GET", url) - def _post(self, url: str, json: Dict) -> Response: + def _post(self, url: str, json: Optional[Dict] = None) -> Response: + json = json or {} + if "origin" not in json: + json["origin"] = "ApeWorX/ape-safe" + return self._request("POST", url, json=json) def _request(self, method: str, url: str, json: Optional[Dict] = None) -> Response: diff --git a/ape_safe/client/mock.py b/ape_safe/client/mock.py index a78d015..66cf143 100644 --- a/ape_safe/client/mock.py +++ b/ape_safe/client/mock.py @@ -7,7 +7,7 @@ from eth_utils import keccak from ape_safe.client.base import BaseSafeClient -from ape_safe.client.models import ( +from ape_safe.client.types import ( SafeApiTxData, SafeDetails, SafeTx, @@ -84,17 +84,17 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna else: self.transactions_by_nonce[safe_tx_data.nonce] = [safe_tx_data.safe_tx_hash] - def post_signature( + def post_signatures( self, safe_tx_or_hash: Union[SafeTx, SafeTxID], - signer: AddressType, - signature: MessageSignature, + signatures: Dict[AddressType, MessageSignature], ): - self.transactions[safe_tx_or_hash].confirmations.append( - SafeTxConfirmation( - owner=signer, - submissionDate=datetime.now(timezone.utc), - signature=signature.encode_rsv(), - signatureType=SignatureType.EOA, + for signer, signature in signatures.items(): + self.transactions[safe_tx_or_hash].confirmations.append( + SafeTxConfirmation( + owner=signer, + submissionDate=datetime.now(timezone.utc), + signature=signature.encode_rsv(), + signatureType=SignatureType.EOA, + ) ) - ) diff --git a/ape_safe/client/models.py b/ape_safe/client/types.py similarity index 100% rename from ape_safe/client/models.py rename to ape_safe/client/types.py diff --git a/ape_safe/utils.py b/ape_safe/utils.py index a2a671d..fa2a6f5 100644 --- a/ape_safe/utils.py +++ b/ape_safe/utils.py @@ -1,15 +1,22 @@ -from typing import List, Mapping, Union +from typing import TYPE_CHECKING, List, Mapping, cast from ape.types import AddressType, MessageSignature -from eth_utils import to_int +from eth_utils import keccak, to_int from hexbytes import HexBytes +if TYPE_CHECKING: + from ape_safe.client.types import SafeTxID -def order_by_signer( - signatures: Mapping[AddressType, Union[MessageSignature, HexBytes]] -) -> List[Union[MessageSignature, HexBytes]]: + +def order_by_signer(signatures: Mapping[AddressType, MessageSignature]) -> List[MessageSignature]: # NOTE: Must order signatures in ascending order of signer address (converted to int) def addr_to_int(a: AddressType) -> int: return to_int(hexstr=a) return list(signatures[signer] for signer in sorted(signatures, key=addr_to_int)) + + +def get_safe_tx_hash(safe_tx) -> "SafeTxID": + return cast( + "SafeTxID", HexBytes(keccak(b"".join([bytes.fromhex("19"), *safe_tx.signable_message]))) + ) diff --git a/docs/detailed.rst b/docs/detailed.rst index dd44924..fb1dc82 100644 --- a/docs/detailed.rst +++ b/docs/detailed.rst @@ -64,7 +64,7 @@ Play around the same way you would do with a normal account: # Post an additional confirmation to the transaction service >>> signature = safe.sign_transaction(safe_tx) - >>> safe.post_signature(safe_tx, signature) + >>> safe.post_signatures(safe_tx, signature) # Retrieve pending transactions from the transaction service >>> safe.pending_transactions From 3eff332147f27740dd9467a60bb016ab76aa0e28 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Fri, 1 Dec 2023 21:56:46 -0600 Subject: [PATCH 088/134] chore: highlight problem --- ape_safe/client/__init__.py | 22 ++++++++----------- ape_safe/client/base.py | 43 +++++++++++++++++++++++++++++++------ ape_safe/exceptions.py | 5 +++++ 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index 1131f8d..dddf848 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -1,8 +1,7 @@ from datetime import datetime from functools import reduce -from typing import Dict, Iterator, Optional, Union +from typing import Dict, Iterator, Optional, Union, cast -from ape.exceptions import SignatureError from ape.types import AddressType, HexBytes, MessageSignature from eip712.common import SafeTxV1, SafeTxV2 @@ -139,25 +138,22 @@ def post_signatures( else: safe_tx_hash = safe_tx_or_hash - safe_tx_hash = HexBytes(safe_tx_hash).hex() + safe_tx_hash = cast(SafeTxID, HexBytes(safe_tx_hash).hex()) url = f"multisig-transactions/{safe_tx_hash}/confirmations" - signature_bytes = HexBytes( - b"".join([x.encode_rsv() for x in order_by_signer(signatures)]) - ).hex() + signature = HexBytes(b"".join([x.encode_rsv() for x in order_by_signer(signatures)])).hex() + + # from gnosis.safe.safe_signature import SafeSignature + # parsed_signatures = SafeSignature.parse_signature(signature, safe_tx_hash) + # breakpoint() + try: - result = self._post(url, json={"signature": signature_bytes}) + self._post(url, json={"signature": signature}) except ClientResponseError as err: if "The requested resource was not found on this server" in err.response.text: raise MultisigTransactionNotFoundError(safe_tx_hash, url, err.response) from err raise # The error from BaseClient we are already raising (no changes) - data = result.json() - - result_signatures = [x["signature"] for x in data.get("results", [])] - if not all(s in result_signatures for s in signatures.values()): - raise SignatureError("Failed to add signature to safe transaction") - __all__ = [ "ExecutedTxData", diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py index cd7d45d..82dd742 100644 --- a/ape_safe/client/base.py +++ b/ape_safe/client/base.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from functools import cached_property from typing import Dict, Iterator, Optional, Set, Union import requests @@ -14,7 +15,7 @@ SafeTxID, UnexecutedTxData, ) -from ape_safe.exceptions import ClientResponseError +from ape_safe.exceptions import ActionNotPerformedError, ClientResponseError class BaseSafeClient(ABC): @@ -96,20 +97,48 @@ def get_transactions( """Request methods""" + @cached_property + def session(self) -> requests.Session: + session = requests.Session() + adapter = requests.adapters.HTTPAdapter( + pool_connections=10, # Doing all the connections to the same url + pool_maxsize=100, # Number of concurrent connections + pool_block=False, + ) + session.mount("http://", adapter) + session.mount("https://", adapter) + return session + def _get(self, url: str) -> Response: return self._request("GET", url) - def _post(self, url: str, json: Optional[Dict] = None) -> Response: + def _post(self, url: str, json: Optional[Dict] = None, **kwargs) -> Response: json = json or {} - if "origin" not in json: + if "origin" not in json and isinstance(json, dict): json["origin"] = "ApeWorX/ape-safe" - return self._request("POST", url, json=json) + if "headers" not in kwargs: + kwargs["headers"] = {"Content-type": "application/json"} + + return self._request("POST", url, json=json, **kwargs) - def _request(self, method: str, url: str, json: Optional[Dict] = None) -> Response: + def _request(self, method: str, url: str, json: Optional[Dict] = None, **kwargs) -> Response: api_url = f"{self.transaction_service_url}/api/v1/{url}" - response = requests.request(method, api_url, json=json) - if not response.ok: + + if "timeout" not in kwargs: + kwargs["timeout"] = 10 + + response = self.session.request(method, api_url, json=json, **kwargs) + do_fail = not kwargs.get("allow_failure", False) + + if method != response.request.method and do_fail: + # Handle weird Safe API behavior where it doesn't do the right thing. + raise ActionNotPerformedError( + f"Was expecting {method} action but got {response.request.method}. " + "Likely, there was some server or request issue." + ) + + if not response.ok and do_fail: raise ClientResponseError(api_url, response) return response diff --git a/ape_safe/exceptions.py b/ape_safe/exceptions.py index 5ce29c4..a2da731 100644 --- a/ape_safe/exceptions.py +++ b/ape_safe/exceptions.py @@ -116,6 +116,11 @@ def __init__(self, chain_id: int): super().__init__(f"Unsupported Chain ID '{chain_id}'.") +class ActionNotPerformedError(SafeClientException): + def __init__(self, message: str): + super().__init__(message) + + class ClientResponseError(SafeClientException): def __init__(self, endpoint_url: str, response: Response, message: Optional[str] = None): self.endpoint_url = endpoint_url From cc0aeb63e982106bd0ccba5f5cd543159059d740 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 4 Dec 2023 09:08:14 -0600 Subject: [PATCH 089/134] feat: sepolia support --- ape_safe/client/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index dddf848..20cc3b1 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -41,6 +41,7 @@ 42161: "https://safe-transaction-arbitrum.safe.global", 43114: "https://safe-transaction-avalanche.safe.global", 84531: "https://safe-transaction-base-testnet.safe.global", + 11155111: "https://safe-transaction-sepolia.safe.global", } From 757582182147ed6e5590a4764e5ad6543c37f8dd Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 4 Dec 2023 16:26:41 -0600 Subject: [PATCH 090/134] fix: signature --- ape_safe/accounts.py | 25 ++++++++++++-- ape_safe/client/__init__.py | 9 ++--- ape_safe/client/base.py | 21 ++++++++---- ape_safe/utils.py | 65 ++++++++++++++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 17 deletions(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 0c0c2bc..297255b 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -34,7 +34,7 @@ SafeClientException, handle_safe_logic_error, ) -from ape_safe.utils import get_safe_tx_hash, order_by_signer +from ape_safe.utils import get_safe_tx_hash, hash_message, order_by_signer class SafeContainer(AccountContainerAPI): @@ -678,9 +678,11 @@ def add_signatures( safe_tx_hash = _get_safe_tx_id(safe_tx, confirmations) for signer in signers: - signature = signer.sign_message(safe_tx.signable_message) + data_hash = hash_message(safe_tx_hash) + signature = signer.sign_message(data_hash) # type: ignore if signature: - signatures[signer.address] = signature + signature_adjusted = adjust_v_in_signature(signature) + signatures[signer.address] = signature_adjusted if signatures: self.client.post_signatures(safe_tx_hash, signatures) @@ -696,3 +698,20 @@ def _get_safe_tx_id(safe_tx: SafeTx, confirmations: List[SafeTxConfirmation]) -> return value raise ApeSafeError("Failed to get transaction hash.") + + +def adjust_v_in_signature(signature: MessageSignature) -> MessageSignature: + MIN_VALID_V_VALUE_FOR_SAFE_ECDSA = 27 + v = signature.v + + if v < MIN_VALID_V_VALUE_FOR_SAFE_ECDSA: + v += MIN_VALID_V_VALUE_FOR_SAFE_ECDSA + + # Add 4 because we signed with the prefix. + v += 4 + + return MessageSignature( + v=v, + r=signature.r, + s=signature.s, + ) diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index 20cc3b1..3b5b50b 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -1,3 +1,4 @@ +import json from datetime import datetime from functools import reduce from typing import Dict, Iterator, Optional, Union, cast @@ -142,13 +143,9 @@ def post_signatures( safe_tx_hash = cast(SafeTxID, HexBytes(safe_tx_hash).hex()) url = f"multisig-transactions/{safe_tx_hash}/confirmations" signature = HexBytes(b"".join([x.encode_rsv() for x in order_by_signer(signatures)])).hex() - - # from gnosis.safe.safe_signature import SafeSignature - # parsed_signatures = SafeSignature.parse_signature(signature, safe_tx_hash) - # breakpoint() - + data_str = json.dumps({"signature": signature}) try: - self._post(url, json={"signature": signature}) + self._post(url, data=data_str) except ClientResponseError as err: if "The requested resource was not found on this server" in err.response.text: raise MultisigTransactionNotFoundError(safe_tx_hash, url, err.response) from err diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py index 82dd742..f134269 100644 --- a/ape_safe/client/base.py +++ b/ape_safe/client/base.py @@ -5,6 +5,7 @@ import requests from ape.types import AddressType, MessageSignature from requests import Response +from requests.adapters import HTTPAdapter from ape_safe.client.types import ( ExecutedTxData, @@ -17,6 +18,8 @@ ) from ape_safe.exceptions import ActionNotPerformedError, ClientResponseError +# from requests import Response + class BaseSafeClient(ABC): def __init__(self, transaction_service_url: str): @@ -100,7 +103,7 @@ def get_transactions( @cached_property def session(self) -> requests.Session: session = requests.Session() - adapter = requests.adapters.HTTPAdapter( + adapter = HTTPAdapter( pool_connections=10, # Doing all the connections to the same url pool_maxsize=100, # Number of concurrent connections pool_block=False, @@ -113,23 +116,27 @@ def _get(self, url: str) -> Response: return self._request("GET", url) def _post(self, url: str, json: Optional[Dict] = None, **kwargs) -> Response: - json = json or {} - if "origin" not in json and isinstance(json, dict): + if json is not None and "origin" not in json and isinstance(json, dict): json["origin"] = "ApeWorX/ape-safe" - if "headers" not in kwargs: - kwargs["headers"] = {"Content-type": "application/json"} - return self._request("POST", url, json=json, **kwargs) def _request(self, method: str, url: str, json: Optional[Dict] = None, **kwargs) -> Response: api_url = f"{self.transaction_service_url}/api/v1/{url}" + do_fail = not kwargs.pop("allow_failure", False) if "timeout" not in kwargs: kwargs["timeout"] = 10 + headers = kwargs.get("headers", {}) + + # Add default headers + default_headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + kwargs["headers"] = {**default_headers, **headers} response = self.session.request(method, api_url, json=json, **kwargs) - do_fail = not kwargs.get("allow_failure", False) if method != response.request.method and do_fail: # Handle weird Safe API behavior where it doesn't do the right thing. diff --git a/ape_safe/utils.py b/ape_safe/utils.py index fa2a6f5..6ffc63b 100644 --- a/ape_safe/utils.py +++ b/ape_safe/utils.py @@ -1,7 +1,8 @@ from typing import TYPE_CHECKING, List, Mapping, cast from ape.types import AddressType, MessageSignature -from eth_utils import keccak, to_int +from eth_typing import HexStr +from eth_utils import add_0x_prefix, keccak, to_int from hexbytes import HexBytes if TYPE_CHECKING: @@ -20,3 +21,65 @@ def get_safe_tx_hash(safe_tx) -> "SafeTxID": return cast( "SafeTxID", HexBytes(keccak(b"".join([bytes.fromhex("19"), *safe_tx.signable_message]))) ) + + +def to_int_array(value) -> List[int]: + value_hex = HexBytes(value).hex() + value_int = int(value_hex, 16) + + result: List[int] = [] + while value_int: + result.insert(0, value_int & 0xFF) + value_int = value_int // 256 + + if len(result) == 0: + result.append(0) + + return result + + +def to_utf8_bytes(value: str) -> List[int]: + result = [] + i = 0 + while i < len(value): + c = ord(value[i]) + + if c < 0x80: + result.append(c) + + elif c < 0x800: + result.append((c >> 6) | 0xC0) + result.append((c & 0x3F) | 0x80) + + elif 0xD800 <= c <= 0xDBFF: + i += 1 + c2 = ord(value[i]) + + if i >= len(value) or not (0xDC00 <= c2 <= 0xDFFF): + raise ValueError("Invalid UTF-8 string") + + # Surrogate Pair + pair = 0x10000 + ((c & 0x03FF) << 10) + (c2 & 0x03FF) + result.append((pair >> 18) | 0xF0) + result.append(((pair >> 12) & 0x3F) | 0x80) + result.append(((pair >> 6) & 0x3F) | 0x80) + result.append((pair & 0x3F) | 0x80) + + else: + result.append((c >> 12) | 0xE0) + result.append(((c >> 6) & 0x3F) | 0x80) + result.append((c & 0x3F) | 0x80) + + i += 1 + + return result + + +def hash_message(message: str) -> str: + message_array = to_int_array(message) + message_prefix = "\x19Ethereum Signed Message:\n" + prefix_bytes = to_utf8_bytes(message_prefix) + length_bytes = to_utf8_bytes(f"{len(message_array)}") + full_array = prefix_bytes + length_bytes + message_array + result = keccak(bytearray(full_array)).hex() + return add_0x_prefix(cast(HexStr, result)) From 10db013284db647453b02726bfe922d1a0ad8965 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 4 Dec 2023 17:44:53 -0600 Subject: [PATCH 091/134] fix: critical trailing slash --- ape_safe/client/__init__.py | 4 +--- ape_safe/client/base.py | 25 +++++++++++-------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index 3b5b50b..00e7fc1 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -1,4 +1,3 @@ -import json from datetime import datetime from functools import reduce from typing import Dict, Iterator, Optional, Union, cast @@ -143,9 +142,8 @@ def post_signatures( safe_tx_hash = cast(SafeTxID, HexBytes(safe_tx_hash).hex()) url = f"multisig-transactions/{safe_tx_hash}/confirmations" signature = HexBytes(b"".join([x.encode_rsv() for x in order_by_signer(signatures)])).hex() - data_str = json.dumps({"signature": signature}) try: - self._post(url, data=data_str) + self._post(url, json={"signature": signature}) except ClientResponseError as err: if "The requested resource was not found on this server" in err.response.text: raise MultisigTransactionNotFoundError(safe_tx_hash, url, err.response) from err diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py index f134269..dfdd2ed 100644 --- a/ape_safe/client/base.py +++ b/ape_safe/client/base.py @@ -2,7 +2,9 @@ from functools import cached_property from typing import Dict, Iterator, Optional, Set, Union +import certifi import requests +import urllib3 from ape.types import AddressType, MessageSignature from requests import Response from requests.adapters import HTTPAdapter @@ -16,9 +18,7 @@ SafeTxID, UnexecutedTxData, ) -from ape_safe.exceptions import ActionNotPerformedError, ClientResponseError - -# from requests import Response +from ape_safe.exceptions import ClientResponseError class BaseSafeClient(ABC): @@ -116,13 +116,17 @@ def _get(self, url: str) -> Response: return self._request("GET", url) def _post(self, url: str, json: Optional[Dict] = None, **kwargs) -> Response: - if json is not None and "origin" not in json and isinstance(json, dict): - json["origin"] = "ApeWorX/ape-safe" - return self._request("POST", url, json=json, **kwargs) + @cached_property + def _http(self): + return urllib3.PoolManager(ca_certs=certifi.where()) + def _request(self, method: str, url: str, json: Optional[Dict] = None, **kwargs) -> Response: - api_url = f"{self.transaction_service_url}/api/v1/{url}" + # **WARNING**: The trailing slash in the URL is CRITICAL! + # If you remove it, things will not work as expected. + + api_url = f"{self.transaction_service_url}/api/v1/{url}/" do_fail = not kwargs.pop("allow_failure", False) if "timeout" not in kwargs: @@ -138,13 +142,6 @@ def _request(self, method: str, url: str, json: Optional[Dict] = None, **kwargs) kwargs["headers"] = {**default_headers, **headers} response = self.session.request(method, api_url, json=json, **kwargs) - if method != response.request.method and do_fail: - # Handle weird Safe API behavior where it doesn't do the right thing. - raise ActionNotPerformedError( - f"Was expecting {method} action but got {response.request.method}. " - "Likely, there was some server or request issue." - ) - if not response.ok and do_fail: raise ClientResponseError(api_url, response) From 22d1feda30930f4aa5248cd5d9c29ffc7ed93918 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 5 Dec 2023 11:22:13 -0600 Subject: [PATCH 092/134] fix: tx getting and id issue --- ape_safe/_cli/pending.py | 11 ++++----- ape_safe/accounts.py | 50 ++++++++++++++++------------------------ ape_safe/client/base.py | 17 ++++++++++++++ ape_safe/client/mock.py | 24 +++++++++++++------ ape_safe/client/types.py | 11 +++++---- ape_safe/utils.py | 3 ++- tests/test_account.py | 27 ++++++++++++++++------ 7 files changed, 87 insertions(+), 56 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index f04b4ad..d281e02 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -94,8 +94,7 @@ def _load_submitter(ctx, param, val): def approve(cli_ctx: SafeCliContext, network, safe, nonce, execute): _ = network # Needed for NetworkBoundCommand submitter: Optional[AccountAPI] = execute if isinstance(execute, AccountAPI) else None - txn = next(safe.client.get_transactions(confirmed=False, starting_nonce=nonce), None) - if not txn: + if not (txn := safe.client.get_transaction(nonce, confirmed=False)): cli_ctx.abort(f"Pending transaction '{nonce}' not found.") safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) @@ -141,8 +140,7 @@ def execute(cli_ctx, network, safe, nonce, submitter): """ Execute a transaction """ - txn = next(safe.client.get_transactions(confirmed=False, starting_nonce=nonce), None) - if not txn: + if not (txn := safe.client.get_transaction(nonce, confirmed=False)): cli_ctx.abort(f"Pending transaction '{nonce}' not found.") safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) @@ -162,7 +160,7 @@ def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): """ _ = network # Needed for NetworkBoundCommand - pending_transactions = safe.client.get_transactions(starting_nonce=safe.next_nonce) + pending_transactions = safe.client.get_transaction(confirmed=False, nonce=safe.next_nonce) for txn_id in txn_ids: try: @@ -185,8 +183,7 @@ def show_confs(cli_ctx, network, safe, nonce): Show existing confirmations """ _ = network # Needed for NetworkBoundCommand - txn = next(safe.client.get_transactions(confirmed=False, starting_nonce=nonce), None) - if not txn: + if not (txn := safe.client.get_transaction(nonce, confirmed=False)): cli_ctx.abort(f"Pending transaction '{nonce}' not found.") rich.print(f"Showing confirmations for transaction '{txn.nonce}'") diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 297255b..9e8ed3f 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -1,6 +1,5 @@ import json import os -from itertools import islice from pathlib import Path from typing import Dict, Iterable, Iterator, List, Mapping, Optional, Tuple, Type, Union, cast @@ -8,7 +7,7 @@ from ape.api.address import BaseAddress from ape.api.networks import LOCAL_NETWORK_NAME, ForkedNetworkAPI from ape.contracts import ContractInstance -from ape.exceptions import ProviderNotConnectedError, SignatureError +from ape.exceptions import ProviderNotConnectedError from ape.logging import logger from ape.managers.accounts import AccountManager, TestAccountManager from ape.types import AddressType, HexBytes, MessageSignature, SignableMessage @@ -157,12 +156,18 @@ def _get_path(self, alias: str) -> Path: def get_signatures( - safe_tx: SafeTx, + safe_tx_hash: str, signers: Iterable[AccountAPI], -) -> Iterator[Tuple[AddressType, MessageSignature]]: +) -> Dict[AddressType, MessageSignature]: + data_hash = hash_message(safe_tx_hash) + signatures: Dict[AddressType, MessageSignature] = {} for signer in signers: - if sig := signer.sign_message(safe_tx.signable_message): - yield signer.address, sig + signature = signer.sign_message(HexBytes(data_hash)) # type: ignore + if signature: + signature_adjusted = adjust_v_in_signature(signature) + signatures[signer.address] = signature_adjusted + + return signatures def _safe_tx_exec_args(safe_tx: SafeTx) -> List: @@ -465,11 +470,7 @@ def call( # type: ignore[override] if impersonate: return self._impersonated_call(txn, **call_kwargs) - try: - return super().call(txn, **call_kwargs) - except SignatureError: - # TODO: Create an intermediate receipt object - return None # type: ignore + return super().call(txn, **call_kwargs) def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSignature]: safe_tx_id = get_safe_tx_hash(safe_tx) @@ -604,14 +605,11 @@ def skip_signer(signer: AccountAPI): # Attempt to fetch just enough signatures to satisfy the amount we need # NOTE: It is okay to have less signatures, but it never should fetch more than needed - sigs_by_signer.update( - dict( - islice( - get_signatures(safe_tx, available_signers), - signatures_required - len(sigs_by_signer), - ) - ) - ) + signers = [x for x in self.local_signers if x.address not in sigs_by_signer] + if signers: + safe_tx_hash = get_safe_tx_hash(safe_tx) + new_signatures = get_signatures(safe_tx_hash, signers) + sigs_by_signer = {**sigs_by_signer, **new_signatures} if ( submit # NOTE: `submitter` should be set if `submit=True` @@ -648,8 +646,8 @@ def skip_signer(signer: AccountAPI): elif submitter and submitter.address in self.signers: # Not enough signatures were gathered to submit, but submitter also didn't sign yet, # so might as well get one more sig from them before publishing confirmations to API. - if sig := submitter.sign_message(safe_tx.signable_message): - sigs_by_signer[submitter.address] = sig + signatures = get_signatures(get_safe_tx_hash(safe_tx), [submitter]) + sigs_by_signer = {**sigs_by_signer, **signatures} # NOTE: Not enough signatures were obtained to publish on-chain logger.info( @@ -674,16 +672,8 @@ def add_signatures( acc for acc in self.local_signers if acc.address not in [c.owner for c in confirmations] ][:amount_needed] - signatures: Dict[AddressType, MessageSignature] = {} safe_tx_hash = _get_safe_tx_id(safe_tx, confirmations) - - for signer in signers: - data_hash = hash_message(safe_tx_hash) - signature = signer.sign_message(data_hash) # type: ignore - if signature: - signature_adjusted = adjust_v_in_signature(signature) - signatures[signer.address] = signature_adjusted - + signatures = get_signatures(safe_tx_hash, signers) if signatures: self.client.post_signatures(safe_tx_hash, signatures) diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py index dfdd2ed..e524a59 100644 --- a/ape_safe/client/base.py +++ b/ape_safe/client/base.py @@ -58,6 +58,23 @@ def post_signatures( """Shared methods""" + def get_transaction( + self, + nonce: int, + confirmed: Optional[bool] = None, + filter_by_ids: Optional[Set[SafeTxID]] = None, + filter_by_missing_signers: Optional[Set[AddressType]] = None, + ) -> Optional[SafeApiTxData]: + for tx in self.get_transactions( + confirmed=confirmed, + filter_by_ids=filter_by_ids, + filter_by_missing_signers=filter_by_missing_signers, + ): + if tx.nonce == nonce: + return tx + + return None + def get_transactions( self, confirmed: Optional[bool] = None, diff --git a/ape_safe/client/mock.py b/ape_safe/client/mock.py index 66cf143..c5ae021 100644 --- a/ape_safe/client/mock.py +++ b/ape_safe/client/mock.py @@ -1,10 +1,11 @@ from datetime import datetime, timezone -from typing import Dict, Iterator, List, Union +from typing import Dict, Iterator, List, Union, cast from ape.contracts import ContractInstance from ape.types import AddressType, MessageSignature from ape.utils import ZERO_ADDRESS, ManagerAccessMixin from eth_utils import keccak +from hexbytes import HexBytes from ape_safe.client.base import BaseSafeClient from ape_safe.client.types import ( @@ -16,12 +17,13 @@ SignatureType, UnexecutedTxData, ) +from ape_safe.utils import get_safe_tx_hash class MockSafeClient(BaseSafeClient, ManagerAccessMixin): def __init__(self, contract: ContractInstance): self.contract = contract - self.transactions: Dict[Union[SafeTx, SafeTxID], SafeApiTxData] = {} + self.transactions: Dict[SafeTxID, SafeApiTxData] = {} self.transactions_by_nonce: Dict[int, List[SafeTxID]] = {} @property @@ -63,7 +65,8 @@ def _all_transactions( yield from map(self.transactions.get, self.transactions_by_nonce[nonce]) def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: - if safe_tx_data := self.transactions.get(safe_tx_hash): + tx_hash = cast(SafeTxID, HexBytes(safe_tx_hash).hex()) + if safe_tx_data := self.transactions.get(tx_hash): yield from safe_tx_data.confirmations def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): @@ -77,12 +80,13 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna ) for signer, sig in sigs.items() ) - self.transactions[safe_tx_data.safe_tx_hash] = safe_tx_data + tx_id = cast(SafeTxID, HexBytes(safe_tx_data.safe_tx_hash).hex()) + self.transactions[tx_id] = safe_tx_data if safe_tx_data.nonce in self.transactions_by_nonce: - self.transactions_by_nonce[safe_tx_data.nonce].append(safe_tx_data.safe_tx_hash) + self.transactions_by_nonce[safe_tx_data.nonce].append(tx_id) else: - self.transactions_by_nonce[safe_tx_data.nonce] = [safe_tx_data.safe_tx_hash] + self.transactions_by_nonce[safe_tx_data.nonce] = [tx_id] def post_signatures( self, @@ -90,7 +94,13 @@ def post_signatures( signatures: Dict[AddressType, MessageSignature], ): for signer, signature in signatures.items(): - self.transactions[safe_tx_or_hash].confirmations.append( + safe_tx_id = ( + safe_tx_or_hash + if isinstance(safe_tx_or_hash, (str, bytes, int)) + else get_safe_tx_hash(safe_tx_or_hash) + ) + tx_id = cast(SafeTxID, HexBytes(safe_tx_id).hex()) + self.transactions[tx_id].confirmations.append( SafeTxConfirmation( owner=signer, submissionDate=datetime.now(timezone.utc), diff --git a/ape_safe/client/types.py b/ape_safe/client/types.py index 409a243..17703a2 100644 --- a/ape_safe/client/types.py +++ b/ape_safe/client/types.py @@ -1,12 +1,15 @@ from datetime import datetime, timezone from enum import Enum -from typing import Dict, List, NewType, Optional, Union +from typing import Dict, List, NewType, Optional, Union, cast from ape.types import AddressType, HexBytes from eip712.common import SafeTxV1, SafeTxV2 -from eip712.messages import hash_eip712_message +from eth_typing import HexStr +from eth_utils import add_0x_prefix from pydantic import BaseModel, Field +from ape_safe.utils import get_safe_tx_hash + SafeTx = Union[SafeTxV1, SafeTxV2] SafeTxID = NewType("SafeTxID", str) @@ -69,7 +72,7 @@ def from_safe_tx(cls, safe_tx: SafeTx, confirmations_required: int) -> "Unexecut submissionDate=datetime.now(timezone.utc), modified=datetime.now(timezone.utc), confirmationsRequired=confirmations_required, - safeTxHash=hash_eip712_message(safe_tx).hex(), + safeTxHash=get_safe_tx_hash(safe_tx), **safe_tx._body_["message"], ) @@ -100,7 +103,7 @@ def __str__(self) -> str: from: {self.safe} to: {self.to} value: {self.value / 1e18} ether - data: 0x{data_hex} + data: {add_0x_prefix(cast(HexStr, data_hex))} """ diff --git a/ape_safe/utils.py b/ape_safe/utils.py index 6ffc63b..7a64bd3 100644 --- a/ape_safe/utils.py +++ b/ape_safe/utils.py @@ -19,7 +19,8 @@ def addr_to_int(a: AddressType) -> int: def get_safe_tx_hash(safe_tx) -> "SafeTxID": return cast( - "SafeTxID", HexBytes(keccak(b"".join([bytes.fromhex("19"), *safe_tx.signable_message]))) + "SafeTxID", + HexBytes(keccak(b"".join([bytes.fromhex("19"), *safe_tx.signable_message]))).hex(), ) diff --git a/tests/test_account.py b/tests/test_account.py index 07b222a..fb4bbcc 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -1,5 +1,6 @@ import pytest from ape.exceptions import SignatureError +from eth_utils import add_0x_prefix def test_init(safe, OWNERS, THRESHOLD, safe_contract): @@ -20,7 +21,6 @@ def test_swap_owner(safe, accounts, OWNERS, mode): # NOTE: Since the signers are processed in order, we replace the last account prev_owner = safe.compute_prev_signer(old_owner) - exec_transaction = lambda: safe.contract.swapOwner( # noqa: E731 prev_owner, old_owner, @@ -34,16 +34,29 @@ def test_swap_owner(safe, accounts, OWNERS, mode): receipt = exec_transaction() else: - # Attempting to execute should emit a `SignatureError` and push `safe_tx` to mock client + # Attempting to execute should raise `SignatureError` and push `safe_tx` to mock client assert len(list(safe.client.get_transactions(confirmed=False))) == 0 + with pytest.raises(SignatureError): exec_transaction() - assert len(list(safe.client.get_transactions(confirmed=False))) == 1 + pending_txns = list(safe.client.get_transactions(confirmed=False)) + assert len(pending_txns) == 1 + assert len(pending_txns[0].confirmations) >= 1 + safe_tx_hash = add_0x_prefix(f"{pending_txns[0].safe_tx_hash}") + + safe_tx_data = pending_txns[0] + safe_tx = safe.create_safe_tx(**safe_tx_data.dict(by_alias=True)) + + # Ensure client confirmations works + client_confs = list(safe.client.get_confirmations(safe_tx_hash)) + assert len(client_confs) >= 1 + + # Ensure API confirmations work + api_confs = safe.get_api_confirmations(safe_tx) + assert len(api_confs) >= 1 # `safe_tx` is in mock client, extract it and execute it successfully this time - safe_tx_data = next(safe.client.get_transactions(confirmed=False)) - safe_tx = safe.create_safe_tx(**safe_tx_data.dict()) receipt = safe.submit_safe_tx(safe_tx) assert receipt.events == [ @@ -85,7 +98,7 @@ def test_add_owner(safe, accounts, OWNERS, mode): # `safe_tx` is in mock client, extract it and execute it successfully this time safe_tx_data = next(safe.client.get_transactions(confirmed=False)) - safe_tx = safe.create_safe_tx(**safe_tx_data.dict()) + safe_tx = safe.create_safe_tx(**safe_tx_data.dict(by_alias=True)) receipt = safe.submit_safe_tx(safe_tx) assert receipt.events == [ @@ -131,7 +144,7 @@ def test_remove_owner(safe, OWNERS, mode): # `safe_tx` is in mock client, extract it and execute it successfully this time safe_tx_data = next(safe.client.get_transactions(confirmed=False)) - safe_tx = safe.create_safe_tx(**safe_tx_data.dict()) + safe_tx = safe.create_safe_tx(**safe_tx_data.dict(by_alias=True)) receipt = safe.submit_safe_tx(safe_tx) expected_events = [ From 69d582bee35fe8a1e0aa0b36f592ceefd98e66e9 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 5 Dec 2023 13:11:00 -0600 Subject: [PATCH 093/134] feat: way better list cmds --- ape_safe/_cli/pending.py | 184 +++++++++++++++++++++++++++------------ ape_safe/accounts.py | 3 +- ape_safe/client/base.py | 25 ++---- ape_safe/exceptions.py | 2 +- 4 files changed, 137 insertions(+), 77 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index d281e02..371fc23 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Dict, List, Optional import click import rich @@ -7,7 +7,9 @@ from click.exceptions import BadOptionUsage from hexbytes import HexBytes +from ape_safe import SafeAccount from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option +from ape_safe.client import UnexecutedTxData @click.group() @@ -21,22 +23,51 @@ def pending(): @safe_cli_ctx @network_option() @safe_option -def _list(cli_ctx: SafeCliContext, network, safe) -> None: +@click.option("--show-confs", is_flag=True) +def _list(cli_ctx: SafeCliContext, network, safe, show_confs) -> None: """ View pending transactions for a Safe """ _ = network # Needed for NetworkBoundCommand - tx = None - for tx in safe.client.get_transactions(confirmed=False): - rich.print( - f"Transaction {tx.nonce}: " - f"({len(tx.confirmations)}/{safe.confirmations_required}) " - f"safe_tx_hash={tx.safe_tx_hash}" - ) - - if tx is None: + txns = list(safe.client.get_transactions(confirmed=False)) + if not txns: rich.print("There are no pending transactions.") + return + + txns_by_nonce: Dict[int, List[UnexecutedTxData]] = {} + for txn in txns: + if txn.nonce in txns_by_nonce: + txns_by_nonce[txn.nonce].append(txn) + else: + txns_by_nonce[txn.nonce] = [txn] + + all_items = txns_by_nonce.items() + total_items = len(all_items) + for root_idx, (nonce, tx_list) in enumerate(all_items): + tx_len = len(tx_list) + for idx, tx in enumerate(tx_list): + title = f"Transaction {nonce}" + is_rejection = not tx.value and not tx.data and tx.to == tx.safe + operation_name = tx.operation.name if tx.data else "transfer" + if is_rejection: + title = f"{title} (rejection)" + elif len(tx_list) > 1 and idx > 0: + title = f"{title} {operation_name} (replacement #{idx})" + else: + title = f"{title} {operation_name}" + + confirmations = tx.confirmations + rich.print( + f"{title} " + f"({len(confirmations)}/{safe.confirmations_required}) " + f"safe_tx_hash={tx.safe_tx_hash}" + ) + + if show_confs: + _show_confs(tx.confirmations, extra_line=False) + if root_idx < total_items - 1 or idx < tx_len - 1: + click.echo() # NOTE: The handling of the `--execute` flag in the `pending` CLI @@ -94,40 +125,46 @@ def _load_submitter(ctx, param, val): def approve(cli_ctx: SafeCliContext, network, safe, nonce, execute): _ = network # Needed for NetworkBoundCommand submitter: Optional[AccountAPI] = execute if isinstance(execute, AccountAPI) else None - if not (txn := safe.client.get_transaction(nonce, confirmed=False)): + + # NOTE: May be more than one if there's a conflicting nonce. + txns = list( + safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) + ) + if not txns: cli_ctx.abort(f"Pending transaction '{nonce}' not found.") - safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) - num_confirmations = len(txn.confirmations) - signatures_added = {} - - if num_confirmations < safe.confirmations_required: - signatures_added = safe.add_signatures(safe_tx, txn.confirmations) - if signatures_added: - accounts_used_str = ", ".join(list(signatures_added.keys())) - cli_ctx.logger.success( - f"Signatures added to transaction '{safe_tx.nonce}' " - f"using accounts '{accounts_used_str}'." + for txn in txns: + safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) + num_confirmations = len(txn.confirmations) + signatures_added = {} + + if num_confirmations < safe.confirmations_required: + signatures_added = safe.add_signatures(safe_tx, txn.confirmations) + if signatures_added: + accounts_used_str = ", ".join(list(signatures_added.keys())) + cli_ctx.logger.success( + f"Signatures added to transaction '{safe_tx.nonce}' " + f"using accounts '{accounts_used_str}'." + ) + num_confirmations += len(signatures_added) + + if execute is None and submitter is None: + # Check if we _can_ execute and ask the user. + do_execute = ( + len(safe.local_signers) > 0 + and num_confirmations >= safe.confirmations_required + and click.confirm(f"Submit transaction '{safe_tx.nonce}'") ) - num_confirmations += len(signatures_added) - - if execute is None and submitter is None: - # Check if we _can_ execute and ask the user. - do_execute = ( - len(safe.local_signers) > 0 - and num_confirmations >= safe.confirmations_required - and click.confirm(f"Submit transaction '{safe_tx.nonce}'") - ) - if do_execute: - # The user did provider a value for `--execute` however we are able to - # So we prompt them. - submitter = get_user_selected_account(account_type=safe.local_signers) + if do_execute: + # The user did provider a value for `--execute` however we are able to + # So we prompt them. + submitter = get_user_selected_account(account_type=safe.local_signers) - if submitter: - signatures = {c.owner: c.signature for c in txn.confirmations} - signatures = {**signatures, **signatures_added} - exc_tx = safe.create_execute_transaction(safe_tx, signatures) - submitter.call(exc_tx) + if submitter: + signatures = {c.owner: c.signature for c in txn.confirmations} + signatures = {**signatures, **signatures_added} + exc_tx = safe.create_execute_transaction(safe_tx, signatures) + submitter.call(exc_tx) @pending.command(cls=NetworkBoundCommand) @@ -140,12 +177,25 @@ def execute(cli_ctx, network, safe, nonce, submitter): """ Execute a transaction """ - if not (txn := safe.client.get_transaction(nonce, confirmed=False)): + + # NOTE: May be more than 1 if there are conflicting transactions. + txns = list( + safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) + ) + if not txns: cli_ctx.abort(f"Pending transaction '{nonce}' not found.") + for txn in txns: + _execute(safe, txn, submitter) + + +def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) signatures = {c.owner: c.signature for c in txn.confirmations} - exc_tx = safe.create_execute_transaction(safe_tx, signatures) + + # NOTE: We have a hack that allows bytes in the mapping, hence type ignore + exc_tx = safe.create_execute_transaction(safe_tx, signatures) # type: ignore + submitter.call(exc_tx) @@ -160,17 +210,14 @@ def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): """ _ = network # Needed for NetworkBoundCommand - pending_transactions = safe.client.get_transaction(confirmed=False, nonce=safe.next_nonce) + pending_transactions = safe.client.get_transactions( + confirmed=False, starting_nonce=safe.next_nonce + ) for txn_id in txn_ids: - try: - txn = next(txn for txn in pending_transactions if txn_id == txn.nonce) - except StopIteration: - # NOTE: Not a pending transaction. - continue - - if click.confirm(f"{txn}\nCancel Transaction?"): - safe.transfer(safe, "0 ether", nonce=txn_id, submit_transaction=False) + if txn := next((txn for txn in pending_transactions if txn_id == txn.nonce), None): + if click.confirm(f"{txn}\nCancel Transaction?"): + safe.transfer(safe, "0 ether", nonce=txn_id, submit_transaction=False) @pending.command(cls=NetworkBoundCommand) @@ -183,14 +230,37 @@ def show_confs(cli_ctx, network, safe, nonce): Show existing confirmations """ _ = network # Needed for NetworkBoundCommand - if not (txn := safe.client.get_transaction(nonce, confirmed=False)): + + # NOTE: May be more than 1 if conflicting transactions + txns = list( + safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) + ) + if not txns: cli_ctx.abort(f"Pending transaction '{nonce}' not found.") - rich.print(f"Showing confirmations for transaction '{txn.nonce}'") - length = len(txn.confirmations) - for idx, conf in enumerate(txn.confirmations): + num_txns = len(txns) + for root_idx, txn in enumerate(txns): + header = f"Showing confirmations for transaction '{txn.nonce}'" + operation_name = txn.operation.name if txn.data else "transfer" + is_rejection = not txn.value and not txn.data and txn.to == txn.safe + if is_rejection: + header = f"{header} - (rejection)" + elif num_txns > 1 and root_idx > 0: + header = f"{header} {operation_name} - (replacement #{root_idx})" + else: + header = f"{header} {operation_name}" + + rich.print(header) + _show_confs(txn.confirmations) + if root_idx < num_txns - 1: + click.echo() + + +def _show_confs(confs, extra_line: bool = True): + length = len(confs) + for idx, conf in enumerate(confs): rich.print( f"Confirmation {idx + 1} owner={conf.owner} signature={HexBytes(conf.signature).hex()}" ) - if idx < length - 1: + if extra_line and idx < length - 1: click.echo() diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 9e8ed3f..e5d094c 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -476,7 +476,8 @@ def get_api_confirmations(self, safe_tx: SafeTx) -> Dict[AddressType, MessageSig safe_tx_id = get_safe_tx_hash(safe_tx) try: client_confirmations = self.client.get_confirmations(safe_tx_id) - except SafeClientException: + except SafeClientException as err: + logger.error(str(err)) return {} return { diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py index e524a59..f9dcca0 100644 --- a/ape_safe/client/base.py +++ b/ape_safe/client/base.py @@ -58,27 +58,11 @@ def post_signatures( """Shared methods""" - def get_transaction( - self, - nonce: int, - confirmed: Optional[bool] = None, - filter_by_ids: Optional[Set[SafeTxID]] = None, - filter_by_missing_signers: Optional[Set[AddressType]] = None, - ) -> Optional[SafeApiTxData]: - for tx in self.get_transactions( - confirmed=confirmed, - filter_by_ids=filter_by_ids, - filter_by_missing_signers=filter_by_missing_signers, - ): - if tx.nonce == nonce: - return tx - - return None - def get_transactions( self, confirmed: Optional[bool] = None, starting_nonce: int = 0, + ending_nonce: Optional[int] = None, filter_by_ids: Optional[Set[SafeTxID]] = None, filter_by_missing_signers: Optional[Set[AddressType]] = None, ) -> Iterator[SafeApiTxData]: @@ -87,8 +71,13 @@ def get_transactions( """ next_nonce = self.get_next_nonce() + # NOTE: We loop backwards. for txn in self._all_transactions(): - if txn.nonce < starting_nonce: + if ending_nonce is not None and txn.nonce > ending_nonce: + # NOTE: Skip all largest nonces first + continue + + elif txn.nonce < starting_nonce: break # NOTE: order is largest nonce to smallest, so safe to break here is_confirmed = len(txn.confirmations) >= txn.confirmations_required diff --git a/ape_safe/exceptions.py b/ape_safe/exceptions.py index a2da731..505aed5 100644 --- a/ape_safe/exceptions.py +++ b/ape_safe/exceptions.py @@ -125,7 +125,7 @@ class ClientResponseError(SafeClientException): def __init__(self, endpoint_url: str, response: Response, message: Optional[str] = None): self.endpoint_url = endpoint_url self.response = response - message = message or f"Exception when calling '{endpoint_url}':\n{response}" + message = message or f"Exception when calling '{endpoint_url}':\n{response.text}" super().__init__(message) From a416b2dc7548ddd4b4d2fe0adba204671e5ff9b4 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 5 Dec 2023 13:12:59 -0600 Subject: [PATCH 094/134] fix: get_api_confs fix --- ape_safe/client/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index 00e7fc1..19d302c 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -96,7 +96,7 @@ def _all_transactions(self) -> Iterator[SafeApiTxData]: url = data.get("next") def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmation]: - url = f"multisig-transactions/{str(safe_tx_hash.replace('8', '0'))}/confirmations" + url = f"multisig-transactions/{str(safe_tx_hash)}/confirmations" while url: response = self._get(url) data = response.json() From bc41763f8dc7ffca75676ca80103e32bf9f24fe9 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 5 Dec 2023 21:59:49 -0600 Subject: [PATCH 095/134] refactor: use eip712 hash calc --- ape_safe/utils.py | 7 +++---- setup.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ape_safe/utils.py b/ape_safe/utils.py index 7a64bd3..0c63f5f 100644 --- a/ape_safe/utils.py +++ b/ape_safe/utils.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING, List, Mapping, cast from ape.types import AddressType, MessageSignature +from eip712.messages import calculate_hash from eth_typing import HexStr from eth_utils import add_0x_prefix, keccak, to_int from hexbytes import HexBytes @@ -18,10 +19,8 @@ def addr_to_int(a: AddressType) -> int: def get_safe_tx_hash(safe_tx) -> "SafeTxID": - return cast( - "SafeTxID", - HexBytes(keccak(b"".join([bytes.fromhex("19"), *safe_tx.signable_message]))).hex(), - ) + message_hash = calculate_hash(safe_tx.signable_message) + return cast("SafeTxID", message_hash.hex()) def to_int_array(value) -> List[int]: diff --git a/setup.py b/setup.py index 85c8c24..47e0f4f 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ include_package_data=True, install_requires=[ "eth-ape>=0.6.23,<0.7.0", - "eip712>=0.2.0,<0.3.0", + "eip712>=0.2.2,<0.3.0", "requests>=2.31.0,<3", "click", # Use same version as eth-ape "pydantic", # Use same version as eth-ape From f7c87b1ff4aa356ca2f9f1f8fe62849129a8b21d Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 6 Dec 2023 08:49:02 -0600 Subject: [PATCH 096/134] fix: title --- ape_safe/_cli/pending.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 371fc23..fce1306 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -51,9 +51,7 @@ def _list(cli_ctx: SafeCliContext, network, safe, show_confs) -> None: is_rejection = not tx.value and not tx.data and tx.to == tx.safe operation_name = tx.operation.name if tx.data else "transfer" if is_rejection: - title = f"{title} (rejection)" - elif len(tx_list) > 1 and idx > 0: - title = f"{title} {operation_name} (replacement #{idx})" + title = f"{title} rejection" else: title = f"{title} {operation_name}" @@ -244,9 +242,7 @@ def show_confs(cli_ctx, network, safe, nonce): operation_name = txn.operation.name if txn.data else "transfer" is_rejection = not txn.value and not txn.data and txn.to == txn.safe if is_rejection: - header = f"{header} - (rejection)" - elif num_txns > 1 and root_idx > 0: - header = f"{header} {operation_name} - (replacement #{root_idx})" + header = f"{header} rejection" else: header = f"{header} {operation_name}" From 650a05fe0c1efc884356e104cfc5f66bfed80c19 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 6 Dec 2023 09:03:19 -0600 Subject: [PATCH 097/134] feat: allow hashes in cli --- ape_safe/_cli/pending.py | 68 ++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index fce1306..5b581a0 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -118,18 +118,24 @@ def _load_submitter(ctx, param, val): @safe_cli_ctx @network_option() @safe_option -@click.argument("nonce", type=int) +@click.argument("txn_id") @click.option("--execute", callback=_handle_execute_cli_arg) -def approve(cli_ctx: SafeCliContext, network, safe, nonce, execute): +def approve(cli_ctx: SafeCliContext, network, safe, txn_id, execute): _ = network # Needed for NetworkBoundCommand submitter: Optional[AccountAPI] = execute if isinstance(execute, AccountAPI) else None - # NOTE: May be more than one if there's a conflicting nonce. - txns = list( - safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) - ) + if txn_id.isnumeric(): + nonce = int(txn_id) + + # NOTE: May be more than one if there's a conflicting nonce. + txns = list( + safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) + ) + else: + txns = list(safe.client.get_transactions(filter_by_ids=txn_id, confirmed=False)) + if not txns: - cli_ctx.abort(f"Pending transaction '{nonce}' not found.") + cli_ctx.abort(f"Pending transaction '{txn_id}' not found.") for txn in txns: safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) @@ -169,19 +175,26 @@ def approve(cli_ctx: SafeCliContext, network, safe, nonce, execute): @safe_cli_ctx @network_option() @safe_option -@click.argument("nonce", type=int) +@click.argument("txn_id") @click.option("--submitter", callback=_load_submitter) -def execute(cli_ctx, network, safe, nonce, submitter): +def execute(cli_ctx, network, safe, txn_id, submitter): """ Execute a transaction """ - # NOTE: May be more than 1 if there are conflicting transactions. - txns = list( - safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) - ) + if txn_id.isnumeric(): + nonce = int(txn_id) + + # NOTE: May be more than 1 if there are conflicting transactions. + txns = list( + safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) + ) + + else: + txns = list(safe.client.get_transactions(filter_by_ids=txn_id, confirmed=False)) + if not txns: - cli_ctx.abort(f"Pending transaction '{nonce}' not found.") + cli_ctx.abort(f"Pending transaction '{txn_id}' not found.") for txn in txns: _execute(safe, txn, submitter) @@ -201,7 +214,7 @@ def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): @safe_cli_ctx @network_option() @safe_option -@click.argument("txn-ids", type=int, nargs=-1) +@click.argument("txn-ids", nargs=-1) def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): """ Reject one or more pending transactions @@ -213,7 +226,10 @@ def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): ) for txn_id in txn_ids: - if txn := next((txn for txn in pending_transactions if txn_id == txn.nonce), None): + if txn_id.isnumeric(): + txn_id = int(txn_id) + + if txn := next((txn for txn in pending_transactions if txn_id in (txn.nonce, txn.safe_tx_hash)), None): if click.confirm(f"{txn}\nCancel Transaction?"): safe.transfer(safe, "0 ether", nonce=txn_id, submit_transaction=False) @@ -222,19 +238,25 @@ def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): @safe_cli_ctx @network_option() @safe_option -@click.argument("nonce", type=int) -def show_confs(cli_ctx, network, safe, nonce): +@click.argument("txn_id") +def show_confs(cli_ctx, network, safe, txn_id): """ Show existing confirmations """ _ = network # Needed for NetworkBoundCommand - # NOTE: May be more than 1 if conflicting transactions - txns = list( - safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) - ) + if txn_id.isnumeric(): + nonce = int(txn_id) + + # NOTE: May be more than 1 if conflicting transactions + txns = list( + safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) + ) + else: + txns = list(safe.client.get_transactions(filter_by_ids=txn_id, confirmed=False)) + if not txns: - cli_ctx.abort(f"Pending transaction '{nonce}' not found.") + cli_ctx.abort(f"Pending transaction '{txn_id}' not found.") num_txns = len(txns) for root_idx, txn in enumerate(txns): From a06a831b6d5ecead49347f52c468792a6eb61878 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 6 Dec 2023 09:09:31 -0600 Subject: [PATCH 098/134] feat: allow approving x --- ape_safe/_cli/pending.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 5b581a0..b2478aa 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -118,26 +118,24 @@ def _load_submitter(ctx, param, val): @safe_cli_ctx @network_option() @safe_option -@click.argument("txn_id") +@click.argument("txn_ids") @click.option("--execute", callback=_handle_execute_cli_arg) -def approve(cli_ctx: SafeCliContext, network, safe, txn_id, execute): +def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): _ = network # Needed for NetworkBoundCommand submitter: Optional[AccountAPI] = execute if isinstance(execute, AccountAPI) else None + pending_transactions = safe.client.get_transactions( + confirmed=False, starting_nonce=safe.next_nonce + ) - if txn_id.isnumeric(): - nonce = int(txn_id) + txn_ids = [int(x) if x.isnumeric() else x for x in txn_ids if x] - # NOTE: May be more than one if there's a conflicting nonce. - txns = list( - safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) - ) - else: - txns = list(safe.client.get_transactions(filter_by_ids=txn_id, confirmed=False)) + if not txn_ids: + cli_ctx.abort(f"Pending transaction(s) '{', '.join(txn_ids)}' not found.") - if not txns: - cli_ctx.abort(f"Pending transaction '{txn_id}' not found.") + for txn in pending_transactions: + if txn.nonce not in txn_ids and txn.safe_tx_hash not in txn_ids: + continue - for txn in txns: safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) num_confirmations = len(txn.confirmations) signatures_added = {} @@ -229,7 +227,9 @@ def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): if txn_id.isnumeric(): txn_id = int(txn_id) - if txn := next((txn for txn in pending_transactions if txn_id in (txn.nonce, txn.safe_tx_hash)), None): + if txn := next( + (txn for txn in pending_transactions if txn_id in (txn.nonce, txn.safe_tx_hash)), None + ): if click.confirm(f"{txn}\nCancel Transaction?"): safe.transfer(safe, "0 ether", nonce=txn_id, submit_transaction=False) From ad3935ba69fb8636147707ddb611f3cbaa2c7b71 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 6 Dec 2023 10:14:21 -0600 Subject: [PATCH 099/134] fix: figured out how to sign --- ape_safe/_cli/pending.py | 6 ++-- ape_safe/accounts.py | 7 +++-- ape_safe/utils.py | 66 +--------------------------------------- 3 files changed, 8 insertions(+), 71 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index b2478aa..ce917bc 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -118,13 +118,13 @@ def _load_submitter(ctx, param, val): @safe_cli_ctx @network_option() @safe_option -@click.argument("txn_ids") +@click.argument("txn_ids", nargs=-1) @click.option("--execute", callback=_handle_execute_cli_arg) def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): _ = network # Needed for NetworkBoundCommand submitter: Optional[AccountAPI] = execute if isinstance(execute, AccountAPI) else None - pending_transactions = safe.client.get_transactions( - confirmed=False, starting_nonce=safe.next_nonce + pending_transactions = list( + safe.client.get_transactions(confirmed=False, starting_nonce=safe.next_nonce) ) txn_ids = [int(x) if x.isnumeric() else x for x in txn_ids if x] diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index e5d094c..f73f47e 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -14,6 +14,7 @@ from ape.utils import ZERO_ADDRESS, cached_property from ape_ethereum.transactions import TransactionType from eip712.common import create_safe_tx_def +from eth_account.messages import encode_defunct from eth_utils import keccak, to_bytes, to_int from ethpm_types import ContractType @@ -33,7 +34,7 @@ SafeClientException, handle_safe_logic_error, ) -from ape_safe.utils import get_safe_tx_hash, hash_message, order_by_signer +from ape_safe.utils import get_safe_tx_hash, order_by_signer class SafeContainer(AccountContainerAPI): @@ -159,10 +160,10 @@ def get_signatures( safe_tx_hash: str, signers: Iterable[AccountAPI], ) -> Dict[AddressType, MessageSignature]: - data_hash = hash_message(safe_tx_hash) signatures: Dict[AddressType, MessageSignature] = {} for signer in signers: - signature = signer.sign_message(HexBytes(data_hash)) # type: ignore + message = encode_defunct(hexstr=safe_tx_hash) + signature = signer.sign_message(message) if signature: signature_adjusted = adjust_v_in_signature(signature) signatures[signer.address] = signature_adjusted diff --git a/ape_safe/utils.py b/ape_safe/utils.py index 0c63f5f..d71e609 100644 --- a/ape_safe/utils.py +++ b/ape_safe/utils.py @@ -2,9 +2,7 @@ from ape.types import AddressType, MessageSignature from eip712.messages import calculate_hash -from eth_typing import HexStr -from eth_utils import add_0x_prefix, keccak, to_int -from hexbytes import HexBytes +from eth_utils import to_int if TYPE_CHECKING: from ape_safe.client.types import SafeTxID @@ -21,65 +19,3 @@ def addr_to_int(a: AddressType) -> int: def get_safe_tx_hash(safe_tx) -> "SafeTxID": message_hash = calculate_hash(safe_tx.signable_message) return cast("SafeTxID", message_hash.hex()) - - -def to_int_array(value) -> List[int]: - value_hex = HexBytes(value).hex() - value_int = int(value_hex, 16) - - result: List[int] = [] - while value_int: - result.insert(0, value_int & 0xFF) - value_int = value_int // 256 - - if len(result) == 0: - result.append(0) - - return result - - -def to_utf8_bytes(value: str) -> List[int]: - result = [] - i = 0 - while i < len(value): - c = ord(value[i]) - - if c < 0x80: - result.append(c) - - elif c < 0x800: - result.append((c >> 6) | 0xC0) - result.append((c & 0x3F) | 0x80) - - elif 0xD800 <= c <= 0xDBFF: - i += 1 - c2 = ord(value[i]) - - if i >= len(value) or not (0xDC00 <= c2 <= 0xDFFF): - raise ValueError("Invalid UTF-8 string") - - # Surrogate Pair - pair = 0x10000 + ((c & 0x03FF) << 10) + (c2 & 0x03FF) - result.append((pair >> 18) | 0xF0) - result.append(((pair >> 12) & 0x3F) | 0x80) - result.append(((pair >> 6) & 0x3F) | 0x80) - result.append((pair & 0x3F) | 0x80) - - else: - result.append((c >> 12) | 0xE0) - result.append(((c >> 6) & 0x3F) | 0x80) - result.append((c & 0x3F) | 0x80) - - i += 1 - - return result - - -def hash_message(message: str) -> str: - message_array = to_int_array(message) - message_prefix = "\x19Ethereum Signed Message:\n" - prefix_bytes = to_utf8_bytes(message_prefix) - length_bytes = to_utf8_bytes(f"{len(message_array)}") - full_array = prefix_bytes + length_bytes + message_array - result = keccak(bytearray(full_array)).hex() - return add_0x_prefix(cast(HexStr, result)) From 3886024ce9b9ce18b5602da2415da2b4591b2a42 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 6 Dec 2023 12:03:07 -0600 Subject: [PATCH 100/134] fix: CLI txn ids and err imprv --- ape_safe/_cli/click_ext.py | 13 +++++++ ape_safe/_cli/pending.py | 71 +++++++++++++++++++++++--------------- 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index 8f1ab96..6af251d 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -1,3 +1,5 @@ +from typing import NoReturn, Sequence, Union + import click from ape import accounts from ape.cli import ApeCliContextObject, ape_cli_context @@ -13,6 +15,9 @@ def safes(self) -> SafeContainer: assert "safe" in self.account_manager.containers, "Are all API methods implemented?" return self.account_manager.containers["safe"] + def abort_txns_not_found(self, txn_ids: Sequence[Union[int, str]]) -> NoReturn: + self.abort(f"Pending transaction(s) '{', '.join([f'{x}' for x in txn_ids])}' not found.") + safe_cli_ctx = ape_cli_context(obj_type=SafeCliContext) @@ -36,3 +41,11 @@ def _safe_callback(ctx, param, value): safe_option = click.option("--safe", callback=_safe_callback) + + +def _txn_ids_callback(ctx, param, value): + value_ls = value or [] + return [int(x) if x.isnumeric() else x for x in value_ls if x] + + +txn_ids_argument = click.argument("txn_ids", nargs=-1, callback=_txn_ids_callback) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index ce917bc..54d3ed0 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -8,7 +8,7 @@ from hexbytes import HexBytes from ape_safe import SafeAccount -from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option +from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option, txn_ids_argument from ape_safe.client import UnexecutedTxData @@ -118,7 +118,7 @@ def _load_submitter(ctx, param, val): @safe_cli_ctx @network_option() @safe_option -@click.argument("txn_ids", nargs=-1) +@txn_ids_argument @click.option("--execute", callback=_handle_execute_cli_arg) def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): _ = network # Needed for NetworkBoundCommand @@ -126,14 +126,20 @@ def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): pending_transactions = list( safe.client.get_transactions(confirmed=False, starting_nonce=safe.next_nonce) ) - - txn_ids = [int(x) if x.isnumeric() else x for x in txn_ids if x] - - if not txn_ids: - cli_ctx.abort(f"Pending transaction(s) '{', '.join(txn_ids)}' not found.") - for txn in pending_transactions: - if txn.nonce not in txn_ids and txn.safe_tx_hash not in txn_ids: + # Figure out which given ID(s) we are handling. + found = False + if txn.nonce in txn_ids: + found = True + txn_ids = [x for x in txn_ids if x != txn.nonce] + + # Handle if given nonce and hash for same txn. + if txn.safe_tx_hash in txn_ids: + found = True + txn_ids = [x for x in txn_ids if x != txn.nonce] + + if not found: + # Not a txn the user said to approve. continue safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) @@ -168,35 +174,46 @@ def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): exc_tx = safe.create_execute_transaction(safe_tx, signatures) submitter.call(exc_tx) + # If any txn_ids remain, they were not handled. + if txn_ids: + cli_ctx.abort_txns_not_found(txn_ids) + @pending.command(cls=NetworkBoundCommand) @safe_cli_ctx @network_option() @safe_option -@click.argument("txn_id") +@txn_ids_argument @click.option("--submitter", callback=_load_submitter) -def execute(cli_ctx, network, safe, txn_id, submitter): +def execute(cli_ctx, network, safe, txn_ids, submitter): """ Execute a transaction """ + pending_transactions = list( + safe.client.get_transactions(confirmed=False, starting_nonce=safe.next_nonce) + ) + for txn in pending_transactions: + # Figure out which given ID(s) we are handling. + found = False + if txn.nonce in txn_ids: + found = True + txn_ids = [x for x in txn_ids if x != txn.nonce] + + # Handle if given nonce and hash for same txn. + if txn.safe_tx_hash in txn_ids: + found = True + txn_ids = [x for x in txn_ids if x != txn.nonce] + + if not found: + # Not a txn the user said to approve. + continue - if txn_id.isnumeric(): - nonce = int(txn_id) - - # NOTE: May be more than 1 if there are conflicting transactions. - txns = list( - safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) - ) - - else: - txns = list(safe.client.get_transactions(filter_by_ids=txn_id, confirmed=False)) - - if not txns: - cli_ctx.abort(f"Pending transaction '{txn_id}' not found.") - - for txn in txns: _execute(safe, txn, submitter) + # If any txn_ids remain, they were not handled. + if txn_ids: + cli_ctx.abort_txns_not_found(txn_ids) + def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) @@ -256,7 +273,7 @@ def show_confs(cli_ctx, network, safe, txn_id): txns = list(safe.client.get_transactions(filter_by_ids=txn_id, confirmed=False)) if not txns: - cli_ctx.abort(f"Pending transaction '{txn_id}' not found.") + cli_ctx.abort_txns_not_found([txn_id]) num_txns = len(txns) for root_idx, txn in enumerate(txns): From 968c73261105687f141855577640a4fa2d766f5e Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 6 Dec 2023 13:30:04 -0600 Subject: [PATCH 101/134] fix: issue with submit --- ape_safe/_cli/pending.py | 83 ++++++++++++++++++++++++---------------- ape_safe/accounts.py | 7 ++-- 2 files changed, 53 insertions(+), 37 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 54d3ed0..7fd44c1 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -1,9 +1,10 @@ -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Sequence, Union import click import rich from ape.api import AccountAPI from ape.cli import NetworkBoundCommand, get_user_selected_account, network_option +from ape.exceptions import SignatureError from click.exceptions import BadOptionUsage from hexbytes import HexBytes @@ -128,18 +129,10 @@ def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): ) for txn in pending_transactions: # Figure out which given ID(s) we are handling. - found = False - if txn.nonce in txn_ids: - found = True - txn_ids = [x for x in txn_ids if x != txn.nonce] - - # Handle if given nonce and hash for same txn. - if txn.safe_tx_hash in txn_ids: - found = True - txn_ids = [x for x in txn_ids if x != txn.nonce] - - if not found: - # Not a txn the user said to approve. + length_before = len(txn_ids) + txn_ids = _check_tx_ids(txn_ids, txn) + if len(txn_ids) == length_before: + # Not a specified txn. continue safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) @@ -194,18 +187,10 @@ def execute(cli_ctx, network, safe, txn_ids, submitter): ) for txn in pending_transactions: # Figure out which given ID(s) we are handling. - found = False - if txn.nonce in txn_ids: - found = True - txn_ids = [x for x in txn_ids if x != txn.nonce] - - # Handle if given nonce and hash for same txn. - if txn.safe_tx_hash in txn_ids: - found = True - txn_ids = [x for x in txn_ids if x != txn.nonce] - - if not found: - # Not a txn the user said to approve. + length_before = len(txn_ids) + txn_ids = _check_tx_ids(txn_ids, txn) + if len(txn_ids) == length_before: + # Not a specified txn. continue _execute(safe, txn, submitter) @@ -229,7 +214,7 @@ def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): @safe_cli_ctx @network_option() @safe_option -@click.argument("txn-ids", nargs=-1) +@txn_ids_argument def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): """ Reject one or more pending transactions @@ -240,15 +225,32 @@ def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): confirmed=False, starting_nonce=safe.next_nonce ) - for txn_id in txn_ids: - if txn_id.isnumeric(): - txn_id = int(txn_id) + for txn in pending_transactions: + # Figure out which given ID(s) we are handling. + length_before = len(txn_ids) + txn_ids = _check_tx_ids(txn_ids, txn) + if len(txn_ids) == length_before: + # Not a specified txn. + continue - if txn := next( - (txn for txn in pending_transactions if txn_id in (txn.nonce, txn.safe_tx_hash)), None - ): - if click.confirm(f"{txn}\nCancel Transaction?"): - safe.transfer(safe, "0 ether", nonce=txn_id, submit_transaction=False) + is_rejection = not txn.value and not txn.data and txn.to == txn.safe + if is_rejection: + click.echo(f"Transaction '{txn.safe_tx_hash}' already canceled!") + continue + + elif click.confirm(f"{txn}\nCancel Transaction?"): + try: + safe.transfer(safe, "0 ether", nonce=txn.nonce, submit_transaction=False) + except SignatureError: + # These are expected because of how the plugin works + # when not submitting + pass + + cli_ctx.logger.success(f"Canceled transaction '{txn.safe_tx_hash}'.") + + # If any txn_ids remain, they were not handled. + if txn_ids: + cli_ctx.abort_txns_not_found(txn_ids) @pending.command(cls=NetworkBoundCommand) @@ -299,3 +301,16 @@ def _show_confs(confs, extra_line: bool = True): ) if extra_line and idx < length - 1: click.echo() + + +def _check_tx_ids( + txn_ids: Sequence[Union[int, str]], txn: UnexecutedTxData +) -> Sequence[Union[int, str]]: + if txn.nonce in txn_ids: + return [x for x in txn_ids if x != txn.nonce] + + # Handle if given nonce and hash for same txn. + if txn.safe_tx_hash in txn_ids: + return [x for x in txn_ids if x != txn.nonce] + + return txn_ids diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index f73f47e..1f63cf8 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -564,13 +564,13 @@ def sign_transaction( if not submit and submitter: raise ValueError("Cannot specify a submitter if not submitting.") - elif not isinstance(submitter, AccountAPI): + elif submit and not isinstance(submitter, AccountAPI): submitter_not_specified = submitter is None submitter = self.load_submitter(submitter) if submitter_not_specified: logger.info(f"No submitter specified, so using: '{submitter.address}'.") - if not isinstance(submitter, AccountAPI) or not submitter: + if submit and (not isinstance(submitter, AccountAPI) or not submitter): # Invariant raise ValueError( "`submitter` should be either `AccountAPI` or we are not submitting here." @@ -658,7 +658,8 @@ def skip_signer(signer: AccountAPI): ) # NOTE: Signatures don't have to be in order for Safe API post - self.client.post_transaction(safe_tx, sigs_by_signer) + if submit: + self.client.post_transaction(safe_tx, sigs_by_signer) # Return None so that Ape does not try to submit the transaction. return None From 6fd8a2e0acf209e9ce2bc51d14ccd8b22708ced1 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 6 Dec 2023 14:26:26 -0600 Subject: [PATCH 102/134] fix: improve tx verbose disp --- ape_safe/_cli/pending.py | 43 +++++++++++++++++++++++++++++++--------- ape_safe/accounts.py | 3 +++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 7fd44c1..94599f9 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, Sequence, Union +from typing import Dict, List, Optional, Sequence, Union, cast import click import rich @@ -6,6 +6,8 @@ from ape.cli import NetworkBoundCommand, get_user_selected_account, network_option from ape.exceptions import SignatureError from click.exceptions import BadOptionUsage +from eth_typing import Hash32 +from eth_utils import humanize_hash from hexbytes import HexBytes from ape_safe import SafeAccount @@ -24,8 +26,8 @@ def pending(): @safe_cli_ctx @network_option() @safe_option -@click.option("--show-confs", is_flag=True) -def _list(cli_ctx: SafeCliContext, network, safe, show_confs) -> None: +@click.option("--verbose", is_flag=True) +def _list(cli_ctx: SafeCliContext, network, safe, verbose) -> None: """ View pending transactions for a Safe """ @@ -63,8 +65,31 @@ def _list(cli_ctx: SafeCliContext, network, safe, show_confs) -> None: f"safe_tx_hash={tx.safe_tx_hash}" ) - if show_confs: - _show_confs(tx.confirmations, extra_line=False) + if verbose: + fields = ("to", "value", "data", "base_gas", "gas_price") + data = {} + for field_name, value in tx.dict().items(): + if field_name not in fields: + continue + + if field_name in ("data",) and not value: + value = "0x" + elif not value: + value = "0" + + if isinstance(value, bytes): + value_str = HexBytes(value).hex() + else: + value_str = f"{value}" + + if len(value_str) > 42: + value_str = humanize_hash(cast(Hash32, HexBytes(value_str))) + + data[field_name] = value_str + + data_str = ", ".join([f"{k}={v}" for k, v in data.items()]) + rich.print(f"\t{data_str}") + _show_confs(tx.confirmations, extra_line=False, prefix="\t") if root_idx < total_items - 1 or idx < tx_len - 1: click.echo() @@ -293,12 +318,12 @@ def show_confs(cli_ctx, network, safe, txn_id): click.echo() -def _show_confs(confs, extra_line: bool = True): +def _show_confs(confs, extra_line: bool = True, prefix: Optional[str] = None): + prefix = prefix or "" length = len(confs) for idx, conf in enumerate(confs): - rich.print( - f"Confirmation {idx + 1} owner={conf.owner} signature={HexBytes(conf.signature).hex()}" - ) + signature_str = f"[default]{humanize_hash(conf.signature)}[/default]" + rich.print(f"{prefix}Confirmation {idx + 1} owner={conf.owner} signature='{signature_str}'") if extra_line and idx < length - 1: click.echo() diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 1f63cf8..a450a82 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -576,6 +576,9 @@ def sign_transaction( "`submitter` should be either `AccountAPI` or we are not submitting here." ) + # For mypy + assert isinstance(submitter, AccountAPI) + # Garner either M or M - 1 signatures, depending on if we are submitting # and whether the submitter is also a signer (both must be true to submit M - 1). # NOTE: Will skip or reorder signers based on config From c06f0f2e1642612e7a13c56b4cb73c433cb4f397 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 6 Dec 2023 19:36:57 -0600 Subject: [PATCH 103/134] fix: type issue --- ape_safe/accounts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index a450a82..0d37f35 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -577,7 +577,7 @@ def sign_transaction( ) # For mypy - assert isinstance(submitter, AccountAPI) + assert submitter is None or isinstance(submitter, AccountAPI) # Garner either M or M - 1 signatures, depending on if we are submitting # and whether the submitter is also a signer (both must be true to submit M - 1). @@ -621,6 +621,7 @@ def skip_signer(signer: AccountAPI): # We have enough signatures to commit the transaction, # and a non-signer will submit it as their own transaction and len(sigs_by_signer) >= signatures_required + and submitter is not None ): # We need to encode the submitter's address for Safe to decode if submitter.address in self.signers: From c6a21eaa78a8597fb70839bf61d8bf35188cb710 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 6 Dec 2023 22:57:01 -0600 Subject: [PATCH 104/134] feat: propose and reject 'fix' --- ape_safe/_cli/click_ext.py | 4 ++- ape_safe/_cli/pending.py | 65 +++++++++++++++++++++++++++++++++++--- ape_safe/accounts.py | 6 ++-- 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index 6af251d..636ac21 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -48,4 +48,6 @@ def _txn_ids_callback(ctx, param, value): return [int(x) if x.isnumeric() else x for x in value_ls if x] -txn_ids_argument = click.argument("txn_ids", nargs=-1, callback=_txn_ids_callback) +txn_ids_argument = click.argument( + "txn_ids", nargs=-1, callback=_txn_ids_callback, metavar="NONCE_OR_SAFE_TX_HASH(s)" +) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 94599f9..a272f49 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -5,6 +5,7 @@ from ape.api import AccountAPI from ape.cli import NetworkBoundCommand, get_user_selected_account, network_option from ape.exceptions import SignatureError +from ape.types import AddressType from click.exceptions import BadOptionUsage from eth_typing import Hash32 from eth_utils import humanize_hash @@ -12,7 +13,9 @@ from ape_safe import SafeAccount from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option, txn_ids_argument +from ape_safe.accounts import get_signatures from ape_safe.client import UnexecutedTxData +from ape_safe.utils import get_safe_tx_hash @click.group() @@ -121,6 +124,59 @@ def _handle_execute_cli_arg(ctx, param, val): ) +@pending.command(cls=NetworkBoundCommand) +@safe_cli_ctx +@network_option() +@safe_option +@click.option("--data", type=HexBytes, help="Transaction data", default=HexBytes(0)) +@click.option("--gas-price", type=int, help="Transaction gas price") +@click.option("--value", type=int, help="Transaction value", default=0) +@click.option("--to", "receiver", type=AddressType, help="Transaction receiver") +@click.option("--nonce", type=int, help="Transaction nonce") +@click.option("--execute", callback=_handle_execute_cli_arg) +def propose(cli_ctx, network, safe, data, gas_price, value, receiver, nonce, execute): + """ + Create a new transaction + """ + _ = network # Needed for NetworkBoundCommand + ecosystem = cli_ctx.chain_manager.provider.network.ecosystem + nonce = safe.next_nonce if nonce is None else nonce + txn = ecosystem.create_transaction( + value=value, data=data, gas_price=gas_price, nonce=nonce, receiver=receiver + ) + safe_tx = safe.create_safe_tx(txn) + safe_tx_hash = get_safe_tx_hash(safe_tx) + signatures = get_signatures(safe_tx_hash, safe.local_signers) + safe.client.post_transaction(safe_tx, signatures) + + num_confirmations = len(txn.confirmations) + submitter = execute if isinstance(execute, AccountAPI) else None + if execute is None and submitter is None: + # Check if we _can_ execute and ask the user. + do_execute = ( + len(safe.local_signers) > 0 + and num_confirmations >= safe.confirmations_required + and click.confirm(f"Submit transaction '{safe_tx.nonce}'") + ) + if do_execute: + # The user did provider a value for `--execute` however we are able to + # So we prompt them. + submitter = get_user_selected_account(account_type=safe.local_signers) + + # Ensure we can get the transaction from the API + new_tx = next( + safe.client.get_transactions( + starting_nonce=nonce, ending_nonce=nonce, confirmed=False, filter_by_ids=[safe_tx_hash] + ), + None, + ) + if not new_tx: + cli_ctx.abort("Failed to propose transaction.") + + if submitter: + _execute(safe, new_tx, submitter) + + def _load_submitter(ctx, param, val): if val in ctx.obj.account_manager.aliases: return ctx.obj.account_manager.load(val) @@ -165,7 +221,7 @@ def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): signatures_added = {} if num_confirmations < safe.confirmations_required: - signatures_added = safe.add_signatures(safe_tx, txn.confirmations) + signatures_added = safe.add_signatures(safe_tx, confirmations=txn.confirmations) if signatures_added: accounts_used_str = ", ".join(list(signatures_added.keys())) cli_ctx.logger.success( @@ -187,10 +243,8 @@ def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): submitter = get_user_selected_account(account_type=safe.local_signers) if submitter: - signatures = {c.owner: c.signature for c in txn.confirmations} - signatures = {**signatures, **signatures_added} - exc_tx = safe.create_execute_transaction(safe_tx, signatures) - submitter.call(exc_tx) + txn.confirmations = {**txn.confirmations, **signatures_added} + _execute(safe, txn, submitter) # If any txn_ids remain, they were not handled. if txn_ids: @@ -202,6 +256,7 @@ def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): @network_option() @safe_option @txn_ids_argument +# NOTE: Doesn't use --execute because we don't need BOOL values. @click.option("--submitter", callback=_load_submitter) def execute(cli_ctx, network, safe, txn_ids, submitter): """ diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 0d37f35..ec35dae 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -662,15 +662,15 @@ def skip_signer(signer: AccountAPI): ) # NOTE: Signatures don't have to be in order for Safe API post - if submit: - self.client.post_transaction(safe_tx, sigs_by_signer) + self.client.post_transaction(safe_tx, sigs_by_signer) # Return None so that Ape does not try to submit the transaction. return None def add_signatures( - self, safe_tx: SafeTx, confirmations: List[SafeTxConfirmation] + self, safe_tx: SafeTx, confirmations: Optional[List[SafeTxConfirmation]] = None ) -> Dict[AddressType, MessageSignature]: + confirmations = confirmations or [] if not self.local_signers: raise ApeSafeError("Cannot sign without local signers.") From b07d7a9d77ef4096f74b7c614d763d85f2bc90c2 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 7 Dec 2023 10:56:35 -0600 Subject: [PATCH 105/134] fix: issues with post_tx --- ape_safe/_cli/pending.py | 49 +++++++++++++++++++++++++++---------- ape_safe/accounts.py | 23 ++++++++++++++++- ape_safe/client/__init__.py | 8 ++++-- ape_safe/client/base.py | 18 +++++++------- 4 files changed, 73 insertions(+), 25 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index a272f49..8b63a74 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -36,7 +36,7 @@ def _list(cli_ctx: SafeCliContext, network, safe, verbose) -> None: """ _ = network # Needed for NetworkBoundCommand - txns = list(safe.client.get_transactions(confirmed=False)) + txns = list(safe.client.get_transactions(starting_nonce=safe.next_nonce, confirmed=False)) if not txns: rich.print("There are no pending transactions.") return @@ -140,16 +140,15 @@ def propose(cli_ctx, network, safe, data, gas_price, value, receiver, nonce, exe """ _ = network # Needed for NetworkBoundCommand ecosystem = cli_ctx.chain_manager.provider.network.ecosystem - nonce = safe.next_nonce if nonce is None else nonce + nonce = safe.new_nonce if nonce is None else nonce txn = ecosystem.create_transaction( value=value, data=data, gas_price=gas_price, nonce=nonce, receiver=receiver ) safe_tx = safe.create_safe_tx(txn) safe_tx_hash = get_safe_tx_hash(safe_tx) signatures = get_signatures(safe_tx_hash, safe.local_signers) - safe.client.post_transaction(safe_tx, signatures) - num_confirmations = len(txn.confirmations) + num_confirmations = 0 submitter = execute if isinstance(execute, AccountAPI) else None if execute is None and submitter is None: # Check if we _can_ execute and ask the user. @@ -161,15 +160,35 @@ def propose(cli_ctx, network, safe, data, gas_price, value, receiver, nonce, exe if do_execute: # The user did provider a value for `--execute` however we are able to # So we prompt them. - submitter = get_user_selected_account(account_type=safe.local_signers) - - # Ensure we can get the transaction from the API - new_tx = next( - safe.client.get_transactions( - starting_nonce=nonce, ending_nonce=nonce, confirmed=False, filter_by_ids=[safe_tx_hash] - ), - None, + submitter = get_user_selected_account( + prompt_message="Select a submitter", account_type=safe.local_signers + ) + + sender = ( + submitter + if isinstance(submitter, AccountAPI) + else get_user_selected_account( + prompt_message="Select a `sender`", account_type=safe.local_signers + ) ) + + safe.client.post_transaction( + safe_tx, signatures, sender=sender.address, contractTransactionHash=safe_tx_hash + ) + + # Wait for new transaction to appear + timeout = 3 + new_tx = None + + while new_tx is None and timeout > 0: + new_tx = next( + safe.client.get_transactions( + starting_nonce=safe.next_nonce, confirmed=False, filter_by_ids=[safe_tx_hash] + ), + None, + ) + timeout -= 1 + if not new_tx: cli_ctx.abort("Failed to propose transaction.") @@ -352,7 +371,11 @@ def show_confs(cli_ctx, network, safe, txn_id): safe.client.get_transactions(starting_nonce=nonce, ending_nonce=nonce, confirmed=False) ) else: - txns = list(safe.client.get_transactions(filter_by_ids=txn_id, confirmed=False)) + txns = list( + safe.client.get_transactions( + starting_nonce=safe.next_nonce, filter_by_ids=txn_id, confirmed=False + ) + ) if not txns: cli_ctx.abort_txns_not_found([txn_id]) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index ec35dae..a429077 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -276,11 +276,32 @@ def confirmations_required(self) -> int: @property def next_nonce(self) -> int: + """ + The next nonce for on-chain. If you have multiple transactions + are in the queue but not published on chain, the next nonce + refers to the earliest nonce in that queue. + """ try: return self.client.get_next_nonce() except Exception: return self.contract._view_methods_["nonce"]() + @property + def new_nonce(self): + """ + The next unused nonce in the system. This is different + than ``.next_nonce`` because it includes all nonces the + transaction service is aware of and not just the next + on-chain nonce. + """ + + # NOTE: Transaction and returned greatest nonce first. + if latest_tx := next(self.client.get_transactions(confirmed=False), None): + return latest_tx.nonce + 1 + + # No pending transactions. Use next on-chain nonce. + return self.next_nonce + def sign_message(self, msg: SignableMessage) -> Optional[MessageSignature]: raise NotImplementedError("Safe accounts do not support message signing!") @@ -307,7 +328,7 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) "to": txn.receiver if txn else self.address, # Self-call, e.g. rejection "value": txn.value if txn else 0, "data": (txn.data or b"") if txn else b"", - "nonce": self.next_nonce, + "nonce": self.new_nonce if txn is None or txn.nonce is None else txn.nonce, "operation": 0, "safeTxGas": 0, "gasPrice": 0, diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index 19d302c..85ba508 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -103,7 +103,9 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati yield from map(SafeTxConfirmation.parse_obj, data.get("results")) url = data.get("next") - def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): + def post_transaction( + self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature], **kwargs + ): tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) tx_data.signatures = HexBytes( reduce( @@ -125,8 +127,10 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna else: post_dict[key] = value + post_dict = {**post_dict, **kwargs} url = f"safes/{tx_data.safe}/multisig-transactions" - self._post(url, json=post_dict) + response = self._post(url, json=post_dict) + return response def post_signatures( self, diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py index f9dcca0..cb3de6b 100644 --- a/ape_safe/client/base.py +++ b/ape_safe/client/base.py @@ -20,6 +20,11 @@ ) from ape_safe.exceptions import ClientResponseError +DEFAULT_HEADERS = { + "Accept": "application/json", + "Content-Type": "application/json", +} + class BaseSafeClient(ABC): def __init__(self, transaction_service_url: str): @@ -135,17 +140,12 @@ def _request(self, method: str, url: str, json: Optional[Dict] = None, **kwargs) api_url = f"{self.transaction_service_url}/api/v1/{url}/" do_fail = not kwargs.pop("allow_failure", False) - if "timeout" not in kwargs: - kwargs["timeout"] = 10 - - headers = kwargs.get("headers", {}) + # Use `or 10` to handle when None is explicit. + kwargs["timeout"] = kwargs.get("timeout") or 10 # Add default headers - default_headers = { - "Accept": "application/json", - "Content-Type": "application/json", - } - kwargs["headers"] = {**default_headers, **headers} + headers = kwargs.get("headers", {}) + kwargs["headers"] = {**DEFAULT_HEADERS, **headers} response = self.session.request(method, api_url, json=json, **kwargs) if not response.ok and do_fail: From a40b42b3eea184f3e66e6e64fd679d6c5849b1d2 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 7 Dec 2023 11:44:51 -0600 Subject: [PATCH 106/134] fix: txns work now --- ape_safe/_cli/pending.py | 8 ++++---- ape_safe/client/__init__.py | 14 ++++++++++---- ape_safe/client/base.py | 2 +- ape_safe/client/mock.py | 4 ++-- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 8b63a74..4322692 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -128,7 +128,7 @@ def _handle_execute_cli_arg(ctx, param, val): @safe_cli_ctx @network_option() @safe_option -@click.option("--data", type=HexBytes, help="Transaction data", default=HexBytes(0)) +@click.option("--data", type=HexBytes, help="Transaction data", default=HexBytes("")) @click.option("--gas-price", type=int, help="Transaction gas price") @click.option("--value", type=int, help="Transaction value", default=0) @click.option("--to", "receiver", type=AddressType, help="Transaction receiver") @@ -164,16 +164,16 @@ def propose(cli_ctx, network, safe, data, gas_price, value, receiver, nonce, exe prompt_message="Select a submitter", account_type=safe.local_signers ) - sender = ( + owner = ( submitter if isinstance(submitter, AccountAPI) else get_user_selected_account( - prompt_message="Select a `sender`", account_type=safe.local_signers + prompt_message="Select an `owner`", account_type=safe.local_signers ) ) safe.client.post_transaction( - safe_tx, signatures, sender=sender.address, contractTransactionHash=safe_tx_hash + safe_tx, signatures, sender=owner.address, contractTransactionHash=safe_tx_hash ) # Wait for new transaction to appear diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index 85ba508..6390451 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -104,18 +104,19 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati url = data.get("next") def post_transaction( - self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature], **kwargs + self, safe_tx: SafeTx, signatures: Dict[AddressType, MessageSignature], **kwargs ): tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) - tx_data.signatures = HexBytes( + signature = HexBytes( reduce( lambda raw_sig, next_sig: raw_sig + (next_sig.encode_rsv() if isinstance(next_sig, MessageSignature) else next_sig), - order_by_signer(sigs), + order_by_signer(signatures), b"", ) ) - post_dict: Dict = {} + post_dict: Dict = {"signature": signature.hex()} + for key, value in tx_data.dict(by_alias=True).items(): if isinstance(value, HexBytes): post_dict[key] = value.hex() @@ -128,6 +129,11 @@ def post_transaction( post_dict[key] = value post_dict = {**post_dict, **kwargs} + + if "signatures" in post_dict: + # Signature handled above. + post_dict.pop("signatures") + url = f"safes/{tx_data.safe}/multisig-transactions" response = self._post(url, json=post_dict) return response diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py index cb3de6b..8328a5e 100644 --- a/ape_safe/client/base.py +++ b/ape_safe/client/base.py @@ -50,7 +50,7 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati ... @abstractmethod - def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): + def post_transaction(self, safe_tx: SafeTx, signatures: Dict[AddressType, MessageSignature]): ... @abstractmethod diff --git a/ape_safe/client/mock.py b/ape_safe/client/mock.py index c5ae021..61e0249 100644 --- a/ape_safe/client/mock.py +++ b/ape_safe/client/mock.py @@ -69,7 +69,7 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati if safe_tx_data := self.transactions.get(tx_hash): yield from safe_tx_data.confirmations - def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSignature]): + def post_transaction(self, safe_tx: SafeTx, signaures: Dict[AddressType, MessageSignature]): safe_tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) safe_tx_data.confirmations.extend( SafeTxConfirmation( @@ -78,7 +78,7 @@ def post_transaction(self, safe_tx: SafeTx, sigs: Dict[AddressType, MessageSigna signature=sig.encode_rsv(), signatureType=SignatureType.EOA, ) - for signer, sig in sigs.items() + for signer, sig in signaures.items() ) tx_id = cast(SafeTxID, HexBytes(safe_tx_data.safe_tx_hash).hex()) self.transactions[tx_id] = safe_tx_data From b06b3fb7d7fb379581c2a2af0dae85ac656de883 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 11 Dec 2023 08:49:40 -0600 Subject: [PATCH 107/134] chore: bump ape --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 47e0f4f..a505594 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ url="https://github.com/banteg/ape-safe", include_package_data=True, install_requires=[ - "eth-ape>=0.6.23,<0.7.0", + "eth-ape>=0.6.27,<0.7.0", "eip712>=0.2.2,<0.3.0", "requests>=2.31.0,<3", "click", # Use same version as eth-ape From aa932f91b754fbdced57d230d83662f514da47d5 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 11 Dec 2023 08:52:08 -0600 Subject: [PATCH 108/134] fix: metadata setup --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index a505594..5ecc5c4 100644 --- a/setup.py +++ b/setup.py @@ -53,9 +53,9 @@ description="""ape-safe: Gnosis Safe account plugin for Ape""", long_description=long_description, long_description_content_type="text/markdown", - author="banteg.", - author_email="banteeg@gmail.com", - url="https://github.com/banteg/ape-safe", + author="ApeWorX Ltd.", + author_email="admin@apeworx.io", + url="https://github.com/ApeWorX/ape-safe", include_package_data=True, install_requires=[ "eth-ape>=0.6.27,<0.7.0", From 0b804ba19b99a4f0a05349d086cead052400c1f9 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 11 Dec 2023 08:58:43 -0600 Subject: [PATCH 109/134] fix: type fixes --- ape_safe/_cli/click_ext.py | 5 +++-- ape_safe/accounts.py | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index 636ac21..16b9787 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -1,4 +1,4 @@ -from typing import NoReturn, Sequence, Union +from typing import NoReturn, Sequence, Union, cast import click from ape import accounts @@ -13,7 +13,8 @@ class SafeCliContext(ApeCliContextObject): def safes(self) -> SafeContainer: # NOTE: Would only happen in local development of this plugin. assert "safe" in self.account_manager.containers, "Are all API methods implemented?" - return self.account_manager.containers["safe"] + safe_container = self.account_manager.containers["safe"] + return cast(SafeContainer, safe_container) def abort_txns_not_found(self, txn_ids: Sequence[Union[int, str]]) -> NoReturn: self.abort(f"Pending transaction(s) '{', '.join([f'{x}' for x in txn_ids])}' not found.") diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index a429077..6256455 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -60,14 +60,16 @@ def accounts(self) -> Iterator[AccountAPI]: def __len__(self) -> int: return len([*self._account_files]) - def __setitem__(self, alias: str, address: str): + def __setitem__(self, alias: str, address: str): # type: ignore[override] self.save_account(alias, address) def __delitem__(self, alias: str): self.delete_account(alias) - def __iter__(self) -> Iterator["SafeAccount"]: - yield from self.accounts + def __iter__(self) -> Iterator["SafeAccount"]: # type: ignore[override] + # NOTE: We know our accounts are SafeAccounts, hence the type ignore.s + safe_accounts = cast(Iterator["SafeAccount"], self.accounts) + yield from safe_accounts def __contains__(self, item: Union[str, "SafeAccount"]) -> bool: if item is None: From d50a2fade3dd2a609d76c61256548662af8c6cd5 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 11 Dec 2023 08:58:52 -0600 Subject: [PATCH 110/134] fix: flake8 fix --- tests/test_account.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index fb4bbcc..637e4ac 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -121,15 +121,17 @@ def test_remove_owner(safe, OWNERS, mode): threshold_changed = new_threshold != safe.confirmations_required prev_owner = safe.compute_prev_signer(old_owner) - exec_transaction = lambda: safe.contract.removeOwner( # noqa: E731 - prev_owner, - old_owner, - # Can't set the threshold to zero or more than the number of owners after removal - new_threshold, - sender=safe, - impersonate=impersonate, - submit=submit, - ) + + def exec_transaction(): + return safe.contract.removeOwner( + prev_owner, + old_owner, + # Can't set the threshold to zero or more than the number of owners after removal + new_threshold, + sender=safe, + impersonate=impersonate, + submit=submit, + ) if submit: receipt = exec_transaction() From 95749d0e11805b644ed70d9eef920aa6ec8ee7ef Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 11 Dec 2023 09:38:02 -0600 Subject: [PATCH 111/134] fix: more type ignores --- ape_safe/_cli/pending.py | 6 +++--- ape_safe/accounts.py | 2 +- ape_safe/client/__init__.py | 2 +- ape_safe/utils.py | 9 +++++++++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 4322692..ac05de8 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -15,7 +15,7 @@ from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option, txn_ids_argument from ape_safe.accounts import get_signatures from ape_safe.client import UnexecutedTxData -from ape_safe.utils import get_safe_tx_hash +from ape_safe.utils import _rsv_to_message_signature, get_safe_tx_hash @click.group() @@ -301,10 +301,10 @@ def execute(cli_ctx, network, safe, txn_ids, submitter): def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) - signatures = {c.owner: c.signature for c in txn.confirmations} + signatures = {c.owner: _rsv_to_message_signature(c.signature) for c in txn.confirmations} # NOTE: We have a hack that allows bytes in the mapping, hence type ignore - exc_tx = safe.create_execute_transaction(safe_tx, signatures) # type: ignore + exc_tx = safe.create_execute_transaction(safe_tx, signatures) submitter.call(exc_tx) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 6256455..27b782d 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -399,7 +399,7 @@ def compute_prev_signer(self, signer: Union[str, AddressType, BaseAddress]) -> A return signers[index - 1] # NOTE: SENTINEL_OWNERS is the "previous" address to index 0 - return AddressType("0x0000000000000000000000000000000000000001") # type: ignore[arg-type] + return cast(AddressType, "0x0000000000000000000000000000000000000001") def load_submitter( self, diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index 6390451..c22ac77 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -61,7 +61,7 @@ def __init__( if chain_id not in TRANSACTION_SERVICE_URL: raise ClientUnsupportedChainError(chain_id) - tx_service_url = TRANSACTION_SERVICE_URL.get(chain_id) # type: ignore[assignment] + tx_service_url = TRANSACTION_SERVICE_URL[chain_id] else: raise ValueError("Must provide one of chain_id or override_url.") diff --git a/ape_safe/utils.py b/ape_safe/utils.py index d71e609..3a145aa 100644 --- a/ape_safe/utils.py +++ b/ape_safe/utils.py @@ -3,6 +3,7 @@ from ape.types import AddressType, MessageSignature from eip712.messages import calculate_hash from eth_utils import to_int +from hexbytes import HexBytes if TYPE_CHECKING: from ape_safe.client.types import SafeTxID @@ -19,3 +20,11 @@ def addr_to_int(a: AddressType) -> int: def get_safe_tx_hash(safe_tx) -> "SafeTxID": message_hash = calculate_hash(safe_tx.signable_message) return cast("SafeTxID", message_hash.hex()) + + +def _rsv_to_message_signature(rsv: HexBytes) -> MessageSignature: + # TODO: Can use Signature.from_rsv() in next version of Ape. + if len(rsv) != 65: + raise ValueError("Length of RSV bytes must be 65.") + + return MessageSignature(r=HexBytes(rsv[:32]), s=HexBytes(rsv[32:64]), v=rsv[64]) From e7b0340ebdf1e44eba3287f1b691e84caef11ab3 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 11 Dec 2023 09:56:48 -0600 Subject: [PATCH 112/134] fix: pydantic 2 issue --- ape_safe/client/mock.py | 4 +++- ape_safe/client/types.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ape_safe/client/mock.py b/ape_safe/client/mock.py index 61e0249..d2caf8a 100644 --- a/ape_safe/client/mock.py +++ b/ape_safe/client/mock.py @@ -71,8 +71,10 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati def post_transaction(self, safe_tx: SafeTx, signaures: Dict[AddressType, MessageSignature]): safe_tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) + + # NOTE: Using `construct` to avoid HexBytes pydantic v1 backimports issue. safe_tx_data.confirmations.extend( - SafeTxConfirmation( + SafeTxConfirmation.construct( owner=signer, submissionDate=datetime.now(timezone.utc), signature=sig.encode_rsv(), diff --git a/ape_safe/client/types.py b/ape_safe/client/types.py index 17703a2..5c17942 100644 --- a/ape_safe/client/types.py +++ b/ape_safe/client/types.py @@ -67,7 +67,9 @@ class UnexecutedTxData(BaseModel): @classmethod def from_safe_tx(cls, safe_tx: SafeTx, confirmations_required: int) -> "UnexecutedTxData": - return cls( + # NOTE: Using construct because of a HexBytes issue with pydantic v1 back imports. + # TODO: Switch to model_construct during v2 upgrade. + return cls.construct( safe=safe_tx._verifyingContract_, submissionDate=datetime.now(timezone.utc), modified=datetime.now(timezone.utc), From 7d753c2b46580fe82e839de7654a3648770764be Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 11 Dec 2023 10:48:42 -0600 Subject: [PATCH 113/134] fix: force to pin pydantic :( --- ape_safe/client/__init__.py | 2 ++ ape_safe/client/mock.py | 2 +- ape_safe/client/types.py | 4 +--- setup.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index c22ac77..e232595 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -87,6 +87,8 @@ def _all_transactions(self) -> Iterator[SafeApiTxData]: data = response.json() for txn in data.get("results"): + # TODO: Replace with `model_validate()` after ape 0.7. + # NOTE: Using construct because of pydantic v2 back import validation error. if "isExecuted" in txn and txn["isExecuted"]: yield ExecutedTxData.parse_obj(txn) diff --git a/ape_safe/client/mock.py b/ape_safe/client/mock.py index d2caf8a..ecd019c 100644 --- a/ape_safe/client/mock.py +++ b/ape_safe/client/mock.py @@ -74,7 +74,7 @@ def post_transaction(self, safe_tx: SafeTx, signaures: Dict[AddressType, Message # NOTE: Using `construct` to avoid HexBytes pydantic v1 backimports issue. safe_tx_data.confirmations.extend( - SafeTxConfirmation.construct( + SafeTxConfirmation( owner=signer, submissionDate=datetime.now(timezone.utc), signature=sig.encode_rsv(), diff --git a/ape_safe/client/types.py b/ape_safe/client/types.py index 5c17942..17703a2 100644 --- a/ape_safe/client/types.py +++ b/ape_safe/client/types.py @@ -67,9 +67,7 @@ class UnexecutedTxData(BaseModel): @classmethod def from_safe_tx(cls, safe_tx: SafeTx, confirmations_required: int) -> "UnexecutedTxData": - # NOTE: Using construct because of a HexBytes issue with pydantic v1 back imports. - # TODO: Switch to model_construct during v2 upgrade. - return cls.construct( + return cls( safe=safe_tx._verifyingContract_, submissionDate=datetime.now(timezone.utc), modified=datetime.now(timezone.utc), diff --git a/setup.py b/setup.py index 5ecc5c4..f8b3b6e 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ "eip712>=0.2.2,<0.3.0", "requests>=2.31.0,<3", "click", # Use same version as eth-ape - "pydantic", # Use same version as eth-ape + "pydantic<2", # TODO: Rm constraint on Ape 0.7. "eth-utils", # Use same version as eth-ape ], entry_points={ From 7fbff68b1df4fbc64f6e97158621b35cf9d005b6 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 20 Dec 2023 11:07:43 -0600 Subject: [PATCH 114/134] chore: upgrade to 0.7 --- ape_safe/_cli/pending.py | 49 +++++++++++++++---------------------- ape_safe/accounts.py | 20 +++++++++------ ape_safe/client/__init__.py | 10 ++++---- ape_safe/client/mock.py | 2 +- ape_safe/multisend.py | 2 +- setup.py | 4 +-- tests/conftest.py | 6 +++-- tests/test_account.py | 6 ++--- 8 files changed, 48 insertions(+), 51 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index ac05de8..b3a354a 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -3,7 +3,7 @@ import click import rich from ape.api import AccountAPI -from ape.cli import NetworkBoundCommand, get_user_selected_account, network_option +from ape.cli import ConnectedProviderCommand, get_user_selected_account, network_option from ape.exceptions import SignatureError from ape.types import AddressType from click.exceptions import BadOptionUsage @@ -25,17 +25,15 @@ def pending(): """ -@pending.command("list", cls=NetworkBoundCommand) +@pending.command("list", cls=ConnectedProviderCommand) @safe_cli_ctx -@network_option() @safe_option @click.option("--verbose", is_flag=True) -def _list(cli_ctx: SafeCliContext, network, safe, verbose) -> None: +def _list(cli_ctx: SafeCliContext, safe, verbose) -> None: """ View pending transactions for a Safe """ - _ = network # Needed for NetworkBoundCommand txns = list(safe.client.get_transactions(starting_nonce=safe.next_nonce, confirmed=False)) if not txns: rich.print("There are no pending transactions.") @@ -50,6 +48,7 @@ def _list(cli_ctx: SafeCliContext, network, safe, verbose) -> None: all_items = txns_by_nonce.items() total_items = len(all_items) + max_op_len = len("rejection") for root_idx, (nonce, tx_list) in enumerate(all_items): tx_len = len(tx_list) for idx, tx in enumerate(tx_list): @@ -57,10 +56,11 @@ def _list(cli_ctx: SafeCliContext, network, safe, verbose) -> None: is_rejection = not tx.value and not tx.data and tx.to == tx.safe operation_name = tx.operation.name if tx.data else "transfer" if is_rejection: - title = f"{title} rejection" - else: - title = f"{title} {operation_name}" + operation_name = "rejection" + # Add spacing (unless verbose) so columns are aligned. + spaces = (max(0, max_op_len - len(operation_name))) * " " if verbose else "" + title = f"{title} {operation_name}{spaces}" confirmations = tx.confirmations rich.print( f"{title} " @@ -71,7 +71,7 @@ def _list(cli_ctx: SafeCliContext, network, safe, verbose) -> None: if verbose: fields = ("to", "value", "data", "base_gas", "gas_price") data = {} - for field_name, value in tx.dict().items(): + for field_name, value in tx.model_dump(by_alias=True, mode="json").items(): if field_name not in fields: continue @@ -124,9 +124,8 @@ def _handle_execute_cli_arg(ctx, param, val): ) -@pending.command(cls=NetworkBoundCommand) +@pending.command(cls=ConnectedProviderCommand) @safe_cli_ctx -@network_option() @safe_option @click.option("--data", type=HexBytes, help="Transaction data", default=HexBytes("")) @click.option("--gas-price", type=int, help="Transaction gas price") @@ -134,12 +133,10 @@ def _handle_execute_cli_arg(ctx, param, val): @click.option("--to", "receiver", type=AddressType, help="Transaction receiver") @click.option("--nonce", type=int, help="Transaction nonce") @click.option("--execute", callback=_handle_execute_cli_arg) -def propose(cli_ctx, network, safe, data, gas_price, value, receiver, nonce, execute): +def propose(cli_ctx, ecosystem, safe, data, gas_price, value, receiver, nonce, execute): """ Create a new transaction """ - _ = network # Needed for NetworkBoundCommand - ecosystem = cli_ctx.chain_manager.provider.network.ecosystem nonce = safe.new_nonce if nonce is None else nonce txn = ecosystem.create_transaction( value=value, data=data, gas_price=gas_price, nonce=nonce, receiver=receiver @@ -215,14 +212,12 @@ def _load_submitter(ctx, param, val): return None -@pending.command(cls=NetworkBoundCommand) +@pending.command(cls=ConnectedProviderCommand) @safe_cli_ctx -@network_option() @safe_option @txn_ids_argument @click.option("--execute", callback=_handle_execute_cli_arg) -def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): - _ = network # Needed for NetworkBoundCommand +def approve(cli_ctx: SafeCliContext, safe, txn_ids, execute): submitter: Optional[AccountAPI] = execute if isinstance(execute, AccountAPI) else None pending_transactions = list( safe.client.get_transactions(confirmed=False, starting_nonce=safe.next_nonce) @@ -235,7 +230,7 @@ def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): # Not a specified txn. continue - safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) + safe_tx = safe.create_safe_tx(**txn.model_dump(by_alias=True, mode="json")) num_confirmations = len(txn.confirmations) signatures_added = {} @@ -270,14 +265,14 @@ def approve(cli_ctx: SafeCliContext, network, safe, txn_ids, execute): cli_ctx.abort_txns_not_found(txn_ids) -@pending.command(cls=NetworkBoundCommand) +@pending.command(cls=ConnectedProviderCommand) @safe_cli_ctx @network_option() @safe_option @txn_ids_argument # NOTE: Doesn't use --execute because we don't need BOOL values. @click.option("--submitter", callback=_load_submitter) -def execute(cli_ctx, network, safe, txn_ids, submitter): +def execute(cli_ctx, safe, txn_ids, submitter): """ Execute a transaction """ @@ -300,7 +295,7 @@ def execute(cli_ctx, network, safe, txn_ids, submitter): def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): - safe_tx = safe.create_safe_tx(**txn.dict(by_alias=True)) + safe_tx = safe.create_safe_tx(**txn.model_dump(mode="json", by_alias=True)) signatures = {c.owner: _rsv_to_message_signature(c.signature) for c in txn.confirmations} # NOTE: We have a hack that allows bytes in the mapping, hence type ignore @@ -309,9 +304,8 @@ def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): submitter.call(exc_tx) -@pending.command(cls=NetworkBoundCommand) +@pending.command(cls=ConnectedProviderCommand) @safe_cli_ctx -@network_option() @safe_option @txn_ids_argument def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): @@ -319,7 +313,6 @@ def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): Reject one or more pending transactions """ - _ = network # Needed for NetworkBoundCommand pending_transactions = safe.client.get_transactions( confirmed=False, starting_nonce=safe.next_nonce ) @@ -352,16 +345,14 @@ def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): cli_ctx.abort_txns_not_found(txn_ids) -@pending.command(cls=NetworkBoundCommand) +@pending.command(cls=ConnectedProviderCommand) @safe_cli_ctx -@network_option() @safe_option @click.argument("txn_id") -def show_confs(cli_ctx, network, safe, txn_id): +def show_confs(cli_ctx, safe, txn_id): """ Show existing confirmations """ - _ = network # Needed for NetworkBoundCommand if txn_id.isnumeric(): nonce = int(txn_id) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 27b782d..5f11569 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -1,7 +1,7 @@ import json import os from pathlib import Path -from typing import Dict, Iterable, Iterator, List, Mapping, Optional, Tuple, Type, Union, cast +from typing import Any, Dict, Iterable, Iterator, List, Mapping, Optional, Tuple, Type, Union, cast from ape.api import AccountAPI, AccountContainerAPI, ReceiptAPI, TransactionAPI from ape.api.address import BaseAddress @@ -10,7 +10,7 @@ from ape.exceptions import ProviderNotConnectedError from ape.logging import logger from ape.managers.accounts import AccountManager, TestAccountManager -from ape.types import AddressType, HexBytes, MessageSignature, SignableMessage +from ape.types import AddressType, HexBytes, MessageSignature from ape.utils import ZERO_ADDRESS, cached_property from ape_ethereum.transactions import TransactionType from eip712.common import create_safe_tx_def @@ -206,11 +206,13 @@ def contract(self) -> ContractInstance: if fallback_signatures < contract_signatures: return safe_contract # for some reason this never gets hit - contract_type = safe_contract.contract_type.dict() - fallback_type = self.fallback_handler.contract_type.dict() + contract_type = safe_contract.contract_type.model_dump(by_alias=True, mode="json") + fallback_type = self.fallback_handler.contract_type.model_dump( + by_alias=True, mode="json" + ) contract_type["abi"].extend(fallback_type["abi"]) return self.chain_manager.contracts.instance_at( - self.address, contract_type=ContractType.parse_obj(contract_type) + self.address, contract_type=ContractType.model_validate(contract_type) ) else: @@ -219,7 +221,7 @@ def contract(self) -> ContractInstance: @cached_property def fallback_handler(self) -> Optional[ContractInstance]: slot = keccak(text="fallback_manager.handler.address") - value = self.provider.get_storage_at(self.address, slot) + value = self.provider.get_storage(self.address, slot) address = self.network_manager.ecosystem.decode_address(value[-20:]) return ( self.chain_manager.contracts.instance_at(address) if address != ZERO_ADDRESS else None @@ -304,7 +306,7 @@ def new_nonce(self): # No pending transactions. Use next on-chain nonce. return self.next_nonce - def sign_message(self, msg: SignableMessage) -> Optional[MessageSignature]: + def sign_message(self, msg: Any, **signer_options) -> Optional[MessageSignature]: raise NotImplementedError("Safe accounts do not support message signing!") @property @@ -345,7 +347,9 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs) def pending_transactions(self) -> Iterator[Tuple[SafeTx, List[SafeTxConfirmation]]]: for executed_tx in self.client.get_transactions(confirmed=False): - yield self.create_safe_tx(**executed_tx.dict(by_alias=True)), executed_tx.confirmations + yield self.create_safe_tx( + **executed_tx.model_dump(mode="json", by_alias=True) + ), executed_tx.confirmations @property def local_signers(self) -> List[AccountAPI]: diff --git a/ape_safe/client/__init__.py b/ape_safe/client/__init__.py index e232595..511a0e5 100644 --- a/ape_safe/client/__init__.py +++ b/ape_safe/client/__init__.py @@ -71,7 +71,7 @@ def __init__( @property def safe_details(self) -> SafeDetails: response = self._get(f"safes/{self.address}") - return SafeDetails.parse_obj(response.json()) + return SafeDetails.model_validate(response.json()) def get_next_nonce(self) -> int: return self.safe_details.nonce @@ -90,10 +90,10 @@ def _all_transactions(self) -> Iterator[SafeApiTxData]: # TODO: Replace with `model_validate()` after ape 0.7. # NOTE: Using construct because of pydantic v2 back import validation error. if "isExecuted" in txn and txn["isExecuted"]: - yield ExecutedTxData.parse_obj(txn) + yield ExecutedTxData.model_validate(txn) else: - yield UnexecutedTxData.parse_obj(txn) + yield UnexecutedTxData.model_validate(txn) url = data.get("next") @@ -102,7 +102,7 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati while url: response = self._get(url) data = response.json() - yield from map(SafeTxConfirmation.parse_obj, data.get("results")) + yield from map(SafeTxConfirmation.model_validate, data.get("results")) url = data.get("next") def post_transaction( @@ -119,7 +119,7 @@ def post_transaction( ) post_dict: Dict = {"signature": signature.hex()} - for key, value in tx_data.dict(by_alias=True).items(): + for key, value in tx_data.model_dump(by_alias=True, mode="json").items(): if isinstance(value, HexBytes): post_dict[key] = value.hex() elif isinstance(value, OperationType): diff --git a/ape_safe/client/mock.py b/ape_safe/client/mock.py index ecd019c..904f062 100644 --- a/ape_safe/client/mock.py +++ b/ape_safe/client/mock.py @@ -29,7 +29,7 @@ def __init__(self, contract: ContractInstance): @property def safe_details(self) -> SafeDetails: slot = keccak(text="fallback_manager.handler.address") - value = self.provider.get_storage_at(self.contract.address, slot) + value = self.provider.get_storage(self.contract.address, slot) fallback_address = self.network_manager.ecosystem.decode_address(value[-20:]) return SafeDetails( diff --git a/ape_safe/multisend.py b/ape_safe/multisend.py index 182763b..ead9dfd 100644 --- a/ape_safe/multisend.py +++ b/ape_safe/multisend.py @@ -129,7 +129,7 @@ def contract(self) -> ContractInstance: # All versions have this ABI contract = self.chain_manager.contracts.instance_at( multisend_address, - contract_type=ContractType.parse_obj(MULTISEND_CONTRACT_TYPE), + contract_type=ContractType.model_validate(MULTISEND_CONTRACT_TYPE), ) if contract.code != MULTISEND_CODE: diff --git a/setup.py b/setup.py index f8b3b6e..6803253 100644 --- a/setup.py +++ b/setup.py @@ -59,10 +59,10 @@ include_package_data=True, install_requires=[ "eth-ape>=0.6.27,<0.7.0", - "eip712>=0.2.2,<0.3.0", "requests>=2.31.0,<3", + "eip712", # Use same version as eth-ape "click", # Use same version as eth-ape - "pydantic<2", # TODO: Rm constraint on Ape 0.7. + "pydantic", # Use same version as eth-ape "eth-utils", # Use same version as eth-ape ], entry_points={ diff --git a/tests/conftest.py b/tests/conftest.py index 8b1374f..e1a6eee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -104,13 +104,15 @@ def safe(safe_data_file): @pytest.fixture def token(deployer: SafeAccount): - contract = ContractType.parse_file(contracts_directory / "Token.json") + text = (contracts_directory / "Token.json").read_text() + contract = ContractType.model_validate_json(text) return deployer.deploy(ContractContainer(contract)) @pytest.fixture def vault(deployer: SafeAccount, token): - vault = ContractContainer(ContractType.parse_file(contracts_directory / "VyperVault.json")) + text = (contracts_directory / "VyperVault.json").read_text() + vault = ContractContainer(ContractType.model_validate_json(text)) return deployer.deploy(vault, token) diff --git a/tests/test_account.py b/tests/test_account.py index 637e4ac..f53f1d4 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -46,7 +46,7 @@ def test_swap_owner(safe, accounts, OWNERS, mode): safe_tx_hash = add_0x_prefix(f"{pending_txns[0].safe_tx_hash}") safe_tx_data = pending_txns[0] - safe_tx = safe.create_safe_tx(**safe_tx_data.dict(by_alias=True)) + safe_tx = safe.create_safe_tx(**safe_tx_data.model_dump(by_alias=True, mode="json")) # Ensure client confirmations works client_confs = list(safe.client.get_confirmations(safe_tx_hash)) @@ -98,7 +98,7 @@ def test_add_owner(safe, accounts, OWNERS, mode): # `safe_tx` is in mock client, extract it and execute it successfully this time safe_tx_data = next(safe.client.get_transactions(confirmed=False)) - safe_tx = safe.create_safe_tx(**safe_tx_data.dict(by_alias=True)) + safe_tx = safe.create_safe_tx(**safe_tx_data.model_dump(by_alias=True, mode="json")) receipt = safe.submit_safe_tx(safe_tx) assert receipt.events == [ @@ -146,7 +146,7 @@ def exec_transaction(): # `safe_tx` is in mock client, extract it and execute it successfully this time safe_tx_data = next(safe.client.get_transactions(confirmed=False)) - safe_tx = safe.create_safe_tx(**safe_tx_data.dict(by_alias=True)) + safe_tx = safe.create_safe_tx(**safe_tx_data.model_dump(by_alias=True, mode="json")) receipt = safe.submit_safe_tx(safe_tx) expected_events = [ From adf4e391cf5915d77c8c9a02ea8ea142a889fc11 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 20 Dec 2023 11:33:28 -0600 Subject: [PATCH 115/134] feat: complete upgrade --- tests/test_account.py | 17 +++++++++++++++++ tests/test_multisend.py | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index f53f1d4..21c27c3 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -1,5 +1,7 @@ import pytest +from ape.api import AccountAPI from ape.exceptions import SignatureError +from ape.types import AddressType from eth_utils import add_0x_prefix @@ -158,3 +160,18 @@ def exec_transaction(): assert receipt.events == expected_events assert old_owner not in safe.signers + + +def test_account_type(safe): + actual = type(safe) + assert issubclass(actual, AccountAPI) + + +def test_safe_account_convert(safe): + """ + We had a bug where converting safe accounts to AddressType + would fail. + """ + convert = safe.conversion_manager.convert + actual = convert(safe, AddressType) + assert actual == safe.address diff --git a/tests/test_multisend.py b/tests/test_multisend.py index deefd2e..9d4712b 100644 --- a/tests/test_multisend.py +++ b/tests/test_multisend.py @@ -9,7 +9,7 @@ def test_asset(vault, token): def test_default_operation(safe, token, vault, multisend): amount = token.balanceOf(safe) - multisend.add(token.approve, vault, safe) + multisend.add(token.approve, vault, 123) multisend.add(vault.transfer, safe, amount) receipt = multisend(sender=safe) assert receipt.txn_hash @@ -17,7 +17,7 @@ def test_default_operation(safe, token, vault, multisend): def test_no_operation(safe, token, vault, multisend): amount = token.balanceOf(safe) - multisend.add(token.approve, vault, safe) + multisend.add(token.approve, vault, 123) multisend.add(vault.transfer, safe, amount) with pytest.raises(SafeLogicError, match="Safe transaction failed"): multisend(sender=safe, operation=0) From 377deef4d4555aa84873b40fe787b571c0991a3f Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 20 Dec 2023 11:40:03 -0600 Subject: [PATCH 116/134] chore: bump ape --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6803253..99dc90a 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ url="https://github.com/ApeWorX/ape-safe", include_package_data=True, install_requires=[ - "eth-ape>=0.6.27,<0.7.0", + "eth-ape>=0.7.0,<0.8", "requests>=2.31.0,<3", "eip712", # Use same version as eth-ape "click", # Use same version as eth-ape From 72b372a788baeb294974b874d65b6c92531e991d Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 20 Dec 2023 11:44:50 -0600 Subject: [PATCH 117/134] refactor: sigs --- ape_safe/_cli/pending.py | 12 +++++++----- ape_safe/utils.py | 9 --------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index b3a354a..87b1f8f 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -5,7 +5,7 @@ from ape.api import AccountAPI from ape.cli import ConnectedProviderCommand, get_user_selected_account, network_option from ape.exceptions import SignatureError -from ape.types import AddressType +from ape.types import AddressType, MessageSignature from click.exceptions import BadOptionUsage from eth_typing import Hash32 from eth_utils import humanize_hash @@ -15,7 +15,7 @@ from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option, txn_ids_argument from ape_safe.accounts import get_signatures from ape_safe.client import UnexecutedTxData -from ape_safe.utils import _rsv_to_message_signature, get_safe_tx_hash +from ape_safe.utils import get_safe_tx_hash @click.group() @@ -296,11 +296,13 @@ def execute(cli_ctx, safe, txn_ids, submitter): def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): safe_tx = safe.create_safe_tx(**txn.model_dump(mode="json", by_alias=True)) - signatures = {c.owner: _rsv_to_message_signature(c.signature) for c in txn.confirmations} - # NOTE: We have a hack that allows bytes in the mapping, hence type ignore - exc_tx = safe.create_execute_transaction(safe_tx, signatures) + # TODO: Can remove type ignore after Ape 0.7.1 + signatures: Dict[AddressType, MessageSignature] = { + c.owner: MessageSignature.from_rsv(c.signature) for c in txn.confirmations # type: ignore + } + exc_tx = safe.create_execute_transaction(safe_tx, signatures) submitter.call(exc_tx) diff --git a/ape_safe/utils.py b/ape_safe/utils.py index 3a145aa..d71e609 100644 --- a/ape_safe/utils.py +++ b/ape_safe/utils.py @@ -3,7 +3,6 @@ from ape.types import AddressType, MessageSignature from eip712.messages import calculate_hash from eth_utils import to_int -from hexbytes import HexBytes if TYPE_CHECKING: from ape_safe.client.types import SafeTxID @@ -20,11 +19,3 @@ def addr_to_int(a: AddressType) -> int: def get_safe_tx_hash(safe_tx) -> "SafeTxID": message_hash = calculate_hash(safe_tx.signable_message) return cast("SafeTxID", message_hash.hex()) - - -def _rsv_to_message_signature(rsv: HexBytes) -> MessageSignature: - # TODO: Can use Signature.from_rsv() in next version of Ape. - if len(rsv) != 65: - raise ValueError("Length of RSV bytes must be 65.") - - return MessageSignature(r=HexBytes(rsv[:32]), s=HexBytes(rsv[32:64]), v=rsv[64]) From c7ceb7731bf5de84bee1050bef8321e6b3739bda Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 20 Dec 2023 12:12:57 -0600 Subject: [PATCH 118/134] chore: more 07 upgrade --- ape_safe/_cli/pending.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 87b1f8f..682cb60 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -3,7 +3,7 @@ import click import rich from ape.api import AccountAPI -from ape.cli import ConnectedProviderCommand, get_user_selected_account, network_option +from ape.cli import ConnectedProviderCommand, network_option, select_account from ape.exceptions import SignatureError from ape.types import AddressType, MessageSignature from click.exceptions import BadOptionUsage @@ -157,15 +157,15 @@ def propose(cli_ctx, ecosystem, safe, data, gas_price, value, receiver, nonce, e if do_execute: # The user did provider a value for `--execute` however we are able to # So we prompt them. - submitter = get_user_selected_account( - prompt_message="Select a submitter", account_type=safe.local_signers + submitter = select_account( + prompt_message="Select a submitter", key=safe.local_signers ) owner = ( submitter if isinstance(submitter, AccountAPI) - else get_user_selected_account( - prompt_message="Select an `owner`", account_type=safe.local_signers + else select_account( + prompt_message="Select an `owner`", key=safe.local_signers ) ) @@ -207,7 +207,7 @@ def _load_submitter(ctx, param, val): if not safe.local_signers: ctx.obj.abort("Cannot use `--execute TRUE` without a local signer.") - return get_user_selected_account(account_type=safe.local_signers) + return select_account(key=safe.local_signers) return None @@ -254,7 +254,7 @@ def approve(cli_ctx: SafeCliContext, safe, txn_ids, execute): if do_execute: # The user did provider a value for `--execute` however we are able to # So we prompt them. - submitter = get_user_selected_account(account_type=safe.local_signers) + submitter = select_account(key=safe.local_signers) if submitter: txn.confirmations = {**txn.confirmations, **signatures_added} From 8f737b1f3683fa81586b0338b4163348a1ced992 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 20 Dec 2023 12:24:00 -0600 Subject: [PATCH 119/134] chore: spacing fix --- ape_safe/_cli/pending.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 682cb60..24d56b2 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -29,7 +29,7 @@ def pending(): @safe_cli_ctx @safe_option @click.option("--verbose", is_flag=True) -def _list(cli_ctx: SafeCliContext, safe, verbose) -> None: +def _list(cli_ctx, safe, verbose) -> None: """ View pending transactions for a Safe """ @@ -59,7 +59,7 @@ def _list(cli_ctx: SafeCliContext, safe, verbose) -> None: operation_name = "rejection" # Add spacing (unless verbose) so columns are aligned. - spaces = (max(0, max_op_len - len(operation_name))) * " " if verbose else "" + spaces = "" if verbose else (max(0, max_op_len - len(operation_name))) * " " title = f"{title} {operation_name}{spaces}" confirmations = tx.confirmations rich.print( From ce487f36ba060b1b7ed78abca6d1124b8f0956a7 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 20 Dec 2023 12:25:38 -0600 Subject: [PATCH 120/134] refactor: use a lambda --- ape_safe/_cli/pending.py | 8 ++------ ape_safe/utils.py | 5 +---- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 24d56b2..91f8881 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -157,16 +157,12 @@ def propose(cli_ctx, ecosystem, safe, data, gas_price, value, receiver, nonce, e if do_execute: # The user did provider a value for `--execute` however we are able to # So we prompt them. - submitter = select_account( - prompt_message="Select a submitter", key=safe.local_signers - ) + submitter = select_account(prompt_message="Select a submitter", key=safe.local_signers) owner = ( submitter if isinstance(submitter, AccountAPI) - else select_account( - prompt_message="Select an `owner`", key=safe.local_signers - ) + else select_account(prompt_message="Select an `owner`", key=safe.local_signers) ) safe.client.post_transaction( diff --git a/ape_safe/utils.py b/ape_safe/utils.py index d71e609..c890f5c 100644 --- a/ape_safe/utils.py +++ b/ape_safe/utils.py @@ -10,10 +10,7 @@ def order_by_signer(signatures: Mapping[AddressType, MessageSignature]) -> List[MessageSignature]: # NOTE: Must order signatures in ascending order of signer address (converted to int) - def addr_to_int(a: AddressType) -> int: - return to_int(hexstr=a) - - return list(signatures[signer] for signer in sorted(signatures, key=addr_to_int)) + return list(signatures[signer] for signer in sorted(signatures, key=lambda a: to_int(hexstr=a))) def get_safe_tx_hash(safe_tx) -> "SafeTxID": From 2ac779f5ba2b3cd8aff2847b32e752c4d67f0204 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 20 Dec 2023 12:37:54 -0600 Subject: [PATCH 121/134] test: add tests for order --- tests/test_utils.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/test_utils.py diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..808ca07 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,32 @@ +from ape_safe.utils import order_by_signer + + +def test_order_by_signer_empty(): + assert order_by_signer({}) == [] + + +def test_order_by_signer_1_sig(accounts): + signature = accounts[0].sign_message("hello") + signature_map = {accounts[0].address: signature} + assert order_by_signer(signature_map) == [signature] + + +def test_order_by_signer_n_sigs(accounts): + # NOTE: acct_0 > acct_1 by integer value. + acct_0 = accounts[0] # 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + acct_1 = accounts[1] # 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 + + signature_0 = acct_0.sign_message("hello") + signature_1 = acct_1.sign_message("hello") + + # Ensure all orders of the dict work. + signature_map_0 = {acct_0.address: signature_0, acct_1.address: signature_1} + signature_map_1 = {acct_1.address: signature_1, acct_0.address: signature_0} + + # We expect the signatures to be sorted in ascending order by the + # signer's address. Here, acct_0 > acct_1 so acct_1's signature is + # the first and acct_0's is the latter. + expected = [signature_1, signature_0] + act_0 = order_by_signer(signature_map_0) + act_1 = order_by_signer(signature_map_1) + assert act_0 == act_1 == expected From 9f5c9395ee2fd3fb929e278ce6e3a26051d915bd Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 20 Dec 2023 13:42:01 -0600 Subject: [PATCH 122/134] fix: fixes --- ape_safe/_cli/pending.py | 34 +++++++++++++++-------- ape_safe/accounts.py | 59 ++++++++++++++++++---------------------- ape_safe/client/base.py | 4 ++- ape_safe/client/mock.py | 6 ++-- 4 files changed, 56 insertions(+), 47 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 91f8881..d52ffbd 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -100,7 +100,7 @@ def _list(cli_ctx, safe, verbose) -> None: # NOTE: The handling of the `--execute` flag in the `pending` CLI # all happens here EXCEPT if a pending tx is executable and no # value of `--execute` was provided. -def _handle_execute_cli_arg(ctx, param, val): +def _handle_execute_cli_arg(ctx, param, val) -> Optional[Union[AccountAPI, bool]]: """ Either returns the account or ``False`` meaning don't execute """ @@ -190,7 +190,10 @@ def propose(cli_ctx, ecosystem, safe, data, gas_price, value, receiver, nonce, e def _load_submitter(ctx, param, val): - if val in ctx.obj.account_manager.aliases: + if val is None: + return None + + elif val in ctx.obj.account_manager.aliases: return ctx.obj.account_manager.load(val) # Account address - execute using this account. @@ -221,7 +224,7 @@ def approve(cli_ctx: SafeCliContext, safe, txn_ids, execute): for txn in pending_transactions: # Figure out which given ID(s) we are handling. length_before = len(txn_ids) - txn_ids = _check_tx_ids(txn_ids, txn) + txn_ids = _filter_tx_from_ids(txn_ids, txn) if len(txn_ids) == length_before: # Not a specified txn. continue @@ -263,7 +266,6 @@ def approve(cli_ctx: SafeCliContext, safe, txn_ids, execute): @pending.command(cls=ConnectedProviderCommand) @safe_cli_ctx -@network_option() @safe_option @txn_ids_argument # NOTE: Doesn't use --execute because we don't need BOOL values. @@ -275,10 +277,14 @@ def execute(cli_ctx, safe, txn_ids, submitter): pending_transactions = list( safe.client.get_transactions(confirmed=False, starting_nonce=safe.next_nonce) ) + + if not submitter: + submitter = select_account("Select a submitter", key=safe.local_signers) + for txn in pending_transactions: # Figure out which given ID(s) we are handling. length_before = len(txn_ids) - txn_ids = _check_tx_ids(txn_ids, txn) + txn_ids = _filter_tx_from_ids(txn_ids, txn) if len(txn_ids) == length_before: # Not a specified txn. continue @@ -306,11 +312,13 @@ def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): @safe_cli_ctx @safe_option @txn_ids_argument -def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): +@click.option("--execute", callback=_handle_execute_cli_arg) +def reject(cli_ctx: SafeCliContext, safe, txn_ids, execute): """ Reject one or more pending transactions """ - + submit = False if execute is False else True + submitter = execute if isinstance(execute, AccountAPI) else None pending_transactions = safe.client.get_transactions( confirmed=False, starting_nonce=safe.next_nonce ) @@ -318,7 +326,7 @@ def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): for txn in pending_transactions: # Figure out which given ID(s) we are handling. length_before = len(txn_ids) - txn_ids = _check_tx_ids(txn_ids, txn) + txn_ids = _filter_tx_from_ids(txn_ids, txn) if len(txn_ids) == length_before: # Not a specified txn. continue @@ -330,7 +338,9 @@ def reject(cli_ctx: SafeCliContext, network, safe, txn_ids): elif click.confirm(f"{txn}\nCancel Transaction?"): try: - safe.transfer(safe, "0 ether", nonce=txn.nonce, submit_transaction=False) + safe.transfer( + safe, "0 ether", nonce=txn.nonce, submit_transaction=submit, submitter=submitter + ) except SignatureError: # These are expected because of how the plugin works # when not submitting @@ -395,14 +405,16 @@ def _show_confs(confs, extra_line: bool = True, prefix: Optional[str] = None): click.echo() -def _check_tx_ids( +# Helper method for handling transactions in a loop. +def _filter_tx_from_ids( txn_ids: Sequence[Union[int, str]], txn: UnexecutedTxData ) -> Sequence[Union[int, str]]: if txn.nonce in txn_ids: + # Filter out all transactions with the same nonce return [x for x in txn_ids if x != txn.nonce] # Handle if given nonce and hash for same txn. if txn.safe_tx_hash in txn_ids: - return [x for x in txn_ids if x != txn.nonce] + return [x for x in txn_ids if x != txn.safe_tx_hash] return txn_ids diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 5f11569..6d22ca8 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -585,26 +585,19 @@ def sign_transaction( Returns: Optional[``TransactionAPI``]: Returns ``None`` if the transaction is successful. """ - safe_tx = self.create_safe_tx(txn, **signer_options) - # Determine who is submitting the transaction (if enough signatures are gathered) if not submit and submitter: raise ValueError("Cannot specify a submitter if not submitting.") - elif submit and not isinstance(submitter, AccountAPI): - submitter_not_specified = submitter is None - submitter = self.load_submitter(submitter) - if submitter_not_specified: - logger.info(f"No submitter specified, so using: '{submitter.address}'.") - - if submit and (not isinstance(submitter, AccountAPI) or not submitter): - # Invariant - raise ValueError( - "`submitter` should be either `AccountAPI` or we are not submitting here." - ) + safe_tx = self.create_safe_tx(txn, **signer_options) - # For mypy - assert submitter is None or isinstance(submitter, AccountAPI) + # Determine who is submitting the transaction (if enough signatures are gathered) + # NOTE: This is needed even if not submitting right now. + submitter_account: AccountAPI = ( + self.load_submitter(submitter) + if submitter is None or not isinstance(submitter, AccountAPI) + else submitter + ) # Garner either M or M - 1 signatures, depending on if we are submitting # and whether the submitter is also a signer (both must be true to submit M - 1). @@ -613,13 +606,13 @@ def sign_transaction( # If number of signatures required not specified, figure out how many are needed if not signatures_required: - if submitter and submitter.address in self.signers: + if submit and submitter_account.address in self.signers: # Sender doesn't have to sign signatures_required = self.confirmations_required - 1 # NOTE: Adjust signers to sign with by skipping submitter - available_signers = filter(lambda s: s != submitter, available_signers) + available_signers = filter(lambda s: s != submitter_account, available_signers) - else: # NOTE: `submitter` is `None` if `submit` is False + else: # Not submitting, or submitter isn't a signer, so we need all confirmations signatures_required = self.confirmations_required @@ -634,25 +627,26 @@ def skip_signer(signer: AccountAPI): # Check if transaction has existing tracked signatures sigs_by_signer = self._all_approvals(safe_tx) + safe_tx_hash = get_safe_tx_hash(safe_tx) # Attempt to fetch just enough signatures to satisfy the amount we need # NOTE: It is okay to have less signatures, but it never should fetch more than needed - signers = [x for x in self.local_signers if x.address not in sigs_by_signer] + signers = [x for x in available_signers if x.address not in sigs_by_signer] if signers: - safe_tx_hash = get_safe_tx_hash(safe_tx) new_signatures = get_signatures(safe_tx_hash, signers) sigs_by_signer = {**sigs_by_signer, **new_signatures} if ( - submit # NOTE: `submitter` should be set if `submit=True` + submit # We have enough signatures to commit the transaction, # and a non-signer will submit it as their own transaction and len(sigs_by_signer) >= signatures_required - and submitter is not None ): # We need to encode the submitter's address for Safe to decode - if submitter.address in self.signers: - sigs_by_signer[submitter.address] = self._preapproved_signature(submitter) + if submitter_account.address in self.signers: + sigs_by_signer[submitter_account.address] = self._preapproved_signature( + submitter_account + ) # Inherit gas args from safe_tx, if set gas_args = {"gas_limit": txn.gas_limit} @@ -668,20 +662,14 @@ def skip_signer(signer: AccountAPI): safe_tx, sigs_by_signer, **gas_args, - nonce=submitter.nonce, # NOTE: Required to correctly set nonce in encoded txn + nonce=submitter_account.nonce, ) - return submitter.sign_transaction(exec_transaction, **signer_options) + return submitter_account.sign_transaction(exec_transaction, **signer_options) elif submit: # NOTE: User wanted to submit transaction, but we can't, so don't publish to API raise NotEnoughSignatures(signatures_required, len(sigs_by_signer)) - elif submitter and submitter.address in self.signers: - # Not enough signatures were gathered to submit, but submitter also didn't sign yet, - # so might as well get one more sig from them before publishing confirmations to API. - signatures = get_signatures(get_safe_tx_hash(safe_tx), [submitter]) - sigs_by_signer = {**sigs_by_signer, **signatures} - # NOTE: Not enough signatures were obtained to publish on-chain logger.info( f"Collected {len(sigs_by_signer)}/{self.confirmations_required} signatures " @@ -689,7 +677,12 @@ def skip_signer(signer: AccountAPI): ) # NOTE: Signatures don't have to be in order for Safe API post - self.client.post_transaction(safe_tx, sigs_by_signer) + self.client.post_transaction( + safe_tx, + sigs_by_signer, + contractTransactionHash=safe_tx_hash, + sender=submitter_account.address, + ) # Return None so that Ape does not try to submit the transaction. return None diff --git a/ape_safe/client/base.py b/ape_safe/client/base.py index 8328a5e..5832c85 100644 --- a/ape_safe/client/base.py +++ b/ape_safe/client/base.py @@ -50,7 +50,9 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati ... @abstractmethod - def post_transaction(self, safe_tx: SafeTx, signatures: Dict[AddressType, MessageSignature]): + def post_transaction( + self, safe_tx: SafeTx, signatures: Dict[AddressType, MessageSignature], **kwargs + ): ... @abstractmethod diff --git a/ape_safe/client/mock.py b/ape_safe/client/mock.py index 904f062..96208be 100644 --- a/ape_safe/client/mock.py +++ b/ape_safe/client/mock.py @@ -69,7 +69,9 @@ def get_confirmations(self, safe_tx_hash: SafeTxID) -> Iterator[SafeTxConfirmati if safe_tx_data := self.transactions.get(tx_hash): yield from safe_tx_data.confirmations - def post_transaction(self, safe_tx: SafeTx, signaures: Dict[AddressType, MessageSignature]): + def post_transaction( + self, safe_tx: SafeTx, signatures: Dict[AddressType, MessageSignature], **kwargs + ): safe_tx_data = UnexecutedTxData.from_safe_tx(safe_tx, self.safe_details.threshold) # NOTE: Using `construct` to avoid HexBytes pydantic v1 backimports issue. @@ -80,7 +82,7 @@ def post_transaction(self, safe_tx: SafeTx, signaures: Dict[AddressType, Message signature=sig.encode_rsv(), signatureType=SignatureType.EOA, ) - for signer, sig in signaures.items() + for signer, sig in signatures.items() ) tx_id = cast(SafeTxID, HexBytes(safe_tx_data.safe_tx_hash).hex()) self.transactions[tx_id] = safe_tx_data From 2a0a883f9728eb70c1a267f8d44adf580f5eb66e Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 20 Dec 2023 14:31:05 -0600 Subject: [PATCH 123/134] fix: more tx fix and refactor --- ape_safe/_cli/pending.py | 21 ++++++++++----------- ape_safe/accounts.py | 5 +++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index d52ffbd..4992255 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -3,7 +3,7 @@ import click import rich from ape.api import AccountAPI -from ape.cli import ConnectedProviderCommand, network_option, select_account +from ape.cli import ConnectedProviderCommand from ape.exceptions import SignatureError from ape.types import AddressType, MessageSignature from click.exceptions import BadOptionUsage @@ -157,13 +157,9 @@ def propose(cli_ctx, ecosystem, safe, data, gas_price, value, receiver, nonce, e if do_execute: # The user did provider a value for `--execute` however we are able to # So we prompt them. - submitter = select_account(prompt_message="Select a submitter", key=safe.local_signers) + submitter = safe.select_signer(for_="submitter") - owner = ( - submitter - if isinstance(submitter, AccountAPI) - else select_account(prompt_message="Select an `owner`", key=safe.local_signers) - ) + owner = submitter if isinstance(submitter, AccountAPI) else safe.select_signer(for_="owner") safe.client.post_transaction( safe_tx, signatures, sender=owner.address, contractTransactionHash=safe_tx_hash @@ -206,7 +202,7 @@ def _load_submitter(ctx, param, val): if not safe.local_signers: ctx.obj.abort("Cannot use `--execute TRUE` without a local signer.") - return select_account(key=safe.local_signers) + return safe.select_signer(for_="submitter") return None @@ -253,7 +249,7 @@ def approve(cli_ctx: SafeCliContext, safe, txn_ids, execute): if do_execute: # The user did provider a value for `--execute` however we are able to # So we prompt them. - submitter = select_account(key=safe.local_signers) + submitter = safe.select_signer(for_="submitter") if submitter: txn.confirmations = {**txn.confirmations, **signatures_added} @@ -279,7 +275,7 @@ def execute(cli_ctx, safe, txn_ids, submitter): ) if not submitter: - submitter = select_account("Select a submitter", key=safe.local_signers) + submitter = safe.select_signer(for_="submitter") for txn in pending_transactions: # Figure out which given ID(s) we are handling. @@ -317,8 +313,11 @@ def reject(cli_ctx: SafeCliContext, safe, txn_ids, execute): """ Reject one or more pending transactions """ - submit = False if execute is False else True + submit = False if execute in (False, None) else True submitter = execute if isinstance(execute, AccountAPI) else None + if submitter is None and submit: + submitter = safe.select_signer(for_="submitter") + pending_transactions = safe.client.get_transactions( confirmed=False, starting_nonce=safe.next_nonce ) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 6d22ca8..ec881b6 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -6,6 +6,7 @@ from ape.api import AccountAPI, AccountContainerAPI, ReceiptAPI, TransactionAPI from ape.api.address import BaseAddress from ape.api.networks import LOCAL_NETWORK_NAME, ForkedNetworkAPI +from ape.cli import select_account from ape.contracts import ContractInstance from ape.exceptions import ProviderNotConnectedError from ape.logging import logger @@ -706,6 +707,10 @@ def add_signatures( return signatures + def select_signer(self, for_: str = None) -> AccountAPI: + for_ = for_ or "submitter" + return select_account(prompt_message=f"Select a {for_}", key=self.local_signers) + def _get_safe_tx_id(safe_tx: SafeTx, confirmations: List[SafeTxConfirmation]) -> SafeTxID: if tx_hash_result := next((c.transaction_hash for c in confirmations), None): From d2ae0c6f98479297cf5a14e6afd828a9fe477a0f Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 11:03:25 -0600 Subject: [PATCH 124/134] feat: allow nonce in cli --- ape_safe/_cli/pending.py | 11 ++++++----- ape_safe/accounts.py | 3 +-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 4992255..6dbe05d 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -265,8 +265,9 @@ def approve(cli_ctx: SafeCliContext, safe, txn_ids, execute): @safe_option @txn_ids_argument # NOTE: Doesn't use --execute because we don't need BOOL values. -@click.option("--submitter", callback=_load_submitter) -def execute(cli_ctx, safe, txn_ids, submitter): +@click.option("--submitter", help="Account to execute", callback=_load_submitter) +@click.option("--nonce", help="Submitter nonce") +def execute(cli_ctx, safe, txn_ids, submitter, nonce): """ Execute a transaction """ @@ -285,14 +286,14 @@ def execute(cli_ctx, safe, txn_ids, submitter): # Not a specified txn. continue - _execute(safe, txn, submitter) + _execute(safe, txn, submitter, nonce=nonce) # If any txn_ids remain, they were not handled. if txn_ids: cli_ctx.abort_txns_not_found(txn_ids) -def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): +def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI, **tx_kwargs): safe_tx = safe.create_safe_tx(**txn.model_dump(mode="json", by_alias=True)) # TODO: Can remove type ignore after Ape 0.7.1 @@ -300,7 +301,7 @@ def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI): c.owner: MessageSignature.from_rsv(c.signature) for c in txn.confirmations # type: ignore } - exc_tx = safe.create_execute_transaction(safe_tx, signatures) + exc_tx = safe.create_execute_transaction(safe_tx, signatures, **tx_kwargs) submitter.call(exc_tx) diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index ec881b6..9b3295e 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -707,8 +707,7 @@ def add_signatures( return signatures - def select_signer(self, for_: str = None) -> AccountAPI: - for_ = for_ or "submitter" + def select_signer(self, for_: str = "submitter") -> AccountAPI: return select_account(prompt_message=f"Select a {for_}", key=self.local_signers) From d9709cc385dfbeb2bafb6c6d35d7cb04974d111f Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 12:19:31 -0600 Subject: [PATCH 125/134] fix: more deprecation fix --- ape_safe/_cli/safe_mgmt.py | 17 ++++++----------- tests/test_cli.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 tests/test_cli.py diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py index 62d9ac5..d66a518 100644 --- a/ape_safe/_cli/safe_mgmt.py +++ b/ape_safe/_cli/safe_mgmt.py @@ -1,5 +1,5 @@ import click -from ape.cli import NetworkBoundCommand, network_option, non_existing_alias_argument +from ape.cli import ConnectedProviderCommand, network_option, non_existing_alias_argument from ape.exceptions import ChainError, ProviderNotConnectedError from ape.types import AddressType @@ -50,17 +50,15 @@ def _list(cli_ctx: SafeCliContext, network): network_ctx.__exit__(None) -@click.command(cls=NetworkBoundCommand) +@click.command(cls=ConnectedProviderCommand) @safe_cli_ctx -@network_option() @click.argument("address", type=AddressType) @non_existing_alias_argument() -def add(cli_ctx: SafeCliContext, network, address, alias): +def add(cli_ctx: SafeCliContext, ecosystem, network, address, alias): """ Add a Safe to locally tracked Safes """ - _ = network # Needed for NetworkBoundCommand address = cli_ctx.conversion_manager.convert(address, AddressType) safe_contract = cli_ctx.chain_manager.contracts.instance_at(address) version_display = safe_contract.VERSION() @@ -69,7 +67,7 @@ def add(cli_ctx: SafeCliContext, network, address, alias): cli_ctx.logger.info( f"""Safe Found - network: {network} + network: {ecosystem.name}:{network.name} address: {safe_contract.address} version: {version_display} required confirmations: {required_confirmations} @@ -99,18 +97,15 @@ def remove(cli_ctx: SafeCliContext, safe): cli_ctx.logger.success(f"Safe '{address}' ({alias}) removed.") -@click.command(cls=NetworkBoundCommand) +@click.command(cls=ConnectedProviderCommand) @safe_cli_ctx -@network_option() @click.argument("address", type=AddressType) @click.option("--confirmed", is_flag=True, default=None) -def all_txns(cli_ctx: SafeCliContext, network, address, confirmed): +def all_txns(cli_ctx: SafeCliContext, address, confirmed): """ View and filter all transactions for a given Safe using Safe API """ - _ = network # Needed for NetworkBoundCommand - # NOTE: Create a client to support non-local safes. client = cli_ctx.safes.create_client(address) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..de7cd3b --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,20 @@ +import pytest +from click.testing import CliRunner + +from ape_safe._cli import cli as CLI + + +@pytest.fixture() +def runner(): + return CliRunner() + + +@pytest.fixture() +def cli(): + return CLI + + +def test_list_pending_transactions(runner, cli): + command = ["pending", "list"] + result = runner.invoke(cli, command) + assert result.exit_code == 0 From 27aa7effd1f8fe2dc34fafd354e5711734517e21 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 13:43:15 -0600 Subject: [PATCH 126/134] test: init cli test --- ape_safe/_cli/click_ext.py | 4 +++- ape_safe/_cli/pending.py | 12 ++++++------ ape_safe/_cli/safe_mgmt.py | 8 ++++---- ape_safe/accounts.py | 1 + tests/conftest.py | 18 ++++++++++++++++++ tests/test_account.py | 7 +++++++ tests/test_cli.py | 33 +++++++++++++++++++++++++++------ 7 files changed, 66 insertions(+), 17 deletions(-) diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index 16b9787..e2f84b5 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -13,6 +13,7 @@ class SafeCliContext(ApeCliContextObject): def safes(self) -> SafeContainer: # NOTE: Would only happen in local development of this plugin. assert "safe" in self.account_manager.containers, "Are all API methods implemented?" + safe_container = self.account_manager.containers["safe"] return cast(SafeContainer, safe_container) @@ -20,7 +21,8 @@ def abort_txns_not_found(self, txn_ids: Sequence[Union[int, str]]) -> NoReturn: self.abort(f"Pending transaction(s) '{', '.join([f'{x}' for x in txn_ids])}' not found.") -safe_cli_ctx = ape_cli_context(obj_type=SafeCliContext) +def safe_cli_ctx(): + return ape_cli_context(obj_type=SafeCliContext) def _safe_callback(ctx, param, value): diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index 6dbe05d..bb01fee 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -26,7 +26,7 @@ def pending(): @pending.command("list", cls=ConnectedProviderCommand) -@safe_cli_ctx +@safe_cli_ctx() @safe_option @click.option("--verbose", is_flag=True) def _list(cli_ctx, safe, verbose) -> None: @@ -125,7 +125,7 @@ def _handle_execute_cli_arg(ctx, param, val) -> Optional[Union[AccountAPI, bool] @pending.command(cls=ConnectedProviderCommand) -@safe_cli_ctx +@safe_cli_ctx() @safe_option @click.option("--data", type=HexBytes, help="Transaction data", default=HexBytes("")) @click.option("--gas-price", type=int, help="Transaction gas price") @@ -208,7 +208,7 @@ def _load_submitter(ctx, param, val): @pending.command(cls=ConnectedProviderCommand) -@safe_cli_ctx +@safe_cli_ctx() @safe_option @txn_ids_argument @click.option("--execute", callback=_handle_execute_cli_arg) @@ -261,7 +261,7 @@ def approve(cli_ctx: SafeCliContext, safe, txn_ids, execute): @pending.command(cls=ConnectedProviderCommand) -@safe_cli_ctx +@safe_cli_ctx() @safe_option @txn_ids_argument # NOTE: Doesn't use --execute because we don't need BOOL values. @@ -306,7 +306,7 @@ def _execute(safe: SafeAccount, txn: UnexecutedTxData, submitter: AccountAPI, ** @pending.command(cls=ConnectedProviderCommand) -@safe_cli_ctx +@safe_cli_ctx() @safe_option @txn_ids_argument @click.option("--execute", callback=_handle_execute_cli_arg) @@ -354,7 +354,7 @@ def reject(cli_ctx: SafeCliContext, safe, txn_ids, execute): @pending.command(cls=ConnectedProviderCommand) -@safe_cli_ctx +@safe_cli_ctx() @safe_option @click.argument("txn_id") def show_confs(cli_ctx, safe, txn_id): diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py index d66a518..1a65951 100644 --- a/ape_safe/_cli/safe_mgmt.py +++ b/ape_safe/_cli/safe_mgmt.py @@ -8,7 +8,7 @@ @click.command(name="list") -@safe_cli_ctx +@safe_cli_ctx() @network_option(default=None) def _list(cli_ctx: SafeCliContext, network): """ @@ -51,7 +51,7 @@ def _list(cli_ctx: SafeCliContext, network): @click.command(cls=ConnectedProviderCommand) -@safe_cli_ctx +@safe_cli_ctx() @click.argument("address", type=AddressType) @non_existing_alias_argument() def add(cli_ctx: SafeCliContext, ecosystem, network, address, alias): @@ -82,7 +82,7 @@ def add(cli_ctx: SafeCliContext, ecosystem, network, address, alias): @click.command() -@safe_cli_ctx +@safe_cli_ctx() @safe_option def remove(cli_ctx: SafeCliContext, safe): """ @@ -98,7 +98,7 @@ def remove(cli_ctx: SafeCliContext, safe): @click.command(cls=ConnectedProviderCommand) -@safe_cli_ctx +@safe_cli_ctx() @click.argument("address", type=AddressType) @click.option("--confirmed", is_flag=True, default=None) def all_txns(cli_ctx: SafeCliContext, address, confirmed): diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 9b3295e..7c0aa0b 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -104,6 +104,7 @@ def save_account(self, alias: str, address: str): if path.is_file(): raise ApeSafeError(f"Safe with alias '{alias}' already exists.") + path.parent.mkdir(exist_ok=True, parents=True) path.write_text(json.dumps(account_data)) def load_account(self, alias: str) -> "SafeAccount": diff --git a/tests/conftest.py b/tests/conftest.py index e1a6eee..c4de66f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ import tempfile from pathlib import Path +import ape import pytest from ape.contracts import ContractContainer from ape.utils import ZERO_ADDRESS @@ -12,6 +13,18 @@ contracts_directory = Path(__file__).parent / "contracts" TESTS_DIR = Path(__file__).parent.absolute() +DATA_FOLDER = Path(tempfile.mkdtemp()).resolve() +ape.config.DATA_FOLDER = DATA_FOLDER + + +@pytest.fixture(scope="session") +def config(): + return ape.config + + +@pytest.fixture +def data_folder(config): + return config.DATA_FOLDER / "safe" @pytest.fixture(scope="session") @@ -102,6 +115,11 @@ def safe(safe_data_file): return SafeAccount(account_file_path=safe_data_file) +@pytest.fixture +def safes(): + return ape.accounts.containers["safe"] + + @pytest.fixture def token(deployer: SafeAccount): text = (contracts_directory / "Token.json").read_text() diff --git a/tests/test_account.py b/tests/test_account.py index 21c27c3..5378354 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest from ape.api import AccountAPI from ape.exceptions import SignatureError @@ -5,6 +7,11 @@ from eth_utils import add_0x_prefix +def test_data_folder(safes, config): + assert Path.home() not in safes.data_folder.parents + assert safes.data_folder == config.DATA_FOLDER / "safe" + + def test_init(safe, OWNERS, THRESHOLD, safe_contract): assert safe.contract == safe_contract assert safe.confirmations_required == THRESHOLD diff --git a/tests/test_cli.py b/tests/test_cli.py index de7cd3b..c702908 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,20 +1,41 @@ +import shutil + import pytest from click.testing import CliRunner from ape_safe._cli import cli as CLI -@pytest.fixture() +@pytest.fixture def runner(): return CliRunner() -@pytest.fixture() +@pytest.fixture def cli(): return CLI -def test_list_pending_transactions(runner, cli): - command = ["pending", "list"] - result = runner.invoke(cli, command) - assert result.exit_code == 0 +@pytest.fixture +def no_safes(data_folder): + shutil.rmtree(data_folder, ignore_errors=True) + + +@pytest.fixture +def one_safe(data_folder, safes, safe): + shutil.rmtree(data_folder) + safes.save_account(safe.alias, safe.address) + return safes.load_account(safe.alias) + + +def test_list_no_safes(runner, cli, no_safes): + result = runner.invoke(cli, ["list"], catch_exceptions=False) + assert result.exit_code == 0, result.output + assert "No Safes found" in result.output + + +def test_list_one_safe(runner, cli, one_safe): + result = runner.invoke(cli, ["list"], catch_exceptions=False) + assert result.exit_code == 0, result.output + assert "Found 1 Safe" in result.output + assert "0x5FbDB2315678afecb367f032d93F642f64180aa3" in result.output From 252208e03e3ad009bfad8f39679cc99f75b3e47e Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 14:25:49 -0600 Subject: [PATCH 127/134] fix: issue with 38 39 cli annotated type --- ape_safe/_cli/pending.py | 4 ++-- ape_safe/_cli/safe_mgmt.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ape_safe/_cli/pending.py b/ape_safe/_cli/pending.py index bb01fee..f6cef96 100644 --- a/ape_safe/_cli/pending.py +++ b/ape_safe/_cli/pending.py @@ -7,7 +7,7 @@ from ape.exceptions import SignatureError from ape.types import AddressType, MessageSignature from click.exceptions import BadOptionUsage -from eth_typing import Hash32 +from eth_typing import ChecksumAddress, Hash32 from eth_utils import humanize_hash from hexbytes import HexBytes @@ -130,7 +130,7 @@ def _handle_execute_cli_arg(ctx, param, val) -> Optional[Union[AccountAPI, bool] @click.option("--data", type=HexBytes, help="Transaction data", default=HexBytes("")) @click.option("--gas-price", type=int, help="Transaction gas price") @click.option("--value", type=int, help="Transaction value", default=0) -@click.option("--to", "receiver", type=AddressType, help="Transaction receiver") +@click.option("--to", "receiver", type=ChecksumAddress, help="Transaction receiver") @click.option("--nonce", type=int, help="Transaction nonce") @click.option("--execute", callback=_handle_execute_cli_arg) def propose(cli_ctx, ecosystem, safe, data, gas_price, value, receiver, nonce, execute): diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py index 1a65951..216551f 100644 --- a/ape_safe/_cli/safe_mgmt.py +++ b/ape_safe/_cli/safe_mgmt.py @@ -2,6 +2,7 @@ from ape.cli import ConnectedProviderCommand, network_option, non_existing_alias_argument from ape.exceptions import ChainError, ProviderNotConnectedError from ape.types import AddressType +from eth_typing import ChecksumAddress from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option from ape_safe.client import ExecutedTxData @@ -52,7 +53,7 @@ def _list(cli_ctx: SafeCliContext, network): @click.command(cls=ConnectedProviderCommand) @safe_cli_ctx() -@click.argument("address", type=AddressType) +@click.argument("address", type=ChecksumAddress) @non_existing_alias_argument() def add(cli_ctx: SafeCliContext, ecosystem, network, address, alias): """ @@ -99,7 +100,7 @@ def remove(cli_ctx: SafeCliContext, safe): @click.command(cls=ConnectedProviderCommand) @safe_cli_ctx() -@click.argument("address", type=AddressType) +@click.argument("address", type=ChecksumAddress) @click.option("--confirmed", is_flag=True, default=None) def all_txns(cli_ctx: SafeCliContext, address, confirmed): """ From 368a4001a7a007ae7cc88b5b2ab002ae0929be2d Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 14:40:57 -0600 Subject: [PATCH 128/134] feat: default safe config --- ape_safe/__init__.py | 13 +++++++++++++ ape_safe/_cli/click_ext.py | 10 ++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/ape_safe/__init__.py b/ape_safe/__init__.py index 2422253..4407700 100644 --- a/ape_safe/__init__.py +++ b/ape_safe/__init__.py @@ -1,9 +1,22 @@ +from typing import Optional + from ape import plugins +from ape.api import PluginConfig from .accounts import SafeAccount, SafeContainer from .multisend import MultiSend +class SafeConfig(PluginConfig): + default_safe: Optional[str] = None + """Alias of the default safe.""" + + +@plugins.register(plugins.Config) +def config_class(): + return SafeConfig + + @plugins.register(plugins.AccountPlugin) def account_types(): return SafeContainer, SafeAccount diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index e2f84b5..271bb68 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -1,7 +1,7 @@ from typing import NoReturn, Sequence, Union, cast import click -from ape import accounts +from ape import accounts, config from ape.cli import ApeCliContextObject, ape_cli_context from click import BadOptionUsage, MissingParameter @@ -29,8 +29,14 @@ def _safe_callback(ctx, param, value): # NOTE: For some reason, the Cli CTX object is not the SafeCliCtx yet at this point. safes = accounts.containers["safe"] if value is None: + # First, check config for a default. If one is there, + # we must use that. + safe_config = config.get_config("safe") + if alias := safe_config.default_safe: + return accounts.load(alias) + # If there is only 1 safe, just use that. - if len(safes) == 1: + elif len(safes) == 1: return next(safes.accounts) options = ", ".join(safes.aliases) From b99c065163b89f0176366eec6fce7f69541259b7 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 14:57:20 -0600 Subject: [PATCH 129/134] fix: remove alias arg instead of opt --- ape_safe/_cli/click_ext.py | 1 + ape_safe/_cli/safe_mgmt.py | 4 +-- tests/{ => functional}/test_account.py | 0 tests/{ => functional}/test_multisend.py | 0 tests/{ => functional}/test_utils.py | 0 tests/integration/__init__.py | 0 tests/integration/conftest.py | 28 ++++++++++++++++ tests/integration/test_pending_cli.py | 3 ++ tests/integration/test_safe_mgmt_cli.py | 25 +++++++++++++++ tests/test_cli.py | 41 ------------------------ 10 files changed, 59 insertions(+), 43 deletions(-) rename tests/{ => functional}/test_account.py (100%) rename tests/{ => functional}/test_multisend.py (100%) rename tests/{ => functional}/test_utils.py (100%) create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/conftest.py create mode 100644 tests/integration/test_pending_cli.py create mode 100644 tests/integration/test_safe_mgmt_cli.py delete mode 100644 tests/test_cli.py diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index 271bb68..672bcd3 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -50,6 +50,7 @@ def _safe_callback(ctx, param, value): safe_option = click.option("--safe", callback=_safe_callback) +safe_argument = click.argument("safe", callback=_safe_callback) def _txn_ids_callback(ctx, param, value): diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py index 216551f..71442b2 100644 --- a/ape_safe/_cli/safe_mgmt.py +++ b/ape_safe/_cli/safe_mgmt.py @@ -4,7 +4,7 @@ from ape.types import AddressType from eth_typing import ChecksumAddress -from ape_safe._cli.click_ext import SafeCliContext, safe_cli_ctx, safe_option +from ape_safe._cli.click_ext import SafeCliContext, safe_argument, safe_cli_ctx from ape_safe.client import ExecutedTxData @@ -84,7 +84,7 @@ def add(cli_ctx: SafeCliContext, ecosystem, network, address, alias): @click.command() @safe_cli_ctx() -@safe_option +@safe_argument def remove(cli_ctx: SafeCliContext, safe): """ Stop tracking a locally-tracked Safe diff --git a/tests/test_account.py b/tests/functional/test_account.py similarity index 100% rename from tests/test_account.py rename to tests/functional/test_account.py diff --git a/tests/test_multisend.py b/tests/functional/test_multisend.py similarity index 100% rename from tests/test_multisend.py rename to tests/functional/test_multisend.py diff --git a/tests/test_utils.py b/tests/functional/test_utils.py similarity index 100% rename from tests/test_utils.py rename to tests/functional/test_utils.py diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..798ca67 --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,28 @@ +import shutil + +import pytest +from click.testing import CliRunner + +from ape_safe._cli import cli as CLI + + +@pytest.fixture +def runner(): + return CliRunner() + + +@pytest.fixture +def cli(): + return CLI + + +@pytest.fixture +def no_safes(data_folder): + shutil.rmtree(data_folder, ignore_errors=True) + + +@pytest.fixture +def one_safe(data_folder, safes, safe): + shutil.rmtree(data_folder) + safes.save_account(safe.alias, safe.address) + return safes.load_account(safe.alias) diff --git a/tests/integration/test_pending_cli.py b/tests/integration/test_pending_cli.py new file mode 100644 index 0000000..40792fe --- /dev/null +++ b/tests/integration/test_pending_cli.py @@ -0,0 +1,3 @@ +def test_help(runner, cli): + result = runner.invoke(cli, ["--help"], catch_exceptions=False) + assert result.exit_code == 0, result.output diff --git a/tests/integration/test_safe_mgmt_cli.py b/tests/integration/test_safe_mgmt_cli.py new file mode 100644 index 0000000..8532b55 --- /dev/null +++ b/tests/integration/test_safe_mgmt_cli.py @@ -0,0 +1,25 @@ +def test_list_no_safes(runner, cli, no_safes): + result = runner.invoke(cli, ["list"], catch_exceptions=False) + assert result.exit_code == 0, result.output + assert "No Safes found" in result.output + + +def test_list_one_safe(runner, cli, one_safe): + result = runner.invoke(cli, ["list"], catch_exceptions=False) + assert result.exit_code == 0, result.output + assert "Found 1 Safe" in result.output + assert "0x5FbDB2315678afecb367f032d93F642f64180aa3" in result.output + + +def test_add_safe(runner, cli, no_safes, safe): + result = runner.invoke( + cli, ["add", safe.address, safe.alias], catch_exceptions=False, input="y\n" + ) + assert result.exit_code == 0, result.output + assert "SUCCESS" in result.output, result.output + + +def test_remove_safe(runner, cli, one_safe, safe): + result = runner.invoke(cli, ["remove", safe.alias], catch_exceptions=False, input="y\n") + assert result.exit_code == 0, result.output + assert "SUCCESS" in result.output, result.output diff --git a/tests/test_cli.py b/tests/test_cli.py deleted file mode 100644 index c702908..0000000 --- a/tests/test_cli.py +++ /dev/null @@ -1,41 +0,0 @@ -import shutil - -import pytest -from click.testing import CliRunner - -from ape_safe._cli import cli as CLI - - -@pytest.fixture -def runner(): - return CliRunner() - - -@pytest.fixture -def cli(): - return CLI - - -@pytest.fixture -def no_safes(data_folder): - shutil.rmtree(data_folder, ignore_errors=True) - - -@pytest.fixture -def one_safe(data_folder, safes, safe): - shutil.rmtree(data_folder) - safes.save_account(safe.alias, safe.address) - return safes.load_account(safe.alias) - - -def test_list_no_safes(runner, cli, no_safes): - result = runner.invoke(cli, ["list"], catch_exceptions=False) - assert result.exit_code == 0, result.output - assert "No Safes found" in result.output - - -def test_list_one_safe(runner, cli, one_safe): - result = runner.invoke(cli, ["list"], catch_exceptions=False) - assert result.exit_code == 0, result.output - assert "Found 1 Safe" in result.output - assert "0x5FbDB2315678afecb367f032d93F642f64180aa3" in result.output From 7a324c68606be4b3bdbf612bd0415624b3bac492 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 15:03:56 -0600 Subject: [PATCH 130/134] feat: use skip conf opt --- ape_safe/_cli/safe_mgmt.py | 12 +++++++++--- tests/integration/test_safe_mgmt_cli.py | 6 ++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py index 71442b2..f7c60ab 100644 --- a/ape_safe/_cli/safe_mgmt.py +++ b/ape_safe/_cli/safe_mgmt.py @@ -1,5 +1,10 @@ import click -from ape.cli import ConnectedProviderCommand, network_option, non_existing_alias_argument +from ape.cli import ( + ConnectedProviderCommand, + network_option, + non_existing_alias_argument, + skip_confirmation_option, +) from ape.exceptions import ChainError, ProviderNotConnectedError from ape.types import AddressType from eth_typing import ChecksumAddress @@ -85,7 +90,8 @@ def add(cli_ctx: SafeCliContext, ecosystem, network, address, alias): @click.command() @safe_cli_ctx() @safe_argument -def remove(cli_ctx: SafeCliContext, safe): +@skip_confirmation_option() +def remove(cli_ctx: SafeCliContext, safe, skip_confirmation): """ Stop tracking a locally-tracked Safe """ @@ -93,7 +99,7 @@ def remove(cli_ctx: SafeCliContext, safe): alias = safe.alias address = safe.address - if click.confirm(f"Remove safe {address} ({alias})"): + if skip_confirmation or click.confirm(f"Remove safe {address} ({alias})"): cli_ctx.safes.delete_account(alias) cli_ctx.logger.success(f"Safe '{address}' ({alias}) removed.") diff --git a/tests/integration/test_safe_mgmt_cli.py b/tests/integration/test_safe_mgmt_cli.py index 8532b55..d2dec05 100644 --- a/tests/integration/test_safe_mgmt_cli.py +++ b/tests/integration/test_safe_mgmt_cli.py @@ -23,3 +23,9 @@ def test_remove_safe(runner, cli, one_safe, safe): result = runner.invoke(cli, ["remove", safe.alias], catch_exceptions=False, input="y\n") assert result.exit_code == 0, result.output assert "SUCCESS" in result.output, result.output + + +def test_remove_safe_skip_confirmation(runner, cli, one_safe, safe): + result = runner.invoke(cli, ["remove", safe.alias, "--yes"], catch_exceptions=False) + assert result.exit_code == 0, result.output + assert "SUCCESS" in result.output, result.output From b3c8f29db391eaf2e49ecc5fb0e4c37dc01bcd74 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 15:11:57 -0600 Subject: [PATCH 131/134] fix: issue with pending when had no safes --- ape_safe/_cli/click_ext.py | 6 +++++- tests/integration/test_pending_cli.py | 14 +++++++++++++- tests/integration/test_safe_mgmt_cli.py | 5 +++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index 672bcd3..72f1c46 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -6,6 +6,7 @@ from click import BadOptionUsage, MissingParameter from ape_safe.accounts import SafeContainer +from ape.exceptions import Abort class SafeCliContext(ApeCliContextObject): @@ -39,8 +40,11 @@ def _safe_callback(ctx, param, value): elif len(safes) == 1: return next(safes.accounts) + elif len(safes) == 0: + raise Abort("First, add a safe account using command:\n\t`ape safe add`") + options = ", ".join(safes.aliases) - raise MissingParameter(message=f"Must specify safe to use (one of '{options}').") + raise MissingParameter(message=f"Must specify one of '{options}').") elif value in safes.aliases: return accounts.load(value) diff --git a/tests/integration/test_pending_cli.py b/tests/integration/test_pending_cli.py index 40792fe..3546e57 100644 --- a/tests/integration/test_pending_cli.py +++ b/tests/integration/test_pending_cli.py @@ -1,3 +1,15 @@ def test_help(runner, cli): - result = runner.invoke(cli, ["--help"], catch_exceptions=False) + result = runner.invoke(cli, ["pending", "--help"], catch_exceptions=False) + assert result.exit_code == 0, result.output + + +def test_list_no_safes(runner, cli, no_safes): + result = runner.invoke(cli, ["pending", "list"]) + assert result.exit_code != 0, result.output + assert "First, add a safe account using command" in result.output + assert "ape safe add" in result.output + + +def test_list(runner, cli, one_safe): + result = runner.invoke(cli, ["pending", "list"], catch_exceptions=False) assert result.exit_code == 0, result.output diff --git a/tests/integration/test_safe_mgmt_cli.py b/tests/integration/test_safe_mgmt_cli.py index d2dec05..1c55665 100644 --- a/tests/integration/test_safe_mgmt_cli.py +++ b/tests/integration/test_safe_mgmt_cli.py @@ -1,3 +1,8 @@ +def test_help(runner, cli): + result = runner.invoke(cli, ["--help"], catch_exceptions=False) + assert result.exit_code == 0, result.output + + def test_list_no_safes(runner, cli, no_safes): result = runner.invoke(cli, ["list"], catch_exceptions=False) assert result.exit_code == 0, result.output From c1acab2e25b9e1412af9572ac7ff0f962aa4d3b1 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 15:14:07 -0600 Subject: [PATCH 132/134] test: no tx test --- tests/integration/test_pending_cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_pending_cli.py b/tests/integration/test_pending_cli.py index 3546e57..1599abd 100644 --- a/tests/integration/test_pending_cli.py +++ b/tests/integration/test_pending_cli.py @@ -10,6 +10,7 @@ def test_list_no_safes(runner, cli, no_safes): assert "ape safe add" in result.output -def test_list(runner, cli, one_safe): +def test_list_no_txns(runner, cli, one_safe, safe): result = runner.invoke(cli, ["pending", "list"], catch_exceptions=False) assert result.exit_code == 0, result.output + assert "There are no pending transactions" in result.output From f590a5593387ddefbab8ff3b37d61a65045bd100 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 15:49:28 -0600 Subject: [PATCH 133/134] fix: issues with network in list cmd --- ape_safe/_cli/click_ext.py | 2 +- ape_safe/_cli/safe_mgmt.py | 42 ++++++++++++++++++++----- ape_safe/accounts.py | 19 ++++++----- tests/integration/test_safe_mgmt_cli.py | 20 ++++++++++++ 4 files changed, 66 insertions(+), 17 deletions(-) diff --git a/ape_safe/_cli/click_ext.py b/ape_safe/_cli/click_ext.py index 72f1c46..ade047d 100644 --- a/ape_safe/_cli/click_ext.py +++ b/ape_safe/_cli/click_ext.py @@ -3,10 +3,10 @@ import click from ape import accounts, config from ape.cli import ApeCliContextObject, ape_cli_context +from ape.exceptions import Abort from click import BadOptionUsage, MissingParameter from ape_safe.accounts import SafeContainer -from ape.exceptions import Abort class SafeCliContext(ApeCliContextObject): diff --git a/ape_safe/_cli/safe_mgmt.py b/ape_safe/_cli/safe_mgmt.py index f7c60ab..6e8e7d1 100644 --- a/ape_safe/_cli/safe_mgmt.py +++ b/ape_safe/_cli/safe_mgmt.py @@ -1,4 +1,5 @@ import click +import rich from ape.cli import ( ConnectedProviderCommand, network_option, @@ -16,14 +17,17 @@ @click.command(name="list") @safe_cli_ctx() @network_option(default=None) -def _list(cli_ctx: SafeCliContext, network): +@click.option("--verbose", help="Show verbose info about each safe", is_flag=True) +def _list(cli_ctx: SafeCliContext, network, provider, verbose): """ Show locally-tracked Safes """ + if verbose and network is None: + cli_ctx.abort("Must use '--network' with '--verbose'.") network_ctx = None if network is not None: - network_ctx = cli_ctx.network_manager.parse_network_choice(network) + network_ctx = network.use_provider(provider.name) network_ctx.__enter__() try: @@ -36,20 +40,42 @@ def _list(cli_ctx: SafeCliContext, network): header = f"Found {number_of_safes} Safe" header += "s:" if number_of_safes > 1 else ":" click.echo(header) + total = len(cli_ctx.safes) - for account in cli_ctx.safes: + for idx, safe in enumerate(cli_ctx.safes): extras = [] - if account.alias: - extras.append(f"alias: '{account.alias}'") + if safe.alias: + extras.append(f"alias: '{safe.alias}'") + output: str = "" try: - extras.append(f"version: '{account.version}'") + extras.append(f"version: '{safe.version}'") except (ChainError, ProviderNotConnectedError): # Not connected to the network where safe is deployed extras.append("version: (not connected)") - extras_display = f" ({', '.join(extras)})" if extras else "" - click.echo(f" {account.address}{extras_display}") + else: + # NOTE: Only handle verbose if we are connected. + + if verbose: + local_signers = safe.local_signers or [] + if local_signers: + local_signers_str = ", ".join([x.alias for x in local_signers if x.alias]) + if local_signers_str: + extras.append(f"\n local signers: '{local_signers_str}'") + + extras.append(f"next nonce: '{safe.next_nonce}'") + extras_joined = ", ".join(extras) + extras_display = f" {extras_joined}" if extras else "" + output = f" {safe.address}{extras_display}" + if idx < total - 1: + output = f"{output}\n" + + if not output: + extras_display = f" ({', '.join(extras)})" if extras else "" + output = f" {safe.address}{extras_display}" + + rich.print(output) finally: if network_ctx: diff --git a/ape_safe/accounts.py b/ape_safe/accounts.py index 7c0aa0b..8d26e5a 100644 --- a/ape_safe/accounts.py +++ b/ape_safe/accounts.py @@ -5,10 +5,10 @@ from ape.api import AccountAPI, AccountContainerAPI, ReceiptAPI, TransactionAPI from ape.api.address import BaseAddress -from ape.api.networks import LOCAL_NETWORK_NAME, ForkedNetworkAPI +from ape.api.networks import ForkedNetworkAPI from ape.cli import select_account from ape.contracts import ContractInstance -from ape.exceptions import ProviderNotConnectedError +from ape.exceptions import ContractNotFoundError, ProviderNotConnectedError from ape.logging import logger from ape.managers.accounts import AccountManager, TestAccountManager from ape.types import AddressType, HexBytes, MessageSignature @@ -234,7 +234,7 @@ def client(self) -> BaseSafeClient: chain_id = self.provider.chain_id override_url = os.environ.get("SAFE_TRANSACTION_SERVICE_URL") - if self.provider.network.name == LOCAL_NETWORK_NAME: + if self.provider.network.is_local: return MockSafeClient(contract=self.contract) elif chain_id in self.account_file["deployed_chain_ids"]: @@ -359,15 +359,18 @@ def local_signers(self) -> List[AccountAPI]: # TODO: Skip per user config # TODO: Order per user config container: Union[AccountManager, TestAccountManager] - if ( - self.network_manager.active_provider - and self.provider.network.name == LOCAL_NETWORK_NAME - or self.provider.network.name.endswith("-fork") - ): + if self.network_manager.active_provider and self.provider.network.is_dev: container = self.account_manager.test_accounts else: container = self.account_manager + # Ensure the contract is available before continuing. + # Else, return an empty list + try: + _ = self.contract + except ContractNotFoundError: + return [] + return list(container[address] for address in self.signers if address in container) @handle_safe_logic_error() diff --git a/tests/integration/test_safe_mgmt_cli.py b/tests/integration/test_safe_mgmt_cli.py index 1c55665..aa197b1 100644 --- a/tests/integration/test_safe_mgmt_cli.py +++ b/tests/integration/test_safe_mgmt_cli.py @@ -16,6 +16,26 @@ def test_list_one_safe(runner, cli, one_safe): assert "0x5FbDB2315678afecb367f032d93F642f64180aa3" in result.output +def test_list_network_not_connected(runner, cli, one_safe): + result = runner.invoke( + cli, ["list", "--network", "ethereum:local:test"], catch_exceptions=False + ) + assert result.exit_code == 0, result.output + assert "Found 1 Safe" in result.output + assert "0x5FbDB2315678afecb367f032d93F642f64180aa3" in result.output + assert "not connected" in result.output + + +def test_list_network_connected(runner, cli, one_safe): + result = runner.invoke( + cli, ["list", "--network", "ethereum:local:foundry"], catch_exceptions=False + ) + assert result.exit_code == 0, result.output + assert "Found 1 Safe" in result.output + assert "0x5FbDB2315678afecb367f032d93F642f64180aa3" in result.output + assert "not connected" not in result.output + + def test_add_safe(runner, cli, no_safes, safe): result = runner.invoke( cli, ["add", safe.address, safe.alias], catch_exceptions=False, input="y\n" From 178b4cb4bd6586b9aa0e033564d7f950756bf095 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 21 Dec 2023 16:37:15 -0600 Subject: [PATCH 134/134] docs: much improve guide --- README.md | 110 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 64e4070..5a8e9e1 100644 --- a/README.md +++ b/README.md @@ -38,14 +38,94 @@ $ python3 setup.py install To use the plugin, first use the CLI extension to add a safe you created: -```bash +```sh ape safe add --network ethereum:mainnet "my-safe.eth" my-safe ``` -If you only add 1 safe, you will not have to specify in future commands. -Otherwise, for most commands, specify the safe using the `--safe` option by alias. +If you made a mistake or just need to remove the safe, use the `remove` command: + +```sh +ape safe remove my-safe --yes +``` + +**NOTE** `--yes` is a way to skip the prompt. + +If you only add one safe, you will not have to specify which safe to use other commands. +Otherwise, for most `pending` commands, you specify the safe to use (by alias) via the `--safe` option. + +Additionally, you can configure a safe to use as the default in your `ape-config.yaml` file: + +```yaml +safe: + default_safe: my-safe +``` + +**NOTE**: Also, to avoid always needing to specify `--network`, you can set a default ecosystem, network, and provider in your config file. +The rest of the guide with not specify `--network` on each command but assume the correct one is set in the config file. +Here is an example: + +```yaml +default_ecosystem: optimism + +ethereum: + default_network: sepolia + sepolia: + default_provider: infura +``` + +Once you have a safe, you can view pending transactions: + +```sh +ape safe pending list +``` + +It should show transactions like this: + +```sh +Transaction 8 rejection (1/2) safe_tx_hash=0x09ab9a229fc60da66ec0fa8fa886ab7c95902fdf5df5a5009ba06010fbb9a9a7 +Transaction 8 transfer (1/2) safe_tx_hash=0xed43d80255bcd5ffacb755e8f51bee825913373705d6baea006419d2a33a0a5b +``` + +**NOTE**: Use the `--verbose` flag to see more information about each transaction. + +```sh +ape safe pending list --verbose +``` + +There are several operations you can do on a pending transaction. +One of them is "approve" which adds your local signers' signatures to the transaction. + +```sh +ape safe pending approve 0x09ab9a229fc60da66ec0fa8fa886ab7c95902fdf5df5a5009ba06010fbb9a9a7 +``` + +**NOTE**: Here we are using the transaction hash `0x09ab9a229fc60da66ec0fa8fa886ab7c95902fdf5df5a5009ba06010fbb9a9a7` to specify the transaction because there are more than one. +However, you can also use the nonce if there is only a single transaction. + +If you want to both execute and approve at the same time, you can use the `--execute` option on approve and specify a sender: + +```sh +ape safe pending approve 2 --execute my_account +``` + +Else, you can use the `execute` command directly: + +```sh +ape safe pending execute 2 +``` + +**NOTE**: `execute` requires a full signed transaction ready to be submitted on-chain. + +The last main operation is `reject`. +Rejecting a transaction replaces that transaction with a zero-value transfer from the safe to itself. + +```sh +ape safe pending reject 2 +``` + +### Multisend -Once you've added a safe, you manage pending transactions: +The following example shows how to use multisend: ```python from ape_safe import multisend @@ -69,28 +149,6 @@ txn.add(vault.deposit, amount) txn(sender=safe) ``` -Propose a simple transaction by using `submit=False` as a transaction kwargs so it can await signatures from other signers: - -```python -from ape import accounts - -safe = accounts.load("my-safe") -other = accounts.load("other") -safe.transfer(other, "1 wei", submit=False) -``` - -**NOTE**: It may error saying the transaction was not fully signed but that is ok. - -You can use the CLI extension to view and sign for pending transactions: - -```bash -ape safe pending list --network ethereum:goerli:alchemy -ape safe pending show-confs 2 --network ethereum:goerli:alchemy -ape safe pending approve 3 --network ethereum:goerli:alchemy -ape safe pending reject 4 --network ethereum:goerli:alchemy -ape safe pending execute 4 --network ethereum:goerli:alchemy --submitter metamask0 -``` - ## Development Please see the [contributing guide](CONTRIBUTING.md) to learn more how to contribute to this project.