diff --git a/.gitignore b/.gitignore index 1a702ee..602479f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -/sent-backend.db* -/config.py +/*.db +/src/config.py /__pycache__ /oxend/*.sock */__pycache__ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..726b286 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "session-token-contracts"] + path = session-token-contracts + url = https://github.com/session-foundation/session-token-contracts.git diff --git a/Makefile b/Makefile deleted file mode 100644 index e0ef739..0000000 --- a/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -uwsgi: - uwsgi --http 127.0.0.1:5000 --master -p 4 -w sent --callable app - -database: - sqlite3 sent-backend.db < schema.sqlite diff --git a/README.md b/README.md index 1b00a47..0426ed1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SENT Staking Backend +# Session Staking Backend ## Running the backend @@ -7,10 +7,14 @@ The `liboxenc-dev` and `liboxenmq-dev` packages require the development headers by setting up the [Oxen Deb Repository](https://deb.oxen.io). Follow those instructions then they can be installed with `apt`. -To run the backend on **Ubuntu >= 24.04**: +The backend has been tested on Ubuntu 24.04. It requires Python >=3.11.x and +`pnpm` from NodeJS >=20.12.x . To get started with this repository ensure you +have the necessary dependencies and the submodules have been synchronised. + ```shell apt install build-essential python3-pip python3-dev pybind11-dev liboxenc-dev liboxenmq-dev -python3 -m pip install eth_utils web3 PyNaCl Flask uWSGI +python3 -m pip install --requirement requirements.txt +git submodule update --init --recursive ``` **Python bindings for oxen-mq & oxen-encoding** @@ -20,21 +24,28 @@ Instructions available at: - [oxen-pyoxenc](https://github.com/oxen-io/oxen-pyoxenc) - [oxen-pyoxenmq](https://github.com/oxen-io/oxen-pyoxenmq) +### Structure + +The backend has multiple parts: + +- **Events**: `app_events.py` retrieves events emitted by contracts on Arbitrum and stores them to the database +- **Fetcher**: `app_fetcher.py` retrieves data from the Session and Arbitrum network +- **Price**: `app_price.py` polls Coingecko for pricing information TODO: Merge this into staking +- **Registrations**: `app_registrations.py` handles HTTP requests for Session node registrations TODO: Merge this into staking +- **Snapshot**: TODO: Remove this class, snapshot should mean copying the DB file or using sqlite's native backup +- **Staking**: Serves endpoints for managing the state of staking into the Session network via the staking portal website + ### Instance -Before running an instance, `oxend` must be running and its address/smart contracts configured in -`config.py`. +Before running the Fetcher or API, `oxend` must be running and its address/smart contracts configured in `config.py`. -It's possible to run the service in flask directly but the timers to poll the smart contracts -requires WSGI. Both methods are detailed below: +### Running the backend stack - FLASK_APP=sent flask run --reload --debugger - uwsgi --http 127.0.0.1:5000 --master -p 4 -w sent --callable app +Run the backend using UWSGI (example runs it on port 4455 with 4 request handlers): -You may optionally append `--fs-reload sent.py` to the `uwsgi` invocation to -automatically restart the server when `sent.py` is modified. + uwsgi --http 127.0.0.1:4455 --master -p 4 -w src.app_staking --callable app --mule=src/app_events.py --mule=src/app_fetcher.py -After the server is running, visit `127.0.0.1:5000/info` to verify that the server is up and +After the server is running, visit `127.0.0.1:4455/info` to verify that the server is up and responding correctly with a payload like the following: ```json @@ -63,7 +74,7 @@ There are a few ways to make this work. ### Symlinks -You can add symlinks here to existing oxend sockets. If oxend and the backend code are running as +You can add symlinks here to existing oxend sockets. If oxend and the backend code are running as the same user then you can simply create a symlink: ln -s /var/lib/oxend/oxend.sock mainnet.sock @@ -75,7 +86,7 @@ you can make it work, but will need an extra step to configure socket permission and then do one of: -- Set the active group of the running sent staking backend to the `_loki` user. Whether this is +- Set the active group of the running sent staking backend to the `_loki` user. Whether this is easy or not depends on how the backend service is running. - Add the `_loki` group to the supplemental groups of the user that will be running this backend. @@ -89,14 +100,14 @@ and then do one of: Do *NOT*: -- Run any production service as root (including under sudo). Don't be tempted just because "it +- Run any production service as root (including under sudo). Don't be tempted just because "it works" under sudo: by running things under root/sudo you compromise the security of your entire - system as a solution to properly setting up permissions. Please don't do this, ever. + system as a solution to properly setting up permissions. Please don't do this, ever. ### Make oxend create the socket The `lmq-public=ipc:///path/to/sent-staking-backend/oxend/mainnet.sock` can be added to oxen.conf -(or run with `--lmq-public=...`) to have it listen on that socket. Note that when oxend and +(or run with `--lmq-public=...`) to have it listen on that socket. Note that when oxend and sent-staking-backend are running as separate users, this has the same permission issues as the Symlinks approach (see above for solutions). @@ -106,3 +117,4 @@ You can configure oxend with `lmq-public=tcp://127.0.0.1:6789` (choose whatever place of `6789`) in the oxen.conf config file [alternatively: run oxend with `--lmq-public=tcp://127.0.0.1:6789`] and then add/uncomment the `mainnet_rpc=...` (or `testnet_rpc=` or `devnet_rpc=`) line in config.py. + diff --git a/abi_manager.py b/abi_manager.py deleted file mode 100644 index aa679a4..0000000 --- a/abi_manager.py +++ /dev/null @@ -1,36 +0,0 @@ -import json -import os - -class ABIManager: - def __init__(self, abi_dir='abis'): - """ - Initializes the ABIManager with the directory containing ABI JSON files. - - :param abi_dir: The directory where ABI files are stored. Default is 'abis'. - """ - self.abi_dir = abi_dir - - def load_abi(self, artifact_name): - """ - Loads the ABI from a specified artifact JSON file. - - :param artifact_name: The name of the artifact file (without .json extension). - :return: The ABI extracted from the specified artifact JSON file. - :raises FileNotFoundError: If the specified file does not exist. - :raises KeyError: If the 'abi' key is not found in the JSON data. - """ - file_path = os.path.join(self.abi_dir, f"{artifact_name}.json") - if not os.path.exists(file_path): - raise FileNotFoundError(f"No such file: {file_path}") - - with open(file_path, 'r') as file: - data = json.load(file) - if 'abi' not in data: - raise KeyError("Missing 'abi' key in the JSON file.") - return data['abi'] - -# Example usage: -# manager = ABIManager() -# abi = manager.load_abi('MyContract') -# This `abi` can now be used with Web3 library to interact with a smart contract. - diff --git a/abis/ServiceNodeRewards.json b/abis/ServiceNodeRewards.json deleted file mode 100644 index 91e3e5f..0000000 --- a/abis/ServiceNodeRewards.json +++ /dev/null @@ -1,2241 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "ServiceNodeRewards", - "sourceName": "contracts/ServiceNodeRewards.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "target", - "type": "address" - } - ], - "name": "AddressEmptyCode", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "AddressInsufficientBalance", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "BLSPubkeyAlreadyExists", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "BLSPubkeyDoesNotMatch", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "internalType": "address", - "name": "contributor", - "type": "address" - } - ], - "name": "CallerNotContributor", - "type": "error" - }, - { - "inputs": [], - "name": "ClaimThresholdExceeded", - "type": "error" - }, - { - "inputs": [], - "name": "ContractAlreadyStarted", - "type": "error" - }, - { - "inputs": [], - "name": "ContractNotStarted", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "required", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "provided", - "type": "uint256" - } - ], - "name": "ContributionTotalMismatch", - "type": "error" - }, - { - "inputs": [], - "name": "DeleteSentinelNodeNotAllowed", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "Ed25519PubkeyAlreadyExists", - "type": "error" - }, - { - "inputs": [], - "name": "EnforcedPause", - "type": "error" - }, - { - "inputs": [], - "name": "ExpectedPause", - "type": "error" - }, - { - "inputs": [], - "name": "FailedInnerCall", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "address", - "name": "contributor", - "type": "address" - } - ], - "name": "FirstContributorMismatch", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "numSigners", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "requiredSigners", - "type": "uint256" - } - ], - "name": "InsufficientBLSSignatures", - "type": "error" - }, - { - "inputs": [], - "name": "InsufficientContributors", - "type": "error" - }, - { - "inputs": [], - "name": "InsufficientNodes", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidBLSProofOfPossession", - "type": "error" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point", - "name": "aggPubkey", - "type": "tuple" - } - ], - "name": "InvalidBLSSignature", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidInitialization", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "LeaveRequestNotInitiatedYet", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "internalType": "uint256", - "name": "endTimestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "currTimestamp", - "type": "uint256" - } - ], - "name": "LeaveRequestTooEarly", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "internalType": "uint256", - "name": "addedTimestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "currenttime", - "type": "uint256" - } - ], - "name": "LiquidationTooEarly", - "type": "error" - }, - { - "inputs": [], - "name": "LiquidatorRewardsTooLow", - "type": "error" - }, - { - "inputs": [], - "name": "MaxClaimExceeded", - "type": "error" - }, - { - "inputs": [], - "name": "MaxContributorsExceeded", - "type": "error" - }, - { - "inputs": [], - "name": "MaxPubkeyAggregationsExceeded", - "type": "error" - }, - { - "inputs": [], - "name": "NotInitializing", - "type": "error" - }, - { - "inputs": [], - "name": "NullAddress", - "type": "error" - }, - { - "inputs": [], - "name": "NullBLSPubkey", - "type": "error" - }, - { - "inputs": [], - "name": "NullEd25519Pubkey", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "OwnableInvalidOwner", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "OwnableUnauthorizedAccount", - "type": "error" - }, - { - "inputs": [], - "name": "PositiveNumberRequirement", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "expectedRecipient", - "type": "address" - }, - { - "internalType": "address", - "name": "providedRecipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "serviceNodeID", - "type": "uint256" - } - ], - "name": "RecipientAddressDoesNotMatch", - "type": "error" - }, - { - "inputs": [], - "name": "RecipientRewardsTooLow", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "SafeERC20FailedOperation", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "ServiceNodeDoesntExist", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "currenttime", - "type": "uint256" - } - ], - "name": "SignatureExpired", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "internalType": "address", - "name": "contributor", - "type": "address" - } - ], - "name": "SmallContributorLeaveTooEarly", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "newMax", - "type": "uint256" - } - ], - "name": "BLSNonSignerThresholdMaxUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "newValue", - "type": "uint256" - } - ], - "name": "ClaimCycleUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "newThreshold", - "type": "uint256" - } - ], - "name": "ClaimThresholdUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint64", - "name": "version", - "type": "uint64" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "newValue", - "type": "uint256" - } - ], - "name": "LiquidatorRewardRatioUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "blsPubkey", - "type": "tuple" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "ed25519Pubkey", - "type": "uint256" - } - ], - "name": "NewSeededServiceNode", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "address", - "name": "initiator", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "serviceNodePubkey", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "serviceNodeSignature1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "serviceNodeSignature2", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "fee", - "type": "uint16" - } - ], - "indexed": false, - "internalType": "struct IServiceNodeRewards.ServiceNodeParams", - "name": "serviceNode", - "type": "tuple" - }, - { - "components": [ - { - "components": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - } - ], - "internalType": "struct IServiceNodeRewards.Staker", - "name": "staker", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "stakedAmount", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct IServiceNodeRewards.Contributor[]", - "name": "contributors", - "type": "tuple[]" - } - ], - "name": "NewServiceNodeV2", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferStarted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Paused", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "newValue", - "type": "uint256" - } - ], - "name": "PoolShareOfLiquidationRatioUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "newValue", - "type": "uint256" - } - ], - "name": "RecipientRatioUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "recipientAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "previousBalance", - "type": "uint256" - } - ], - "name": "RewardsBalanceUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "recipientAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "RewardsClaimed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "returnedAmount", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "ServiceNodeExit", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "address", - "name": "contributor", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "ServiceNodeExitRequest", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "ServiceNodeLiquidated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "newExpiry", - "type": "uint256" - } - ], - "name": "SignatureExpiryUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "newRequirement", - "type": "uint256" - } - ], - "name": "StakingRequirementUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Unpaused", - "type": "event" - }, - { - "inputs": [], - "name": "LIST_SENTINEL", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_PERMITTED_PUBKEY_AGGREGATIONS_LOWER_BOUND", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_SERVICE_NODE_EXIT_WAIT_TIME", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MINIMUM_LIQUIDATION_AGE", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MIN_TIME_BEFORE_REPEATED_LEAVE_REQUEST", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "SMALL_CONTRIBUTOR_DIVISOR", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "SMALL_CONTRIBUTOR_LEAVE_DELAY", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "acceptOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point", - "name": "blsPubkey", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" - } - ], - "internalType": "struct IServiceNodeRewards.BLSSignatureParams", - "name": "blsSignature", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "serviceNodePubkey", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "serviceNodeSignature1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "serviceNodeSignature2", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "fee", - "type": "uint16" - } - ], - "internalType": "struct IServiceNodeRewards.ServiceNodeParams", - "name": "serviceNodeParams", - "type": "tuple" - }, - { - "components": [ - { - "components": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - } - ], - "internalType": "struct IServiceNodeRewards.Staker", - "name": "staker", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "stakedAmount", - "type": "uint256" - } - ], - "internalType": "struct IServiceNodeRewards.Contributor[]", - "name": "contributors", - "type": "tuple[]" - } - ], - "name": "addBLSPublicKey", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "aggregatePubkey", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "allServiceNodeIDs", - "outputs": [ - { - "internalType": "uint64[]", - "name": "ids", - "type": "uint64[]" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point[]", - "name": "pubkeys", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "blsNonSignerThreshold", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "blsNonSignerThresholdMax", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "claimCycle", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "claimRewards", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "claimRewards", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "claimThreshold", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "currentClaimCycle", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "currentClaimTotal", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "designatedToken", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "ed25519Pubkey", - "type": "uint256" - } - ], - "name": "ed25519ToServiceNodeID", - "outputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "exitBLSPublicKeyAfterWaitTime", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point", - "name": "blsPubkey", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" - } - ], - "internalType": "struct IServiceNodeRewards.BLSSignatureParams", - "name": "blsSignature", - "type": "tuple" - }, - { - "internalType": "uint64[]", - "name": "ids", - "type": "uint64[]" - } - ], - "name": "exitBLSPublicKeyWithSignature", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "exitTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "foundationPool", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "hashToG2Tag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token_", - "type": "address" - }, - { - "internalType": "address", - "name": "foundationPool_", - "type": "address" - }, - { - "internalType": "uint256", - "name": "stakingRequirement_", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxContributors_", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "liquidatorRewardRatio_", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "poolShareOfLiquidationRatio_", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "recipientRatio_", - "type": "uint256" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "initiateExitBLSPublicKey", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "isStarted", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lastHeightPubkeyWasAggregated", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point", - "name": "blsPubkey", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" - } - ], - "internalType": "struct IServiceNodeRewards.BLSSignatureParams", - "name": "blsSignature", - "type": "tuple" - }, - { - "internalType": "uint64[]", - "name": "ids", - "type": "uint64[]" - } - ], - "name": "liquidateBLSPublicKeyWithSignature", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "liquidateTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "liquidatorRewardRatio", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maxContributors", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maxPermittedPubkeyAggregations", - "outputs": [ - { - "internalType": "uint256", - "name": "result", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "nextServiceNodeID", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "numPubkeyAggregationsForHeight", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "paused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pendingOwner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "poolShareOfLiquidationRatio", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "proofOfPossessionTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "recipientRatio", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "recipients", - "outputs": [ - { - "internalType": "uint256", - "name": "rewards", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "claimed", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "rederiveTotalNodesAndAggregatePubkey", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "rewardTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point", - "name": "blsPubkey", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "ed25519Pubkey", - "type": "uint256" - }, - { - "components": [ - { - "components": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - } - ], - "internalType": "struct IServiceNodeRewards.Staker", - "name": "staker", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "stakedAmount", - "type": "uint256" - } - ], - "internalType": "struct IServiceNodeRewards.Contributor[]", - "name": "contributors", - "type": "tuple[]" - } - ], - "internalType": "struct IServiceNodeRewards.SeedServiceNode[]", - "name": "nodes", - "type": "tuple[]" - } - ], - "name": "seedPublicKeyList", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "blsPubkey", - "type": "bytes" - } - ], - "name": "serviceNodeIDs", - "outputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "serviceNodes", - "outputs": [ - { - "components": [ - { - "internalType": "uint64", - "name": "next", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "prev", - "type": "uint64" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point", - "name": "blsPubkey", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "addedTimestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "leaveRequestTimestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "latestLeaveRequestTimestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "deposit", - "type": "uint256" - }, - { - "components": [ - { - "components": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - } - ], - "internalType": "struct IServiceNodeRewards.Staker", - "name": "staker", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "stakedAmount", - "type": "uint256" - } - ], - "internalType": "struct IServiceNodeRewards.Contributor[]", - "name": "contributors", - "type": "tuple[]" - }, - { - "internalType": "uint256", - "name": "ed25519Pubkey", - "type": "uint256" - } - ], - "internalType": "struct IServiceNodeRewards.ServiceNode", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newMax", - "type": "uint256" - } - ], - "name": "setBLSNonSignerThresholdMax", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newValue", - "type": "uint256" - } - ], - "name": "setClaimCycle", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newMax", - "type": "uint256" - } - ], - "name": "setClaimThreshold", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newValue", - "type": "uint256" - } - ], - "name": "setLiquidatorRewardRatio", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newValue", - "type": "uint256" - } - ], - "name": "setPoolShareOfLiquidationRatio", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newValue", - "type": "uint256" - } - ], - "name": "setRecipientRatio", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newExpiry", - "type": "uint256" - } - ], - "name": "setSignatureExpiry", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newRequirement", - "type": "uint256" - } - ], - "name": "setStakingRequirement", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "signatureExpiry", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "stakingRequirement", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "start", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "totalNodes", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "unpause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipientAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "recipientRewards", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" - } - ], - "internalType": "struct IServiceNodeRewards.BLSSignatureParams", - "name": "blsSignature", - "type": "tuple" - }, - { - "internalType": "uint64[]", - "name": "ids", - "type": "uint64[]" - } - ], - "name": "updateRewardsBalance", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point", - "name": "blsPubkey", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" - } - ], - "internalType": "struct IServiceNodeRewards.BLSSignatureParams", - "name": "blsSignature", - "type": "tuple" - }, - { - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "internalType": "uint256", - "name": "serviceNodePubkey", - "type": "uint256" - } - ], - "name": "validateProofOfPossession", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x6080604052348015600f57600080fd5b506158fd8061001f6000396000f3fe608060405234801561001057600080fd5b50600436106103015760003560e01c8063040f985314610306578063095f14041461032f5780630962ef79146103465780630bd1b4191461035b5780630caca63b1461036457806317f33a7d146103775780631f744b541461039e578063245fc183146103a657806334fde376146103b9578063372500ab146103c15780633f4ba83a146103c957806343039d90146103d1578063538d2709146103e4578063544736e6146103ed57806356d399e81461040a57806358e93308146104135780635c975abb1461041c578063623b94221461042457806362f1fbc21461042d57806365ca819d146104375780636b348ab41461046b578063715018a61461047457806379ba50971461047c5780637a6d4065146104845780637c89d2f0146104975780638328b610146104bc5780638456cb59146104cf57806384cd9b57146104d757806386e76685146104ea57806386fa5063146104f3578063879d0f0c146104fc5780638a2209e6146105045780638a399481146105195780638da5cb5b146105225780639156ac801461052a57806395055ffd146105335780639592d424146105465780639c80ebee1461054f5780639cebc4741461042d5780639e3b372d14610557578063a44285a514610560578063ab0122ae14610569578063abf2c5031461057c578063ae6c606314610592578063b13aaebd1461059b578063b687f85c146105a4578063bc7efecc146105ac578063be9a6555146105bf578063c0ebedab146105c7578063c4f56d9a146105f0578063d5a8125414610603578063d5ca793f1461060c578063d655422f1461061f578063d84f0bf814610632578063d9054b091461063b578063d995b00c1461064e578063da1d543514610657578063dd644d7414610660578063e30c397814610669578063eb82031214610671578063edbf4ac2146106a6578063ee94e412146106b9578063f2fde38b146106c2578063f324c8eb146106d5578063f88d658b146106e8578063f907f5fc146106fb578063ffd9a86d1461070e575b600080fd5b610319610314366004614c39565b610721565b6040516103269190614cce565b60405180910390f35b610338600d5481565b604051908152602001610326565b610359610354366004614d8c565b610852565b005b610338600f5481565b610359610372366004614d8c565b61085f565b60015461039190600160a01b90046001600160401b031681565b6040516103269190614da5565b6103596108c4565b6103596103b4366004614d8c565b610994565b6103386109f2565b610359610a29565b610359610a5c565b6103596103df366004614d8c565b610a6e565b61033860075481565b6000546103fa9060ff1681565b6040519015158152602001610326565b610338600b5481565b610338600e5481565b6103fa610acc565b61033860155481565b61033862278d0081565b610391610445366004614e49565b80516020818301810180516012825292820191909301209152546001600160401b031681565b610338600a5481565b610359610ae1565b610359610af3565b610359610492366004614f9f565b610b38565b6000546104af9061010090046001600160a01b031681565b6040516103269190615009565b6103596104ca366004614d8c565b610cd6565b610359610d33565b6103596104e5366004614d8c565b610d43565b61033860045481565b610338600c5481565b610338601481565b61050c610da1565b604051610326919061501d565b61033860035481565b6104af610dc4565b61033860195481565b61035961054136600461502b565b610ddf565b61033860025481565b610391600081565b61033860095481565b61033860175481565b610359610577366004614c39565b610f8c565b610584611055565b604051610326929190615084565b61033860065481565b61033860185481565b610338600481565b6103596105ba36600461524e565b6111e0565b610359611509565b6103916105d5366004614d8c565b601b602052600090815260409020546001600160401b031681565b6103596105fe36600461502b565b611520565b61033860055481565b61035961061a3660046152fb565b611923565b61035961062d366004614d8c565b611bb6565b61033860085481565b610359610649366004614c39565b611bf3565b610338611c2081565b61033860165481565b610338601a5481565b6104af611c05565b61069861067f366004615370565b6011602052600090815260409020805460019091015482565b60405161032692919061538d565b6103596106b436600461539b565b611c10565b610338610e1081565b6103596106d0366004615370565b611f6a565b6103596106e3366004614d8c565b611fdb565b6103596106f6366004614d8c565b612039565b6001546104af906001600160a01b031681565b61035961071c3660046153fd565b612097565b610729614ab4565b6001600160401b03808316600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e0860152600881018054835181860281018601909452808452919461010087019491929184015b82821015610839576000848152602090819020604080516080810182526003860290920180546001600160a01b039081169284019283526001808301549091166060850152918352600201548284015290835290920191016107dc565b5050505081526020016009820154815250509050919050565b61085c33826120a9565b50565b6108676121ec565b6000811161088857604051630ef6cf4560e41b815260040160405180910390fd5b600f8190556040518181527f20d448c79534efb2c0adc259142770a7979eea09d1e5850fc1b02403337e11a8906020015b60405180910390a150565b6000600281905580526010602052600080516020615890833981519152546001600160401b03165b6001600160401b0381161561085c576001600160401b0381166000908152601060205260408120600254909103610932576002810154601355600381015460145561097a565b604080518082018252601354815260145460208083019190915282518084019093526002840154835260038401549083015261096d9161221e565b8051601355602001516014555b546002805460010190556001600160401b031690506108ec565b61099c6121ec565b600081116109bd57604051630ef6cf4560e41b815260040160405180910390fd5b60188190556040518181527fa83e79dc52a438d552d0631ce4fbfe7dec7656d10398a2849f352a6ab4f0163b906020016108b9565b60008060646002546002610a069190615463565b610a109190615490565b905060148111610a21576014610a23565b805b91505090565b336000908152601160205260408120600181015490549091610a4b83836154a4565b9050610a5733826120a9565b505050565b610a646121ec565b610a6c6122ca565b565b610a766121ec565b60008111610a9757604051630ef6cf4560e41b815260040160405180910390fd5b60048190556040518181527f0ac8ee09138dfaf5e3ebe4cb4fd42dd1a0695535a530171223fb5066f52e0e3b906020016108b9565b600080610ad7612316565b5460ff1692915050565b610ae96121ec565b610a6c600061233a565b3380610afd611c05565b6001600160a01b031614610b2f578060405163118cdaa760e01b8152600401610b269190615009565b60405180910390fd5b61085c8161233a565b610b40612361565b60005460ff16610b635760405163348b55eb60e21b815260040160405180910390fd5b8051600354811115610ba85780600254610b7d91906154a4565b600354600254610b8d91906154a4565b604051635eee4a3560e01b8152600401610b2692919061538d565b6001600160a01b038516610bcf5760405163e99d5ac560e01b815260040160405180910390fd5b6001600160a01b0385166000908152601160205260409020548411610c07576040516333938e6360e01b815260040160405180910390fd5b6007546040805160208101929092526001600160601b0319606088901b16908201526054810185905260009060740160405160208183030381529060405290506000610c5582600a54612387565b9050610c7084610c6a368890038801886154b7565b83612447565b50506001600160a01b0385166000818152601160205260409081902080549087905590519091907f95390641529563dbfb446535fa996c5ac3be00f90f5705b3abda59a4467b797f90610cc6908890859061538d565b60405180910390a2505050505050565b610cde6121ec565b60008111610cff57604051630ef6cf4560e41b815260040160405180910390fd5b600b8190556040518181527e6b7a1ea14ff2794527a64af37d55a2040e351f8b4c1adcdc9aea80d64e0429906020016108b9565b610d3b6121ec565b610a6c612580565b610d4b6121ec565b60008111610d6c576040516352513c5960e01b815260040160405180910390fd5b600d8190556040518181527f7c6ad2fef3b5f9e109dad55b811c13b9c880062367b00074d9286d7e7300b35f906020016108b9565b610da9614b0e565b50604080518082019091526013548152601454602082015290565b600080610dcf6125c7565b546001600160a01b031692915050565b610de7612361565b60005460ff16610e0a5760405163348b55eb60e21b815260040160405180910390fd5b8051600354811115610e245780600254610b7d91906154a4565b6000610e3d610e38368890038801886154d3565b6125eb565b90506000601282604051610e519190615513565b908152604051908190036020019020546001600160401b03169050610e7586612614565b15610e99578086426040516337030b8b60e01b8152600401610b269392919061552f565b6001600160401b0381166000908152601060205260409020600201548735141580610ee557506001600160401b0381166000908152601060209081526040909120600301549088013514155b15610f0757808760405163c43283b560e01b8152600401610b26929190615550565b600854604051600091610f26918a35906020808d0135918c9101615577565b60405160208183030381529060405290506000610f4582600a54612387565b9050610f5a86610c6a368a90038a018a6154b7565b50506001600160401b038116600090815260106020526040902060070154610f8390829061262c565b50505050505050565b610f94612361565b60005460ff16610fb75760405163348b55eb60e21b815260040160405180910390fd5b6001600160401b03811660009081526010602052604081206005015490819003610ff65781604051630cf4827360e31b8152600401610b269190614da5565b600061100562278d0083615592565b90508042101561102e57828142604051637a54a45360e11b8152600401610b269392919061552f565b6001600160401b038316600090815260106020526040902060070154610a5790849061262c565b6060806002546001600160401b0381111561107257611072614db9565b60405190808252806020026020018201604052801561109b578160200160208202803683370190505b5091506002546001600160401b038111156110b8576110b8614db9565b6040519080825280602002602001820160405280156110f157816020015b6110de614b0e565b8152602001906001900390816110d65790505b5060008080526010602052600080516020615890833981519152549192506001600160401b03909116905b6001600160401b038216156111da576001600160401b0380831660009081526010602052604090208551909184918791851690811061115d5761115d6155a5565b60200260200101906001600160401b031690816001600160401b031681525050806002016040518060400160405290816000820154815260200160018201548152505084836001600160401b0316815181106111bb576111bb6155a5565b6020908102919091010152546001600160401b0316915060010161111c565b50509091565b6111e8612361565b60005460ff1661120b5760405163348b55eb60e21b815260040160405180910390fd5b8051600c548111156112305760405163226c2d8360e21b815260040160405180910390fd5b80156112a4576000805b8281101561127557838181518110611254576112546155a5565b6020026020010151602001518261126b9190615592565b915060010161123a565b50600b54811461129e57600b54816040516367d22bd960e01b8152600401610b2692919061538d565b50611339565b60408051600180825281830190925290816020015b60408051608081018252600091810182815260608201839052815260208101919091528152602001906001900390816112b957505060408051608081018252339181018281526060820192909252908152600b5460208201528151919350908390600090611329576113296155a5565b6020026020010181905250815190505b60006012611346876125eb565b6040516113539190615513565b908152604051908190036020019020546001600160401b03169050801561138f578060405163459c639360e01b8152600401610b269190614da5565b6000836000815181106113a4576113a46155a5565b6020026020010151600001516000015190506113c687878388600001516126cf565b6000806113d78988600001516127b9565b600b5460078201556001810180546001600160a01b0319166001600160a01b038716179055909250905060005b8581101561148b5781600801878281518110611422576114226155a5565b60209081029190910181015182546001808201855560009485529383902082518051600390930290910180546001600160a01b03199081166001600160a01b03948516178255918501518187018054909316931692909217905591015160029091015501611404565b50611494612aed565b816001600160401b03167f533c16cb4158fe7c77021b90e9290bbefa457dd392bd8abc1eab59747834fd5b848b8a8a6040516114d394939291906155bb565b60405180910390a26114fe600060019054906101000a90046001600160a01b03163330600b54612b19565b505050505050505050565b6115116121ec565b6000805460ff19166001179055565b611528612361565b60005460ff1661154b5760405163348b55eb60e21b815260040160405180910390fd5b80516003548111156115655780600254610b7d91906154a4565b6000611579610e38368890038801886154d3565b9050600060128260405161158d9190615513565b908152604051908190036020019020546001600160401b031690506115b186612614565b156115d5578086426040516337030b8b60e01b8152600401610b269392919061552f565b6001600160401b03808216600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e086015260088101805483518186028101860190945280845294959491936101008601939290879084015b828210156116e9576000848152602090819020604080516080810182526003860290920180546001600160a01b0390811692840192835260018083015490911660608501529183526002015482840152908352909201910161168c565b505050908252506009919091015460209091015260608101515190915088351415806117215750806060015160200151886020013514155b1561174357818860405163c43283b560e01b8152600401610b26929190615550565b611c2081608001516117559190615592565b42101561177e57608081015160405163c666e87d60e01b8152610b26918491429060040161552f565b60095460405160009161179d918b35906020808e0135918d9101615577565b604051602081830303815290604052905060006117bc82600a54612387565b90506117d187610c6a368b90038b018b6154b7565b5050816001600160401b03167f0bfb12191b00293af29126b1c5489f8daeb4a4af82db2960b7f8353c3105cd7c82604001518360600151604051611816929190615654565b60405180910390a26000600f54600d54600e546118339190615592565b61183d9190615592565b905060008260e001519050600082600d54836118599190615463565b6118639190615490565b90506000600e54836118759190615463565b156118a457836001600e5461188a91906154a4565b6118949190615490565b61189f906001615592565b6118a7565b60005b90506118c786826118b885876154a4565b6118c291906154a4565b61262c565b600d54156118eb576000546118eb9061010090046001600160a01b03163384612b80565b600e541561191557600054600154611915916001600160a01b036101009091048116911683612b80565b505050505050505050505050565b61192b6121ec565b60005460ff161561194f57604051630a35d5c360e31b815260040160405180910390fd5b8060005b81811015611bad573684848381811061196e5761196e6155a5565b90506020028101906119809190615671565b905060006119916060830183615687565b9050116119b157604051631a10033d60e11b815260040160405180910390fd5b600c546119c16060830183615687565b905011156119e25760405163226c2d8360e21b815260040160405180910390fd5b600080611a016119f7368590038501856154d3565b84604001356127b9565b600b5460078201559092509050611a1b6060840184615687565b6000818110611a2c57611a2c6155a5565b611a429260206060909202019081019150615370565b6001820180546001600160a01b0319166001600160a01b0392909216919091179055600080611a746060860186615687565b9050905060005b81811015611b225736611a916060880188615687565b83818110611aa157611aa16155a5565b9050606002019050806040013584611ab99190615592565b93506000611aca6020830183615370565b6001600160a01b031603611af15760405163e99d5ac560e01b815260040160405180910390fd5b60088501805460018101825560009182526020909120829160030201611b1782826156ef565b505050600101611a7b565b50600b548214611b4b57600b54826040516367d22bd960e01b8152600401610b2692919061538d565b604080518635815260208088013590820152868201358183015290516001600160401b038616917f34be2fa283e1d5eb453459898f160448257aa1ef0f7dd2c3a3b55c45d1af768f919081900360600190a26001860195505050505050611953565b50610a57612aed565b611bbe6121ec565b600e8190556040518181527f4751fdab53c6c42462ab92ce837e339f8efc4c64fa76dadd99cc95baa68a4c00906020016108b9565b611bfb612361565b61085c8133612bb1565b600080610dcf612d81565b6000611c1a612da5565b805490915060ff600160401b82041615906001600160401b0316600081158015611c415750825b90506000826001600160401b03166001148015611c5d5750303b155b905081158015611c6b575080155b15611c895760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b03191660011785558315611cb257845460ff60401b1916600160401b1785555b6001861015611cd457604051630ef6cf4560e41b815260040160405180910390fd5b6001881015611cf6576040516352513c5960e01b815260040160405180910390fd5b6000805460ff19168155600281905560035561012c60045560408051808201909152601b81527a0424c535f5349475f545259414e44494e4352454d454e545f504f5602c1b6020820152611d4990612dc9565b60065560408051808201909152601e81527f424c535f5349475f545259414e44494e4352454d454e545f52455741524400006020820152611d8990612dc9565b60075560408051808201909152601c81527b109314d7d4d251d7d51496505391125390d4915351539517d156125560221b6020820152611dc890612dc9565b600881905550611def6040518060600160405280602181526020016158b060219139612dc9565b600955604080518082019091526019815278424c535f5349475f484153485f544f5f4649454c445f54414760381b6020820152611e2b90612dc9565b600a5561025860055566038d7ea4c6800060175561a8c060185560006019819055601a8190558054610100600160a81b0319166101006001600160a01b038f811691909102919091178255600180546001600160a01b031916918e16919091178155600b8c9055600c8b9055600d8a9055600e899055600f889055611eb0919061572f565b600180546001600160401b0392909216600160a01b02600160a01b600160e01b031990921691909117905560008052601060205260008051602061589083398151915280546001600160801b0319169055611f0a33612dfd565b611f12612e0e565b831561191557845460ff60401b191685556040517fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290611f5490600190614da5565b60405180910390a1505050505050505050505050565b611f726121ec565b6000611f7c612d81565b80546001600160a01b0319166001600160a01b0384169081178255909150611fa2610dc4565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b611fe36121ec565b6000811161200457604051630ef6cf4560e41b815260040160405180910390fd5b60178190556040518181527fe604909b918af52702fa14cd9b5a8870e5bd66da3163365671e583f705fd5463906020016108b9565b6120416121ec565b6000811161206257604051630ef6cf4560e41b815260040160405180910390fd5b60058190556040518181527ffbbc3d0a51e101ce4a7fda20e6e4eb0e230bf7de27ee2e3683fc8539e3766395906020016108b9565b6120a3848484846126cf565b50505050565b6001600160a01b03821660009081526011602052604081206001810154905490916120d483836154a4565b9050808411156120f75760405163363cc6b360e21b815260040160405180910390fd5b6000601854426121079190615490565b9050601a5481111561211e57601a81905560006019555b84601960008282546121309190615592565b9091555050601754601954111561215a57604051638c10944b60e01b815260040160405180910390fd5b6001600160a01b03861660009081526011602052604081206001018054879290612185908490615592565b90915550506040518581526001600160a01b038716907ffc30cddea38e2bf4d6ea7d3f9ed3b6ad7f176419f4963bd81318067a4aee73fe9060200160405180910390a26000546121e49061010090046001600160a01b03168787612b80565b505050505050565b336121f5610dc4565b6001600160a01b031614610a6c573360405163118cdaa760e01b8152600401610b269190615009565b612226614b0e565b61222e614b28565b835181526020808501518183015283516040808401919091529084015160608301526000908360808460066107d05a03fa9050806122c25760405162461bcd60e51b815260206004820152602b60248201527f43616c6c20746f20707265636f6d70696c656420636f6e747261637420666f7260448201526a081859190819985a5b195960aa1b6064820152608401610b26565b505092915050565b6122d2612e1e565b60006122dc612316565b805460ff1916815590507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516108b99190615009565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330090565b6000612344612d81565b80546001600160a01b0319168155905061235d82612e43565b5050565b612369610acc565b15610a6c5760405163d93c066560e01b815260040160405180910390fd5b61238f614b46565b600061239b8484612e9f565b905060008060008061240885600001516001600281106123bd576123bd6155a5565b602002015186516000602002015187602001516001600281106123e2576123e26155a5565b602002015188602001516000600281106123fe576123fe6155a5565b6020020151613070565b60408051608081018252808201948552606081019590955292845282518084019093528252602082810191909152820152955050505050505b92915050565b61244f614b0e565b835160005b818110156124d5576000868281518110612470576124706155a5565b602002602001015190506124ca8460106000846001600160401b03166001600160401b031681526020019081526020016000206002016040518060400160405290816000820154815260200160018201548152505061221e565b935050600101612454565b5060408051808201909152601354815260145460208201526124ff906124fa84613100565b61221e565b604080516080810182526020808801518284019081528851606080850191909152908352835180850185529089015181529288015183820152810191909152909250600061254c84613100565b905061256161255961318a565b8383886131ab565b610f835780604051634fb09a2160e11b8152600401610b26919061501d565b612588612361565b6000612592612316565b805460ff1916600117815590507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586123093390565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b6060816040516020016125fe919061501d565b6040516020818303038152906040529050919050565b6000600554826126249190615592565b421192915050565b6001600160401b0382166000908152601060209081526040918290206001810154835180850190945260028201548452600390910154918301919091526001600160a01b03169061267c846132ac565b612684612aed565b836001600160401b03167f24c4411329b949fad2eba6f79a8ba090a08eac1af4b56c3a2f5a282ed45e233e8385846040516126c19392919061574e565b60405180910390a250505050565b600060065485600001518660200151858560405160200161272095949392919094855260208501939093526040840191909152606090811b6001600160601b03191690830152607482015260940190565b6040516020818303038152906040529050600061273f82600a54612387565b60408051608081018252602080890151828401908152895160608085019190915290835283518085018552908a01518152928901518382015281019190915290915061279c61278c61318a565b826127968a613100565b856131ab565b610f835760405163cf006ab760e01b815260040160405180910390fd5b8151600090819015806127ce57506020840151155b156127ec5760405163139b722760e31b815260040160405180910390fd5b8260000361280d57604051634eb8f11f60e01b815260040160405180910390fd5b6000612818856125eb565b905060006001600160401b03166012826040516128359190615513565b908152604051908190036020019020546001600160401b031614612895576012816040516128639190615513565b9081526040519081900360200181205463459c639360e01b8252610b26916001600160401b0390911690600401614da5565b6000848152601b60205260409020546001600160401b0316156128e5576000848152601b602052604090819020549051630cb4c72160e21b8152610b26916001600160401b031690600401614da5565b60005460ff161561294a57436015541015612904574360155560006016555b6016805490600061291483615772565b919050555060006129236109f2565b90508060165411156129485760405163689f6e7560e11b815260040160405180910390fd5b505b60018054600160a01b90046001600160401b031690601461296a8361578b565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550925060026000815461299f90615772565b909155506001600160401b038381166000818152601060209081526040808320600080516020615890833981519152805482546001600160801b031916600160401b918290049098168082029890981783558154600160401b600160801b03191690870217815586855282852080546001600160401b031990811688179091558c5160028401558c8501516003840155426004840155600983018c90558b8652601b9094529382902080549093169094179091555191945091908590601290612a69908690615513565b90815260405190819003602001902080546001600160401b03929092166001600160401b0319909216919091179055600254600103612ab45786516013556020870151601455612ae2565b6040805180820190915260135481526014546020820152612ad5908861221e565b8051601355602001516014555b5050505b9250929050565b60006003600254612afe9190615490565b90506004548111612b0f5780612b13565b6004545b60035550565b6040516001600160a01b0384811660248301528381166044830152606482018390526120a39186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506135b5565b6040516001600160a01b03838116602483015260448201839052610a5791859182169063a9059cbb90606401612b4e565b60005460ff16612bd45760405163348b55eb60e21b815260040160405180910390fd5b6001600160401b03821660009081526010602052604081206008810154829190825b81811015612c67576000836008018281548110612c1557612c156155a5565b6000918252602090912060039091020180549091506001600160a01b03808916911603612c5e576007840154600282015460019750612c55906004615463565b10945050612c67565b50600101612bf6565b5083612c8a578585604051635bf2837760e11b8152600401610b269291906157b7565b8160050154600003612ce157828015612cb4575062278d008260040154612cb19190615592565b42105b15612cd6578585604051633826feb560e01b8152600401610b269291906157b7565b426005830155612d20565b6000610e108360060154612cf59190615592565b905080421015612d1e57868142604051637a54a45360e11b8152600401610b269392919061552f565b505b426006830155604080516001600160a01b0387168152600284015460208201526003840154918101919091526001600160401b038716907f8600c0145511b317ae0189933fa71eb57a32d74891804c7993ef74ec63a5e80490606001610cc6565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6000814630604051602001612de0939291906157d9565b604051602081830303815290604052805190602001209050919050565b612e0561360f565b61085c81613634565b612e1661360f565b610a6c613666565b612e26610acc565b610a6c57604051638dfc202b60e01b815260040160405180910390fd5b6000612e4d6125c7565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b612ea7614b46565b600080600080600087516001612ebd9190615592565b6001600160401b03811115612ed457612ed4614db9565b6040519080825280601f01601f191660200182016040528015612efe576020820181803683370190505b50885190915060005b81811015612f5d57898181518110612f2157612f216155a5565b602001015160f81c60f81b838281518110612f3e57612f3e6155a5565b60200101906001600160f81b031916908160001a905350600101612f07565b5060005b8060f81b8360018551612f7491906154a4565b81518110612f8457612f846155a5565b60200101906001600160f81b031916908160001a9053506000612fa7848b613683565b91995097509050600080612fbb8a8a61377b565b91509150600080612fcc84846137ee565b9150915081600014158015612fe057508015155b1561301d578415612ffb57612ff5828261395d565b90925090505b9098509650878761300e8c8c84846139a2565b1561301d575050505050613035565b5050505050808061302d9061580d565b915050612f61565b505060408051608081018252808201958652606081019690965293855250825180840190935282526020808301919091528201529392505050565b600080600080613082888888886139b9565b61308e5761308e615823565b6040805160c081018252898152602081018990529081018790526060810186905260016080820152600060a08201526130c681613a5c565b8051602082015160408301516060840151608085015160a08601519596506130ed95613c0e565b929c919b50995090975095505050505050565b613108614b0e565b815115801561311957506020820151155b15613137575050604080518082019091526000808252602082015290565b604051806040016040528083600001518152602001600080516020615870833981519152846020015161316a9190615839565b613182906000805160206158708339815191526154a4565b905292915050565b613192614b0e565b5060408051808201909152600181526002602082015290565b60408051600280825260608201909252600091829190816020015b6131ce614b0e565b8152602001906001900390816131c65750506040805160028082526060820190925291925060009190602082015b613204614b46565b8152602001906001900390816131fc579050509050868260008151811061322d5761322d6155a5565b6020026020010181905250848260018151811061324c5761324c6155a5565b6020026020010181905250858160008151811061326b5761326b6155a5565b6020026020010181905250838160018151811061328a5761328a6155a5565b602002602001018190525061329f8282613c58565b925050505b949350505050565b6000600254116132cf576040516363cd35d560e11b815260040160405180910390fd5b6001600160401b0381166132f657604051631d58f8cb60e11b815260040160405180910390fd5b6001600160401b03808216600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e086015260088101805483518186028101860190945280845294959491936101008601939290879084015b8282101561340a576000848152602090819020604080516080810182526003860290920180546001600160a01b039081169284019283526001808301549091166060850152918352600201548284015290835290920191016133ad565b5050509082525060099190910154602091820152818101805183516001600160401b0390811660009081526010855260408082208054600160401b600160801b031916600160401b9585169590950294909417909355855193518216815282902080546001600160401b0319169390911692909217909155805180820190915260135481526014549181019190915260608201519192506134ae916124fa90613100565b80516013556020015160145560608101516000906134cb906125eb565b6101208301516000908152601b60205260409081902080546001600160401b031916905551909150601290613501908390615513565b908152604080516020928190038301902080546001600160401b03191690556001600160401b03851660009081526010909252812080546001600160801b03191681556001810180546001600160a01b03191690556002810182905560038101829055600481018290556005810182905560068101829055600781018290559061358e6008830182614b6b565b600982016000905550506001600260008282546135ab91906154a4565b9091555050505050565b60006135ca6001600160a01b03841683613f5e565b905080516000141580156135ef5750808060200190518101906135ed919061584d565b155b15610a575782604051635274afe760e01b8152600401610b269190615009565b613617613f73565b610a6c57604051631afcd79f60e31b815260040160405180910390fd5b61363c61360f565b6001600160a01b038116610b2f576000604051631e4fbdf760e01b8152600401610b269190615009565b61366e61360f565b6000613678612316565b805460ff1916905550565b60008060008060008060006136b989896040516020016136a591815260200190565b604051602081830303815290604052613f8d565b935093509350935060405160308152602080820152602060408201528460608201528360808201526001609082015260008051602061587083398151915260b082015260208160d0836005600019fa61371157600080fd5b805197505060405160308152602080820152836050820152602060408201528260708201526001609082015260008051602061587083398151915260b082015260208160d0836005600019fa61376657600080fd5b51969996985060019081161496505050505050565b60008060008060008061379088888a8a6140d6565b90925090506137a182828a8a6140d6565b90925090506137df82826000805160206158d18339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2614147565b90999098509650505050505050565b600080600080600085600003613830576138078761417b565b909350905080156138215782600094509450505050612ae6565b60008394509450505050612ae6565b6000805160206158708339815191528788099250600080516020615870833981519152868709915060008051602061587083398151915282840892506138758361417b565b90935090508061388e5760008094509450505050612ae6565b60008051602061587083398151915283880891506138ab82614212565b915060006138b88361417b565b9250905081613909576138da888560008051602061587083398151915261426c565b92506138e583614212565b92506138f08361417b565b9250905081613909576000809550955050505050612ae6565b80600080516020615870833981519152828308935061393684600080516020615870833981519152614290565b93506000600080516020615870833981519152858a09919a91995090975050505050505050565b60008080613979856000805160206158708339815191526154a4565b90506000613995856000805160206158708339815191526154a4565b9196919550909350505050565b60006139b0858585856139b9565b95945050505050565b60008060008060006139cd878789896140d6565b90945092506139de898981816140d6565b90925090506139ef82828b8b6140d6565b9092509050613a00848484846142e1565b9094509250613a3e84846000805160206158d18339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d26142e1565b909450925083158015613a4f575082155b9998505050505050505050565b613a64614b8c565b613a6c614b8c565b613a74614b8c565b613a7c614b8c565b84516020860151604087015160608801516080890151613ab4946744e992b44a6909f1949093909290918b60055b6020020151614323565b80516020820151604083015160608401516080850151949750613ae09460029493929190896005613aaa565b9150613b4c8360005b602002015184600160200201518560026020020151866003602002015187600460200201518860056020020151886000602002015189600160200201518a600260200201518b600360200201518c600460200201518d60055b60200201516143a6565b9150613b578261462f565b9150613b628361462f565b9050613b6d8161462f565b9050613b7a836000613ae9565b9250613be08360005b6020020151846001602002015185600260200201518660036020020151876004602002015188600560200201518760006020020151886001602002015189600260200201518a600360200201518b600460200201518c6005613b42565b9250613beb8561462f565b9050613bf68161462f565b9050613c018161462f565b90506139b0836000613b83565b600080600080600080613c218888614747565b9092509050613c328c8c84846140d6565b9096509450613c438a8a84846140d6565b969d959c509a50949850929650505050505050565b60008151835114613c6857600080fd5b82516000613c77826006615463565b90506000816001600160401b03811115613c9357613c93614db9565b604051908082528060200260200182016040528015613cbc578160200160208202803683370190505b50905060005b83811015613eed57868181518110613cdc57613cdc6155a5565b60200260200101516000015182826006613cf69190615463565b613d01906000615592565b81518110613d1157613d116155a5565b602002602001018181525050868181518110613d2f57613d2f6155a5565b60200260200101516020015182826006613d499190615463565b613d54906001615592565b81518110613d6457613d646155a5565b602002602001018181525050858181518110613d8257613d826155a5565b6020908102919091010151515182613d9b836006615463565b613da6906002615592565b81518110613db657613db66155a5565b602002602001018181525050858181518110613dd457613dd46155a5565b60209081029190910181015151015182613def836006615463565b613dfa906003615592565b81518110613e0a57613e0a6155a5565b602002602001018181525050858181518110613e2857613e286155a5565b602002602001015160200151600060028110613e4657613e466155a5565b602002015182613e57836006615463565b613e62906004615592565b81518110613e7257613e726155a5565b602002602001018181525050858181518110613e9057613e906155a5565b602002602001015160200151600160028110613eae57613eae6155a5565b602002015182613ebf836006615463565b613eca906005615592565b81518110613eda57613eda6155a5565b6020908102919091010152600101613cc2565b50613ef6614baa565b60006020826020860260208601600060086107d05a03f1905080613f505760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964205369676e617475726560781b6044820152606401610b26565b505115159695505050505050565b6060613f6c838360006147d2565b9392505050565b6000613f7d612da5565b54600160401b900460ff16919050565b60008060008060ff85511115613fa257600080fd5b600060405160005b6088811015613fc157600082820152602001613faa565b506088602060005b8a51811015613fea578a820151848401526020928301929182019101613fc9565b505060898951019050608081830153600201602060005b89518110156140225789820151848401526020928301929182019101614001565b5050608b88518a5101019050875181830153508751875101608c018120915050604051818152600160208201536021602060005b89518110156140775789820151848401526020928301929182019101614056565b505050865187516021018201538651602201812095508582188152600260208201538651602201812094508482188152600360208201538651602201812093508382188152600460208201539551602201909520939692955090935050565b60008061411460008051602061587083398151915285880960008051602061587083398151915285880960008051602061587083398151915261426c565b60008051602061587083398151915280868809600080516020615870833981519152868a09089150915094509492505050565b6000806000805160206158708339815191528487086000805160206158708339815191528487089150915094509492505050565b600080600060405160208152602080820152602060408201528460608201527f0c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52608082015260008051602061587083398151915260a082015260208160c08360056107d05a03fa905193509050600080516020615870833981519152838009841491508061420c5760009250600091505b50915091565b6000600182161515614225600284615490565b915080156142665760008051602061587083398151915260026142576000805160206158708339815191526001615592565b6142619190615490565b830891505b50919050565b6000818061427c5761427c61547a565b61428684846154a4565b8508949350505050565b60008060405160208152602080820152602060408201528460608201526002840360808201528360a082015260208160c08360056107d05a03fa905192509050806142da57600080fd5b5092915050565b6000806142fd868560008051602061587083398151915261426c565b614316868560008051602061587083398151915261426c565b9150915094509492505050565b61432b614b8c565b871561439b57600188161561436c578051602082015160408301516060840151608085015160a08601516143699594939291908d8d8d8d8d8d6143a6565b90505b61437a87878787878761486f565b949b50929950909750955093509150614394600289615490565b975061432b565b979650505050505050565b6143ae614b8c565b881580156143ba575087155b156143fc578686868686868660005b60a0890192909252608088019290925260608701929092526040860192909252602085810193909352909102015261461f565b82158015614408575081155b1561441b578c8c8c8c8c8c8660006143c9565b61442785858b8b6140d6565b90955093506144388b8b85856140d6565b6060830152604082015261444e87878b8b6140d6565b909750955061445f8d8d85856140d6565b60a0830152608082018190528714801561447c575060a081015186145b156144c1576040810151851480156144975750606081015184145b156144b2576144aa8d8d8d8d8d8d61486f565b8660006143c9565b600160008181808086816143c9565b6144cd898985856140d6565b90935091506144ed858583600260200201518460035b60200201516142e1565b909d509b50614507878783600460200201518460056144e3565b909b5099506145188b8b81816140d6565b9099509750614538898983600460200201518460055b60200201516140d6565b909550935061454989898d8d6140d6565b909950975061455a898985856140d6565b60a083015260808201526145708d8d81816140d6565b9097509550614581878785856140d6565b909750955061459287878b8b6142e1565b90975095506145a3858560026149de565b90935091506145b4878785856142e1565b90975095506145c58b8b89896140d6565b602083015281526145d8858589896142e1565b909b5099506145e98d8d8d8d6140d6565b909b5099506146038989836002602002015184600361452e565b909d509b506146148b8b8f8f6142e1565b606083015260408201525b9c9b505050505050505050505050565b614637614b8c565b815161464b908360015b6020020151614a11565b60208301528152604082015161466390836003614641565b60608301526040820152608082015161467e90836005614641565b60a08301526080820152805160208201516146db91907f2fb347984f7911f74c0bec3cf559b143b78cc310c2c3330c99e39557176f553d7f16c9e55061ebae204ba4cc8bd75a079432ae2a1d0b7c9dce1665d51c640fcba26140d6565b602083015281526040810151606082015161473891907f063cf305489af5dcdc5ec698b6e2f9b9dbaae0eda9c95998dc54014671a0135a7f07c03cbcac41049a0704b5a7ec796f2b21807dc98fa25bd282d37f632623b0e36140d6565b60608301526040820152919050565b600080806147886000805160206158708339815191528087880960008051602061587083398151915287880908600080516020615870833981519152614290565b90506000805160206158708339815191528186096000805160206158708339815191528286096147c6906000805160206158708339815191526154a4565b92509250509250929050565b6060814710156147f7573060405163cd78605960e01b8152600401610b269190615009565b600080856001600160a01b031684866040516148139190615513565b60006040518083038185875af1925050503d8060008114614850576040519150601f19603f3d011682016040523d82523d6000602084013e614855565b606091505b5091509150614865868383614a38565b9695505050505050565b6000806000806000806148848c8c60036149de565b909650945061489586868e8e6140d6565b90965094506148a68a8a8a8a6140d6565b90985096506148b78c8c8c8c6140d6565b90945092506148c884848a8a6140d6565b90945092506148d9868681816140d6565b909c509a506148ea848460086149de565b90925090506148fb8c8c84846142e1565b909c509a5061490c888881816140d6565b909250905061491d848460046149de565b909450925061492e84848e8e6142e1565b909450925061493f848488886140d6565b90945092506149508a8a60086149de565b909650945061496186868c8c6140d6565b9096509450614972868684846140d6565b9096509450614983848488886142e1565b90945092506149948c8c60026149de565b90965094506149a586868a8a6140d6565b90965094506149b6888884846140d6565b90925090506149c7828260086149de565b809250819350505096509650965096509650969050565b60008060008051602061587083398151915283860960008051602061587083398151915284860991509150935093915050565b60008083614a2d846000805160206158708339815191526154a4565b915091509250929050565b606082614a4d57614a4882614a8b565b613f6c565b8151158015614a6457506001600160a01b0384163b155b15614a845783604051639996b31560e01b8152600401610b269190615009565b5080613f6c565b805115614a9b5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b60408051610140810182526000808252602082018190529181019190915260608101614ade614b0e565b81526020016000815260200160008152602001600081526020016000815260200160608152602001600081525090565b604051806040016040528060008152602001600081525090565b60405180608001604052806004906020820280368337509192915050565b6040518060400160405280614b59614bc8565b8152602001614b66614bc8565b905290565b508054600082556003029060005260206000209081019061085c9190614be6565b6040518060c001604052806006906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b60405180604001604052806002906020820280368337509192915050565b5b80821115614c195780546001600160a01b03199081168255600182018054909116905560006002820155600301614be7565b5090565b80356001600160401b0381168114614c3457600080fd5b919050565b600060208284031215614c4b57600080fd5b613f6c82614c1d565b80518252602090810151910152565b805180516001600160a01b03908116845260209182015116818401520151604082015260600190565b600081518084526020840193506020830160005b82811015614cc457614cb3868351614c63565b955060209190910190600101614ca0565b5093949350505050565b60208152614ce86020820183516001600160401b03169052565b60006020830151614d0460408401826001600160401b03169052565b5060408301516001600160a01b0381166060840152506060830151614d2c6080840182614c54565b50608083015160c083015260a083015160e083015260c083015161010083015260e0830151610120830152610100830151610160610140840152614d74610180840182614c8c565b90506101208401516101608401528091505092915050565b600060208284031215614d9e57600080fd5b5035919050565b6001600160401b0391909116815260200190565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715614df157614df1614db9565b60405290565b604051608081016001600160401b0381118282101715614df157614df1614db9565b604051601f8201601f191681016001600160401b0381118282101715614e4157614e41614db9565b604052919050565b600060208284031215614e5b57600080fd5b81356001600160401b03811115614e7157600080fd5b8201601f81018413614e8257600080fd5b80356001600160401b03811115614e9b57614e9b614db9565b614eae601f8201601f1916602001614e19565b818152856020838501011115614ec357600080fd5b81602084016020830137600091810160200191909152949350505050565b6001600160a01b038116811461085c57600080fd5b60006080828403121561426657600080fd5b60006001600160401b03821115614f2157614f21614db9565b5060051b60200190565b600082601f830112614f3c57600080fd5b8135614f4f614f4a82614f08565b614e19565b8082825260208201915060208360051b860101925085831115614f7157600080fd5b602085015b83811015614f9557614f8781614c1d565b835260209283019201614f76565b5095945050505050565b60008060008060e08587031215614fb557600080fd5b8435614fc081614ee1565b935060208501359250614fd68660408701614ef6565b915060c08501356001600160401b03811115614ff157600080fd5b614ffd87828801614f2b565b91505092959194509250565b6001600160a01b0391909116815260200190565b604081016124418284614c54565b60008060008084860361010081121561504357600080fd5b604081121561505157600080fd5b50849350604084013592506150698660608601614ef6565b915060e08501356001600160401b03811115614ff157600080fd5b6040808252835190820181905260009060208501906060840190835b818110156150c75783516001600160401b03168352602093840193909201916001016150a0565b50508381036020808601919091528551808352918101925085019060005b8181101561510e576150f8848451614c54565b60409390930192602092909201916001016150e5565b50919695505050505050565b60006040828403121561512c57600080fd5b615134614dcf565b823581526020928301359281019290925250919050565b60006080828403121561515d57600080fd5b615165614df7565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b600082601f8301126151a157600080fd5b81356151af614f4a82614f08565b808282526020820191506020606084028601019250858311156151d157600080fd5b602085015b83811015614f955780870360608112156151ef57600080fd5b6151f7614dcf565b604082121561520557600080fd5b61520d614dcf565b9150823561521a81614ee1565b8252602083013561522a81614ee1565b602083810191909152918152604083013581830152845292909201916060016151d6565b60008060008084860361016081121561526657600080fd5b615270878761511a565b945061527f876040880161514b565b9350608060bf198201121561529357600080fd5b5061529c614df7565b60c0860135815260e08601356020820152610100860135604082015261012086013561ffff811681146152ce57600080fd5b606082015291506101408501356001600160401b038111156152ef57600080fd5b614ffd87828801615190565b6000806020838503121561530e57600080fd5b82356001600160401b0381111561532457600080fd5b8301601f8101851361533557600080fd5b80356001600160401b0381111561534b57600080fd5b8560208260051b840101111561536057600080fd5b6020919091019590945092505050565b60006020828403121561538257600080fd5b8135613f6c81614ee1565b918252602082015260400190565b600080600080600080600060e0888a0312156153b657600080fd5b87356153c181614ee1565b965060208801356153d181614ee1565b96999698505050506040850135946060810135946080820135945060a0820135935060c0909101359150565b600080600080610100858703121561541457600080fd5b61541e868661511a565b935061542d866040870161514b565b925060c085013561543d81614ee1565b9396929550929360e00135925050565b634e487b7160e01b600052601160045260246000fd5b80820281158282048414176124415761244161544d565b634e487b7160e01b600052601260045260246000fd5b60008261549f5761549f61547a565b500490565b818103818111156124415761244161544d565b6000608082840312156154c957600080fd5b613f6c838361514b565b6000604082840312156154e557600080fd5b613f6c838361511a565b60005b8381101561550a5781810151838201526020016154f2565b50506000910152565b600082516155258184602087016154ef565b9190910192915050565b6001600160401b039390931683526020830191909152604082015260600190565b6001600160401b038316815260608101613f6c602083018480358252602090810135910152565b93845260208401929092526040830152606082015260800190565b808201808211156124415761244161544d565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0385168152600061010082016155db6020840187614c54565b8451606084015260208501516080840152604085015160a084015261ffff60608601511660c084015261010060e08401528084518083526101208501915060208601925060005b8181101561564657615635838551614c63565b602094909401939250600101615622565b509098975050505050505050565b6001600160a01b038316815260608101613f6c6020830184614c54565b60008235607e1983360301811261552557600080fd5b6000808335601e1984360301811261569e57600080fd5b8301803591506001600160401b038211156156b857600080fd5b6020019150606081023603821315612ae657600080fd5b80546001600160a01b0319166001600160a01b0392909216919091179055565b81356156fa81614ee1565b61570481836156cf565b50602082013561571381614ee1565b61572081600184016156cf565b50604082013560028201555050565b6001600160401b0381811683821601908111156124415761244161544d565b6001600160a01b038416815260208101839052608081016132a46040830184614c54565b6000600182016157845761578461544d565b5060010190565b60006001600160401b0382166002600160401b031981016157ae576157ae61544d565b60010192915050565b6001600160401b039290921682526001600160a01b0316602082015260400190565b600084516157eb8184602089016154ef565b919091019283525060601b6001600160601b0319166020820152603401919050565b600060ff821660ff81036157ae576157ae61544d565b634e487b7160e01b600052600160045260246000fd5b6000826158485761584861547a565b500690565b60006020828403121561585f57600080fd5b81518015158114613f6c57600080fdfe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd476e0956cda88cad152e89927e53611735b61a5c762d1428573c6931b0a5efcb01424c535f5349475f545259414e44494e4352454d454e545f4c49515549444154452b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5a164736f6c634300081a000a", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106103015760003560e01c8063040f985314610306578063095f14041461032f5780630962ef79146103465780630bd1b4191461035b5780630caca63b1461036457806317f33a7d146103775780631f744b541461039e578063245fc183146103a657806334fde376146103b9578063372500ab146103c15780633f4ba83a146103c957806343039d90146103d1578063538d2709146103e4578063544736e6146103ed57806356d399e81461040a57806358e93308146104135780635c975abb1461041c578063623b94221461042457806362f1fbc21461042d57806365ca819d146104375780636b348ab41461046b578063715018a61461047457806379ba50971461047c5780637a6d4065146104845780637c89d2f0146104975780638328b610146104bc5780638456cb59146104cf57806384cd9b57146104d757806386e76685146104ea57806386fa5063146104f3578063879d0f0c146104fc5780638a2209e6146105045780638a399481146105195780638da5cb5b146105225780639156ac801461052a57806395055ffd146105335780639592d424146105465780639c80ebee1461054f5780639cebc4741461042d5780639e3b372d14610557578063a44285a514610560578063ab0122ae14610569578063abf2c5031461057c578063ae6c606314610592578063b13aaebd1461059b578063b687f85c146105a4578063bc7efecc146105ac578063be9a6555146105bf578063c0ebedab146105c7578063c4f56d9a146105f0578063d5a8125414610603578063d5ca793f1461060c578063d655422f1461061f578063d84f0bf814610632578063d9054b091461063b578063d995b00c1461064e578063da1d543514610657578063dd644d7414610660578063e30c397814610669578063eb82031214610671578063edbf4ac2146106a6578063ee94e412146106b9578063f2fde38b146106c2578063f324c8eb146106d5578063f88d658b146106e8578063f907f5fc146106fb578063ffd9a86d1461070e575b600080fd5b610319610314366004614c39565b610721565b6040516103269190614cce565b60405180910390f35b610338600d5481565b604051908152602001610326565b610359610354366004614d8c565b610852565b005b610338600f5481565b610359610372366004614d8c565b61085f565b60015461039190600160a01b90046001600160401b031681565b6040516103269190614da5565b6103596108c4565b6103596103b4366004614d8c565b610994565b6103386109f2565b610359610a29565b610359610a5c565b6103596103df366004614d8c565b610a6e565b61033860075481565b6000546103fa9060ff1681565b6040519015158152602001610326565b610338600b5481565b610338600e5481565b6103fa610acc565b61033860155481565b61033862278d0081565b610391610445366004614e49565b80516020818301810180516012825292820191909301209152546001600160401b031681565b610338600a5481565b610359610ae1565b610359610af3565b610359610492366004614f9f565b610b38565b6000546104af9061010090046001600160a01b031681565b6040516103269190615009565b6103596104ca366004614d8c565b610cd6565b610359610d33565b6103596104e5366004614d8c565b610d43565b61033860045481565b610338600c5481565b610338601481565b61050c610da1565b604051610326919061501d565b61033860035481565b6104af610dc4565b61033860195481565b61035961054136600461502b565b610ddf565b61033860025481565b610391600081565b61033860095481565b61033860175481565b610359610577366004614c39565b610f8c565b610584611055565b604051610326929190615084565b61033860065481565b61033860185481565b610338600481565b6103596105ba36600461524e565b6111e0565b610359611509565b6103916105d5366004614d8c565b601b602052600090815260409020546001600160401b031681565b6103596105fe36600461502b565b611520565b61033860055481565b61035961061a3660046152fb565b611923565b61035961062d366004614d8c565b611bb6565b61033860085481565b610359610649366004614c39565b611bf3565b610338611c2081565b61033860165481565b610338601a5481565b6104af611c05565b61069861067f366004615370565b6011602052600090815260409020805460019091015482565b60405161032692919061538d565b6103596106b436600461539b565b611c10565b610338610e1081565b6103596106d0366004615370565b611f6a565b6103596106e3366004614d8c565b611fdb565b6103596106f6366004614d8c565b612039565b6001546104af906001600160a01b031681565b61035961071c3660046153fd565b612097565b610729614ab4565b6001600160401b03808316600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e0860152600881018054835181860281018601909452808452919461010087019491929184015b82821015610839576000848152602090819020604080516080810182526003860290920180546001600160a01b039081169284019283526001808301549091166060850152918352600201548284015290835290920191016107dc565b5050505081526020016009820154815250509050919050565b61085c33826120a9565b50565b6108676121ec565b6000811161088857604051630ef6cf4560e41b815260040160405180910390fd5b600f8190556040518181527f20d448c79534efb2c0adc259142770a7979eea09d1e5850fc1b02403337e11a8906020015b60405180910390a150565b6000600281905580526010602052600080516020615890833981519152546001600160401b03165b6001600160401b0381161561085c576001600160401b0381166000908152601060205260408120600254909103610932576002810154601355600381015460145561097a565b604080518082018252601354815260145460208083019190915282518084019093526002840154835260038401549083015261096d9161221e565b8051601355602001516014555b546002805460010190556001600160401b031690506108ec565b61099c6121ec565b600081116109bd57604051630ef6cf4560e41b815260040160405180910390fd5b60188190556040518181527fa83e79dc52a438d552d0631ce4fbfe7dec7656d10398a2849f352a6ab4f0163b906020016108b9565b60008060646002546002610a069190615463565b610a109190615490565b905060148111610a21576014610a23565b805b91505090565b336000908152601160205260408120600181015490549091610a4b83836154a4565b9050610a5733826120a9565b505050565b610a646121ec565b610a6c6122ca565b565b610a766121ec565b60008111610a9757604051630ef6cf4560e41b815260040160405180910390fd5b60048190556040518181527f0ac8ee09138dfaf5e3ebe4cb4fd42dd1a0695535a530171223fb5066f52e0e3b906020016108b9565b600080610ad7612316565b5460ff1692915050565b610ae96121ec565b610a6c600061233a565b3380610afd611c05565b6001600160a01b031614610b2f578060405163118cdaa760e01b8152600401610b269190615009565b60405180910390fd5b61085c8161233a565b610b40612361565b60005460ff16610b635760405163348b55eb60e21b815260040160405180910390fd5b8051600354811115610ba85780600254610b7d91906154a4565b600354600254610b8d91906154a4565b604051635eee4a3560e01b8152600401610b2692919061538d565b6001600160a01b038516610bcf5760405163e99d5ac560e01b815260040160405180910390fd5b6001600160a01b0385166000908152601160205260409020548411610c07576040516333938e6360e01b815260040160405180910390fd5b6007546040805160208101929092526001600160601b0319606088901b16908201526054810185905260009060740160405160208183030381529060405290506000610c5582600a54612387565b9050610c7084610c6a368890038801886154b7565b83612447565b50506001600160a01b0385166000818152601160205260409081902080549087905590519091907f95390641529563dbfb446535fa996c5ac3be00f90f5705b3abda59a4467b797f90610cc6908890859061538d565b60405180910390a2505050505050565b610cde6121ec565b60008111610cff57604051630ef6cf4560e41b815260040160405180910390fd5b600b8190556040518181527e6b7a1ea14ff2794527a64af37d55a2040e351f8b4c1adcdc9aea80d64e0429906020016108b9565b610d3b6121ec565b610a6c612580565b610d4b6121ec565b60008111610d6c576040516352513c5960e01b815260040160405180910390fd5b600d8190556040518181527f7c6ad2fef3b5f9e109dad55b811c13b9c880062367b00074d9286d7e7300b35f906020016108b9565b610da9614b0e565b50604080518082019091526013548152601454602082015290565b600080610dcf6125c7565b546001600160a01b031692915050565b610de7612361565b60005460ff16610e0a5760405163348b55eb60e21b815260040160405180910390fd5b8051600354811115610e245780600254610b7d91906154a4565b6000610e3d610e38368890038801886154d3565b6125eb565b90506000601282604051610e519190615513565b908152604051908190036020019020546001600160401b03169050610e7586612614565b15610e99578086426040516337030b8b60e01b8152600401610b269392919061552f565b6001600160401b0381166000908152601060205260409020600201548735141580610ee557506001600160401b0381166000908152601060209081526040909120600301549088013514155b15610f0757808760405163c43283b560e01b8152600401610b26929190615550565b600854604051600091610f26918a35906020808d0135918c9101615577565b60405160208183030381529060405290506000610f4582600a54612387565b9050610f5a86610c6a368a90038a018a6154b7565b50506001600160401b038116600090815260106020526040902060070154610f8390829061262c565b50505050505050565b610f94612361565b60005460ff16610fb75760405163348b55eb60e21b815260040160405180910390fd5b6001600160401b03811660009081526010602052604081206005015490819003610ff65781604051630cf4827360e31b8152600401610b269190614da5565b600061100562278d0083615592565b90508042101561102e57828142604051637a54a45360e11b8152600401610b269392919061552f565b6001600160401b038316600090815260106020526040902060070154610a5790849061262c565b6060806002546001600160401b0381111561107257611072614db9565b60405190808252806020026020018201604052801561109b578160200160208202803683370190505b5091506002546001600160401b038111156110b8576110b8614db9565b6040519080825280602002602001820160405280156110f157816020015b6110de614b0e565b8152602001906001900390816110d65790505b5060008080526010602052600080516020615890833981519152549192506001600160401b03909116905b6001600160401b038216156111da576001600160401b0380831660009081526010602052604090208551909184918791851690811061115d5761115d6155a5565b60200260200101906001600160401b031690816001600160401b031681525050806002016040518060400160405290816000820154815260200160018201548152505084836001600160401b0316815181106111bb576111bb6155a5565b6020908102919091010152546001600160401b0316915060010161111c565b50509091565b6111e8612361565b60005460ff1661120b5760405163348b55eb60e21b815260040160405180910390fd5b8051600c548111156112305760405163226c2d8360e21b815260040160405180910390fd5b80156112a4576000805b8281101561127557838181518110611254576112546155a5565b6020026020010151602001518261126b9190615592565b915060010161123a565b50600b54811461129e57600b54816040516367d22bd960e01b8152600401610b2692919061538d565b50611339565b60408051600180825281830190925290816020015b60408051608081018252600091810182815260608201839052815260208101919091528152602001906001900390816112b957505060408051608081018252339181018281526060820192909252908152600b5460208201528151919350908390600090611329576113296155a5565b6020026020010181905250815190505b60006012611346876125eb565b6040516113539190615513565b908152604051908190036020019020546001600160401b03169050801561138f578060405163459c639360e01b8152600401610b269190614da5565b6000836000815181106113a4576113a46155a5565b6020026020010151600001516000015190506113c687878388600001516126cf565b6000806113d78988600001516127b9565b600b5460078201556001810180546001600160a01b0319166001600160a01b038716179055909250905060005b8581101561148b5781600801878281518110611422576114226155a5565b60209081029190910181015182546001808201855560009485529383902082518051600390930290910180546001600160a01b03199081166001600160a01b03948516178255918501518187018054909316931692909217905591015160029091015501611404565b50611494612aed565b816001600160401b03167f533c16cb4158fe7c77021b90e9290bbefa457dd392bd8abc1eab59747834fd5b848b8a8a6040516114d394939291906155bb565b60405180910390a26114fe600060019054906101000a90046001600160a01b03163330600b54612b19565b505050505050505050565b6115116121ec565b6000805460ff19166001179055565b611528612361565b60005460ff1661154b5760405163348b55eb60e21b815260040160405180910390fd5b80516003548111156115655780600254610b7d91906154a4565b6000611579610e38368890038801886154d3565b9050600060128260405161158d9190615513565b908152604051908190036020019020546001600160401b031690506115b186612614565b156115d5578086426040516337030b8b60e01b8152600401610b269392919061552f565b6001600160401b03808216600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e086015260088101805483518186028101860190945280845294959491936101008601939290879084015b828210156116e9576000848152602090819020604080516080810182526003860290920180546001600160a01b0390811692840192835260018083015490911660608501529183526002015482840152908352909201910161168c565b505050908252506009919091015460209091015260608101515190915088351415806117215750806060015160200151886020013514155b1561174357818860405163c43283b560e01b8152600401610b26929190615550565b611c2081608001516117559190615592565b42101561177e57608081015160405163c666e87d60e01b8152610b26918491429060040161552f565b60095460405160009161179d918b35906020808e0135918d9101615577565b604051602081830303815290604052905060006117bc82600a54612387565b90506117d187610c6a368b90038b018b6154b7565b5050816001600160401b03167f0bfb12191b00293af29126b1c5489f8daeb4a4af82db2960b7f8353c3105cd7c82604001518360600151604051611816929190615654565b60405180910390a26000600f54600d54600e546118339190615592565b61183d9190615592565b905060008260e001519050600082600d54836118599190615463565b6118639190615490565b90506000600e54836118759190615463565b156118a457836001600e5461188a91906154a4565b6118949190615490565b61189f906001615592565b6118a7565b60005b90506118c786826118b885876154a4565b6118c291906154a4565b61262c565b600d54156118eb576000546118eb9061010090046001600160a01b03163384612b80565b600e541561191557600054600154611915916001600160a01b036101009091048116911683612b80565b505050505050505050505050565b61192b6121ec565b60005460ff161561194f57604051630a35d5c360e31b815260040160405180910390fd5b8060005b81811015611bad573684848381811061196e5761196e6155a5565b90506020028101906119809190615671565b905060006119916060830183615687565b9050116119b157604051631a10033d60e11b815260040160405180910390fd5b600c546119c16060830183615687565b905011156119e25760405163226c2d8360e21b815260040160405180910390fd5b600080611a016119f7368590038501856154d3565b84604001356127b9565b600b5460078201559092509050611a1b6060840184615687565b6000818110611a2c57611a2c6155a5565b611a429260206060909202019081019150615370565b6001820180546001600160a01b0319166001600160a01b0392909216919091179055600080611a746060860186615687565b9050905060005b81811015611b225736611a916060880188615687565b83818110611aa157611aa16155a5565b9050606002019050806040013584611ab99190615592565b93506000611aca6020830183615370565b6001600160a01b031603611af15760405163e99d5ac560e01b815260040160405180910390fd5b60088501805460018101825560009182526020909120829160030201611b1782826156ef565b505050600101611a7b565b50600b548214611b4b57600b54826040516367d22bd960e01b8152600401610b2692919061538d565b604080518635815260208088013590820152868201358183015290516001600160401b038616917f34be2fa283e1d5eb453459898f160448257aa1ef0f7dd2c3a3b55c45d1af768f919081900360600190a26001860195505050505050611953565b50610a57612aed565b611bbe6121ec565b600e8190556040518181527f4751fdab53c6c42462ab92ce837e339f8efc4c64fa76dadd99cc95baa68a4c00906020016108b9565b611bfb612361565b61085c8133612bb1565b600080610dcf612d81565b6000611c1a612da5565b805490915060ff600160401b82041615906001600160401b0316600081158015611c415750825b90506000826001600160401b03166001148015611c5d5750303b155b905081158015611c6b575080155b15611c895760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b03191660011785558315611cb257845460ff60401b1916600160401b1785555b6001861015611cd457604051630ef6cf4560e41b815260040160405180910390fd5b6001881015611cf6576040516352513c5960e01b815260040160405180910390fd5b6000805460ff19168155600281905560035561012c60045560408051808201909152601b81527a0424c535f5349475f545259414e44494e4352454d454e545f504f5602c1b6020820152611d4990612dc9565b60065560408051808201909152601e81527f424c535f5349475f545259414e44494e4352454d454e545f52455741524400006020820152611d8990612dc9565b60075560408051808201909152601c81527b109314d7d4d251d7d51496505391125390d4915351539517d156125560221b6020820152611dc890612dc9565b600881905550611def6040518060600160405280602181526020016158b060219139612dc9565b600955604080518082019091526019815278424c535f5349475f484153485f544f5f4649454c445f54414760381b6020820152611e2b90612dc9565b600a5561025860055566038d7ea4c6800060175561a8c060185560006019819055601a8190558054610100600160a81b0319166101006001600160a01b038f811691909102919091178255600180546001600160a01b031916918e16919091178155600b8c9055600c8b9055600d8a9055600e899055600f889055611eb0919061572f565b600180546001600160401b0392909216600160a01b02600160a01b600160e01b031990921691909117905560008052601060205260008051602061589083398151915280546001600160801b0319169055611f0a33612dfd565b611f12612e0e565b831561191557845460ff60401b191685556040517fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290611f5490600190614da5565b60405180910390a1505050505050505050505050565b611f726121ec565b6000611f7c612d81565b80546001600160a01b0319166001600160a01b0384169081178255909150611fa2610dc4565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b611fe36121ec565b6000811161200457604051630ef6cf4560e41b815260040160405180910390fd5b60178190556040518181527fe604909b918af52702fa14cd9b5a8870e5bd66da3163365671e583f705fd5463906020016108b9565b6120416121ec565b6000811161206257604051630ef6cf4560e41b815260040160405180910390fd5b60058190556040518181527ffbbc3d0a51e101ce4a7fda20e6e4eb0e230bf7de27ee2e3683fc8539e3766395906020016108b9565b6120a3848484846126cf565b50505050565b6001600160a01b03821660009081526011602052604081206001810154905490916120d483836154a4565b9050808411156120f75760405163363cc6b360e21b815260040160405180910390fd5b6000601854426121079190615490565b9050601a5481111561211e57601a81905560006019555b84601960008282546121309190615592565b9091555050601754601954111561215a57604051638c10944b60e01b815260040160405180910390fd5b6001600160a01b03861660009081526011602052604081206001018054879290612185908490615592565b90915550506040518581526001600160a01b038716907ffc30cddea38e2bf4d6ea7d3f9ed3b6ad7f176419f4963bd81318067a4aee73fe9060200160405180910390a26000546121e49061010090046001600160a01b03168787612b80565b505050505050565b336121f5610dc4565b6001600160a01b031614610a6c573360405163118cdaa760e01b8152600401610b269190615009565b612226614b0e565b61222e614b28565b835181526020808501518183015283516040808401919091529084015160608301526000908360808460066107d05a03fa9050806122c25760405162461bcd60e51b815260206004820152602b60248201527f43616c6c20746f20707265636f6d70696c656420636f6e747261637420666f7260448201526a081859190819985a5b195960aa1b6064820152608401610b26565b505092915050565b6122d2612e1e565b60006122dc612316565b805460ff1916815590507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516108b99190615009565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330090565b6000612344612d81565b80546001600160a01b0319168155905061235d82612e43565b5050565b612369610acc565b15610a6c5760405163d93c066560e01b815260040160405180910390fd5b61238f614b46565b600061239b8484612e9f565b905060008060008061240885600001516001600281106123bd576123bd6155a5565b602002015186516000602002015187602001516001600281106123e2576123e26155a5565b602002015188602001516000600281106123fe576123fe6155a5565b6020020151613070565b60408051608081018252808201948552606081019590955292845282518084019093528252602082810191909152820152955050505050505b92915050565b61244f614b0e565b835160005b818110156124d5576000868281518110612470576124706155a5565b602002602001015190506124ca8460106000846001600160401b03166001600160401b031681526020019081526020016000206002016040518060400160405290816000820154815260200160018201548152505061221e565b935050600101612454565b5060408051808201909152601354815260145460208201526124ff906124fa84613100565b61221e565b604080516080810182526020808801518284019081528851606080850191909152908352835180850185529089015181529288015183820152810191909152909250600061254c84613100565b905061256161255961318a565b8383886131ab565b610f835780604051634fb09a2160e11b8152600401610b26919061501d565b612588612361565b6000612592612316565b805460ff1916600117815590507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586123093390565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b6060816040516020016125fe919061501d565b6040516020818303038152906040529050919050565b6000600554826126249190615592565b421192915050565b6001600160401b0382166000908152601060209081526040918290206001810154835180850190945260028201548452600390910154918301919091526001600160a01b03169061267c846132ac565b612684612aed565b836001600160401b03167f24c4411329b949fad2eba6f79a8ba090a08eac1af4b56c3a2f5a282ed45e233e8385846040516126c19392919061574e565b60405180910390a250505050565b600060065485600001518660200151858560405160200161272095949392919094855260208501939093526040840191909152606090811b6001600160601b03191690830152607482015260940190565b6040516020818303038152906040529050600061273f82600a54612387565b60408051608081018252602080890151828401908152895160608085019190915290835283518085018552908a01518152928901518382015281019190915290915061279c61278c61318a565b826127968a613100565b856131ab565b610f835760405163cf006ab760e01b815260040160405180910390fd5b8151600090819015806127ce57506020840151155b156127ec5760405163139b722760e31b815260040160405180910390fd5b8260000361280d57604051634eb8f11f60e01b815260040160405180910390fd5b6000612818856125eb565b905060006001600160401b03166012826040516128359190615513565b908152604051908190036020019020546001600160401b031614612895576012816040516128639190615513565b9081526040519081900360200181205463459c639360e01b8252610b26916001600160401b0390911690600401614da5565b6000848152601b60205260409020546001600160401b0316156128e5576000848152601b602052604090819020549051630cb4c72160e21b8152610b26916001600160401b031690600401614da5565b60005460ff161561294a57436015541015612904574360155560006016555b6016805490600061291483615772565b919050555060006129236109f2565b90508060165411156129485760405163689f6e7560e11b815260040160405180910390fd5b505b60018054600160a01b90046001600160401b031690601461296a8361578b565b91906101000a8154816001600160401b0302191690836001600160401b03160217905550925060026000815461299f90615772565b909155506001600160401b038381166000818152601060209081526040808320600080516020615890833981519152805482546001600160801b031916600160401b918290049098168082029890981783558154600160401b600160801b03191690870217815586855282852080546001600160401b031990811688179091558c5160028401558c8501516003840155426004840155600983018c90558b8652601b9094529382902080549093169094179091555191945091908590601290612a69908690615513565b90815260405190819003602001902080546001600160401b03929092166001600160401b0319909216919091179055600254600103612ab45786516013556020870151601455612ae2565b6040805180820190915260135481526014546020820152612ad5908861221e565b8051601355602001516014555b5050505b9250929050565b60006003600254612afe9190615490565b90506004548111612b0f5780612b13565b6004545b60035550565b6040516001600160a01b0384811660248301528381166044830152606482018390526120a39186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506135b5565b6040516001600160a01b03838116602483015260448201839052610a5791859182169063a9059cbb90606401612b4e565b60005460ff16612bd45760405163348b55eb60e21b815260040160405180910390fd5b6001600160401b03821660009081526010602052604081206008810154829190825b81811015612c67576000836008018281548110612c1557612c156155a5565b6000918252602090912060039091020180549091506001600160a01b03808916911603612c5e576007840154600282015460019750612c55906004615463565b10945050612c67565b50600101612bf6565b5083612c8a578585604051635bf2837760e11b8152600401610b269291906157b7565b8160050154600003612ce157828015612cb4575062278d008260040154612cb19190615592565b42105b15612cd6578585604051633826feb560e01b8152600401610b269291906157b7565b426005830155612d20565b6000610e108360060154612cf59190615592565b905080421015612d1e57868142604051637a54a45360e11b8152600401610b269392919061552f565b505b426006830155604080516001600160a01b0387168152600284015460208201526003840154918101919091526001600160401b038716907f8600c0145511b317ae0189933fa71eb57a32d74891804c7993ef74ec63a5e80490606001610cc6565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6000814630604051602001612de0939291906157d9565b604051602081830303815290604052805190602001209050919050565b612e0561360f565b61085c81613634565b612e1661360f565b610a6c613666565b612e26610acc565b610a6c57604051638dfc202b60e01b815260040160405180910390fd5b6000612e4d6125c7565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b612ea7614b46565b600080600080600087516001612ebd9190615592565b6001600160401b03811115612ed457612ed4614db9565b6040519080825280601f01601f191660200182016040528015612efe576020820181803683370190505b50885190915060005b81811015612f5d57898181518110612f2157612f216155a5565b602001015160f81c60f81b838281518110612f3e57612f3e6155a5565b60200101906001600160f81b031916908160001a905350600101612f07565b5060005b8060f81b8360018551612f7491906154a4565b81518110612f8457612f846155a5565b60200101906001600160f81b031916908160001a9053506000612fa7848b613683565b91995097509050600080612fbb8a8a61377b565b91509150600080612fcc84846137ee565b9150915081600014158015612fe057508015155b1561301d578415612ffb57612ff5828261395d565b90925090505b9098509650878761300e8c8c84846139a2565b1561301d575050505050613035565b5050505050808061302d9061580d565b915050612f61565b505060408051608081018252808201958652606081019690965293855250825180840190935282526020808301919091528201529392505050565b600080600080613082888888886139b9565b61308e5761308e615823565b6040805160c081018252898152602081018990529081018790526060810186905260016080820152600060a08201526130c681613a5c565b8051602082015160408301516060840151608085015160a08601519596506130ed95613c0e565b929c919b50995090975095505050505050565b613108614b0e565b815115801561311957506020820151155b15613137575050604080518082019091526000808252602082015290565b604051806040016040528083600001518152602001600080516020615870833981519152846020015161316a9190615839565b613182906000805160206158708339815191526154a4565b905292915050565b613192614b0e565b5060408051808201909152600181526002602082015290565b60408051600280825260608201909252600091829190816020015b6131ce614b0e565b8152602001906001900390816131c65750506040805160028082526060820190925291925060009190602082015b613204614b46565b8152602001906001900390816131fc579050509050868260008151811061322d5761322d6155a5565b6020026020010181905250848260018151811061324c5761324c6155a5565b6020026020010181905250858160008151811061326b5761326b6155a5565b6020026020010181905250838160018151811061328a5761328a6155a5565b602002602001018190525061329f8282613c58565b925050505b949350505050565b6000600254116132cf576040516363cd35d560e11b815260040160405180910390fd5b6001600160401b0381166132f657604051631d58f8cb60e11b815260040160405180910390fd5b6001600160401b03808216600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e086015260088101805483518186028101860190945280845294959491936101008601939290879084015b8282101561340a576000848152602090819020604080516080810182526003860290920180546001600160a01b039081169284019283526001808301549091166060850152918352600201548284015290835290920191016133ad565b5050509082525060099190910154602091820152818101805183516001600160401b0390811660009081526010855260408082208054600160401b600160801b031916600160401b9585169590950294909417909355855193518216815282902080546001600160401b0319169390911692909217909155805180820190915260135481526014549181019190915260608201519192506134ae916124fa90613100565b80516013556020015160145560608101516000906134cb906125eb565b6101208301516000908152601b60205260409081902080546001600160401b031916905551909150601290613501908390615513565b908152604080516020928190038301902080546001600160401b03191690556001600160401b03851660009081526010909252812080546001600160801b03191681556001810180546001600160a01b03191690556002810182905560038101829055600481018290556005810182905560068101829055600781018290559061358e6008830182614b6b565b600982016000905550506001600260008282546135ab91906154a4565b9091555050505050565b60006135ca6001600160a01b03841683613f5e565b905080516000141580156135ef5750808060200190518101906135ed919061584d565b155b15610a575782604051635274afe760e01b8152600401610b269190615009565b613617613f73565b610a6c57604051631afcd79f60e31b815260040160405180910390fd5b61363c61360f565b6001600160a01b038116610b2f576000604051631e4fbdf760e01b8152600401610b269190615009565b61366e61360f565b6000613678612316565b805460ff1916905550565b60008060008060008060006136b989896040516020016136a591815260200190565b604051602081830303815290604052613f8d565b935093509350935060405160308152602080820152602060408201528460608201528360808201526001609082015260008051602061587083398151915260b082015260208160d0836005600019fa61371157600080fd5b805197505060405160308152602080820152836050820152602060408201528260708201526001609082015260008051602061587083398151915260b082015260208160d0836005600019fa61376657600080fd5b51969996985060019081161496505050505050565b60008060008060008061379088888a8a6140d6565b90925090506137a182828a8a6140d6565b90925090506137df82826000805160206158d18339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2614147565b90999098509650505050505050565b600080600080600085600003613830576138078761417b565b909350905080156138215782600094509450505050612ae6565b60008394509450505050612ae6565b6000805160206158708339815191528788099250600080516020615870833981519152868709915060008051602061587083398151915282840892506138758361417b565b90935090508061388e5760008094509450505050612ae6565b60008051602061587083398151915283880891506138ab82614212565b915060006138b88361417b565b9250905081613909576138da888560008051602061587083398151915261426c565b92506138e583614212565b92506138f08361417b565b9250905081613909576000809550955050505050612ae6565b80600080516020615870833981519152828308935061393684600080516020615870833981519152614290565b93506000600080516020615870833981519152858a09919a91995090975050505050505050565b60008080613979856000805160206158708339815191526154a4565b90506000613995856000805160206158708339815191526154a4565b9196919550909350505050565b60006139b0858585856139b9565b95945050505050565b60008060008060006139cd878789896140d6565b90945092506139de898981816140d6565b90925090506139ef82828b8b6140d6565b9092509050613a00848484846142e1565b9094509250613a3e84846000805160206158d18339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d26142e1565b909450925083158015613a4f575082155b9998505050505050505050565b613a64614b8c565b613a6c614b8c565b613a74614b8c565b613a7c614b8c565b84516020860151604087015160608801516080890151613ab4946744e992b44a6909f1949093909290918b60055b6020020151614323565b80516020820151604083015160608401516080850151949750613ae09460029493929190896005613aaa565b9150613b4c8360005b602002015184600160200201518560026020020151866003602002015187600460200201518860056020020151886000602002015189600160200201518a600260200201518b600360200201518c600460200201518d60055b60200201516143a6565b9150613b578261462f565b9150613b628361462f565b9050613b6d8161462f565b9050613b7a836000613ae9565b9250613be08360005b6020020151846001602002015185600260200201518660036020020151876004602002015188600560200201518760006020020151886001602002015189600260200201518a600360200201518b600460200201518c6005613b42565b9250613beb8561462f565b9050613bf68161462f565b9050613c018161462f565b90506139b0836000613b83565b600080600080600080613c218888614747565b9092509050613c328c8c84846140d6565b9096509450613c438a8a84846140d6565b969d959c509a50949850929650505050505050565b60008151835114613c6857600080fd5b82516000613c77826006615463565b90506000816001600160401b03811115613c9357613c93614db9565b604051908082528060200260200182016040528015613cbc578160200160208202803683370190505b50905060005b83811015613eed57868181518110613cdc57613cdc6155a5565b60200260200101516000015182826006613cf69190615463565b613d01906000615592565b81518110613d1157613d116155a5565b602002602001018181525050868181518110613d2f57613d2f6155a5565b60200260200101516020015182826006613d499190615463565b613d54906001615592565b81518110613d6457613d646155a5565b602002602001018181525050858181518110613d8257613d826155a5565b6020908102919091010151515182613d9b836006615463565b613da6906002615592565b81518110613db657613db66155a5565b602002602001018181525050858181518110613dd457613dd46155a5565b60209081029190910181015151015182613def836006615463565b613dfa906003615592565b81518110613e0a57613e0a6155a5565b602002602001018181525050858181518110613e2857613e286155a5565b602002602001015160200151600060028110613e4657613e466155a5565b602002015182613e57836006615463565b613e62906004615592565b81518110613e7257613e726155a5565b602002602001018181525050858181518110613e9057613e906155a5565b602002602001015160200151600160028110613eae57613eae6155a5565b602002015182613ebf836006615463565b613eca906005615592565b81518110613eda57613eda6155a5565b6020908102919091010152600101613cc2565b50613ef6614baa565b60006020826020860260208601600060086107d05a03f1905080613f505760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964205369676e617475726560781b6044820152606401610b26565b505115159695505050505050565b6060613f6c838360006147d2565b9392505050565b6000613f7d612da5565b54600160401b900460ff16919050565b60008060008060ff85511115613fa257600080fd5b600060405160005b6088811015613fc157600082820152602001613faa565b506088602060005b8a51811015613fea578a820151848401526020928301929182019101613fc9565b505060898951019050608081830153600201602060005b89518110156140225789820151848401526020928301929182019101614001565b5050608b88518a5101019050875181830153508751875101608c018120915050604051818152600160208201536021602060005b89518110156140775789820151848401526020928301929182019101614056565b505050865187516021018201538651602201812095508582188152600260208201538651602201812094508482188152600360208201538651602201812093508382188152600460208201539551602201909520939692955090935050565b60008061411460008051602061587083398151915285880960008051602061587083398151915285880960008051602061587083398151915261426c565b60008051602061587083398151915280868809600080516020615870833981519152868a09089150915094509492505050565b6000806000805160206158708339815191528487086000805160206158708339815191528487089150915094509492505050565b600080600060405160208152602080820152602060408201528460608201527f0c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52608082015260008051602061587083398151915260a082015260208160c08360056107d05a03fa905193509050600080516020615870833981519152838009841491508061420c5760009250600091505b50915091565b6000600182161515614225600284615490565b915080156142665760008051602061587083398151915260026142576000805160206158708339815191526001615592565b6142619190615490565b830891505b50919050565b6000818061427c5761427c61547a565b61428684846154a4565b8508949350505050565b60008060405160208152602080820152602060408201528460608201526002840360808201528360a082015260208160c08360056107d05a03fa905192509050806142da57600080fd5b5092915050565b6000806142fd868560008051602061587083398151915261426c565b614316868560008051602061587083398151915261426c565b9150915094509492505050565b61432b614b8c565b871561439b57600188161561436c578051602082015160408301516060840151608085015160a08601516143699594939291908d8d8d8d8d8d6143a6565b90505b61437a87878787878761486f565b949b50929950909750955093509150614394600289615490565b975061432b565b979650505050505050565b6143ae614b8c565b881580156143ba575087155b156143fc578686868686868660005b60a0890192909252608088019290925260608701929092526040860192909252602085810193909352909102015261461f565b82158015614408575081155b1561441b578c8c8c8c8c8c8660006143c9565b61442785858b8b6140d6565b90955093506144388b8b85856140d6565b6060830152604082015261444e87878b8b6140d6565b909750955061445f8d8d85856140d6565b60a0830152608082018190528714801561447c575060a081015186145b156144c1576040810151851480156144975750606081015184145b156144b2576144aa8d8d8d8d8d8d61486f565b8660006143c9565b600160008181808086816143c9565b6144cd898985856140d6565b90935091506144ed858583600260200201518460035b60200201516142e1565b909d509b50614507878783600460200201518460056144e3565b909b5099506145188b8b81816140d6565b9099509750614538898983600460200201518460055b60200201516140d6565b909550935061454989898d8d6140d6565b909950975061455a898985856140d6565b60a083015260808201526145708d8d81816140d6565b9097509550614581878785856140d6565b909750955061459287878b8b6142e1565b90975095506145a3858560026149de565b90935091506145b4878785856142e1565b90975095506145c58b8b89896140d6565b602083015281526145d8858589896142e1565b909b5099506145e98d8d8d8d6140d6565b909b5099506146038989836002602002015184600361452e565b909d509b506146148b8b8f8f6142e1565b606083015260408201525b9c9b505050505050505050505050565b614637614b8c565b815161464b908360015b6020020151614a11565b60208301528152604082015161466390836003614641565b60608301526040820152608082015161467e90836005614641565b60a08301526080820152805160208201516146db91907f2fb347984f7911f74c0bec3cf559b143b78cc310c2c3330c99e39557176f553d7f16c9e55061ebae204ba4cc8bd75a079432ae2a1d0b7c9dce1665d51c640fcba26140d6565b602083015281526040810151606082015161473891907f063cf305489af5dcdc5ec698b6e2f9b9dbaae0eda9c95998dc54014671a0135a7f07c03cbcac41049a0704b5a7ec796f2b21807dc98fa25bd282d37f632623b0e36140d6565b60608301526040820152919050565b600080806147886000805160206158708339815191528087880960008051602061587083398151915287880908600080516020615870833981519152614290565b90506000805160206158708339815191528186096000805160206158708339815191528286096147c6906000805160206158708339815191526154a4565b92509250509250929050565b6060814710156147f7573060405163cd78605960e01b8152600401610b269190615009565b600080856001600160a01b031684866040516148139190615513565b60006040518083038185875af1925050503d8060008114614850576040519150601f19603f3d011682016040523d82523d6000602084013e614855565b606091505b5091509150614865868383614a38565b9695505050505050565b6000806000806000806148848c8c60036149de565b909650945061489586868e8e6140d6565b90965094506148a68a8a8a8a6140d6565b90985096506148b78c8c8c8c6140d6565b90945092506148c884848a8a6140d6565b90945092506148d9868681816140d6565b909c509a506148ea848460086149de565b90925090506148fb8c8c84846142e1565b909c509a5061490c888881816140d6565b909250905061491d848460046149de565b909450925061492e84848e8e6142e1565b909450925061493f848488886140d6565b90945092506149508a8a60086149de565b909650945061496186868c8c6140d6565b9096509450614972868684846140d6565b9096509450614983848488886142e1565b90945092506149948c8c60026149de565b90965094506149a586868a8a6140d6565b90965094506149b6888884846140d6565b90925090506149c7828260086149de565b809250819350505096509650965096509650969050565b60008060008051602061587083398151915283860960008051602061587083398151915284860991509150935093915050565b60008083614a2d846000805160206158708339815191526154a4565b915091509250929050565b606082614a4d57614a4882614a8b565b613f6c565b8151158015614a6457506001600160a01b0384163b155b15614a845783604051639996b31560e01b8152600401610b269190615009565b5080613f6c565b805115614a9b5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b60408051610140810182526000808252602082018190529181019190915260608101614ade614b0e565b81526020016000815260200160008152602001600081526020016000815260200160608152602001600081525090565b604051806040016040528060008152602001600081525090565b60405180608001604052806004906020820280368337509192915050565b6040518060400160405280614b59614bc8565b8152602001614b66614bc8565b905290565b508054600082556003029060005260206000209081019061085c9190614be6565b6040518060c001604052806006906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b60405180604001604052806002906020820280368337509192915050565b5b80821115614c195780546001600160a01b03199081168255600182018054909116905560006002820155600301614be7565b5090565b80356001600160401b0381168114614c3457600080fd5b919050565b600060208284031215614c4b57600080fd5b613f6c82614c1d565b80518252602090810151910152565b805180516001600160a01b03908116845260209182015116818401520151604082015260600190565b600081518084526020840193506020830160005b82811015614cc457614cb3868351614c63565b955060209190910190600101614ca0565b5093949350505050565b60208152614ce86020820183516001600160401b03169052565b60006020830151614d0460408401826001600160401b03169052565b5060408301516001600160a01b0381166060840152506060830151614d2c6080840182614c54565b50608083015160c083015260a083015160e083015260c083015161010083015260e0830151610120830152610100830151610160610140840152614d74610180840182614c8c565b90506101208401516101608401528091505092915050565b600060208284031215614d9e57600080fd5b5035919050565b6001600160401b0391909116815260200190565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715614df157614df1614db9565b60405290565b604051608081016001600160401b0381118282101715614df157614df1614db9565b604051601f8201601f191681016001600160401b0381118282101715614e4157614e41614db9565b604052919050565b600060208284031215614e5b57600080fd5b81356001600160401b03811115614e7157600080fd5b8201601f81018413614e8257600080fd5b80356001600160401b03811115614e9b57614e9b614db9565b614eae601f8201601f1916602001614e19565b818152856020838501011115614ec357600080fd5b81602084016020830137600091810160200191909152949350505050565b6001600160a01b038116811461085c57600080fd5b60006080828403121561426657600080fd5b60006001600160401b03821115614f2157614f21614db9565b5060051b60200190565b600082601f830112614f3c57600080fd5b8135614f4f614f4a82614f08565b614e19565b8082825260208201915060208360051b860101925085831115614f7157600080fd5b602085015b83811015614f9557614f8781614c1d565b835260209283019201614f76565b5095945050505050565b60008060008060e08587031215614fb557600080fd5b8435614fc081614ee1565b935060208501359250614fd68660408701614ef6565b915060c08501356001600160401b03811115614ff157600080fd5b614ffd87828801614f2b565b91505092959194509250565b6001600160a01b0391909116815260200190565b604081016124418284614c54565b60008060008084860361010081121561504357600080fd5b604081121561505157600080fd5b50849350604084013592506150698660608601614ef6565b915060e08501356001600160401b03811115614ff157600080fd5b6040808252835190820181905260009060208501906060840190835b818110156150c75783516001600160401b03168352602093840193909201916001016150a0565b50508381036020808601919091528551808352918101925085019060005b8181101561510e576150f8848451614c54565b60409390930192602092909201916001016150e5565b50919695505050505050565b60006040828403121561512c57600080fd5b615134614dcf565b823581526020928301359281019290925250919050565b60006080828403121561515d57600080fd5b615165614df7565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b600082601f8301126151a157600080fd5b81356151af614f4a82614f08565b808282526020820191506020606084028601019250858311156151d157600080fd5b602085015b83811015614f955780870360608112156151ef57600080fd5b6151f7614dcf565b604082121561520557600080fd5b61520d614dcf565b9150823561521a81614ee1565b8252602083013561522a81614ee1565b602083810191909152918152604083013581830152845292909201916060016151d6565b60008060008084860361016081121561526657600080fd5b615270878761511a565b945061527f876040880161514b565b9350608060bf198201121561529357600080fd5b5061529c614df7565b60c0860135815260e08601356020820152610100860135604082015261012086013561ffff811681146152ce57600080fd5b606082015291506101408501356001600160401b038111156152ef57600080fd5b614ffd87828801615190565b6000806020838503121561530e57600080fd5b82356001600160401b0381111561532457600080fd5b8301601f8101851361533557600080fd5b80356001600160401b0381111561534b57600080fd5b8560208260051b840101111561536057600080fd5b6020919091019590945092505050565b60006020828403121561538257600080fd5b8135613f6c81614ee1565b918252602082015260400190565b600080600080600080600060e0888a0312156153b657600080fd5b87356153c181614ee1565b965060208801356153d181614ee1565b96999698505050506040850135946060810135946080820135945060a0820135935060c0909101359150565b600080600080610100858703121561541457600080fd5b61541e868661511a565b935061542d866040870161514b565b925060c085013561543d81614ee1565b9396929550929360e00135925050565b634e487b7160e01b600052601160045260246000fd5b80820281158282048414176124415761244161544d565b634e487b7160e01b600052601260045260246000fd5b60008261549f5761549f61547a565b500490565b818103818111156124415761244161544d565b6000608082840312156154c957600080fd5b613f6c838361514b565b6000604082840312156154e557600080fd5b613f6c838361511a565b60005b8381101561550a5781810151838201526020016154f2565b50506000910152565b600082516155258184602087016154ef565b9190910192915050565b6001600160401b039390931683526020830191909152604082015260600190565b6001600160401b038316815260608101613f6c602083018480358252602090810135910152565b93845260208401929092526040830152606082015260800190565b808201808211156124415761244161544d565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b0385168152600061010082016155db6020840187614c54565b8451606084015260208501516080840152604085015160a084015261ffff60608601511660c084015261010060e08401528084518083526101208501915060208601925060005b8181101561564657615635838551614c63565b602094909401939250600101615622565b509098975050505050505050565b6001600160a01b038316815260608101613f6c6020830184614c54565b60008235607e1983360301811261552557600080fd5b6000808335601e1984360301811261569e57600080fd5b8301803591506001600160401b038211156156b857600080fd5b6020019150606081023603821315612ae657600080fd5b80546001600160a01b0319166001600160a01b0392909216919091179055565b81356156fa81614ee1565b61570481836156cf565b50602082013561571381614ee1565b61572081600184016156cf565b50604082013560028201555050565b6001600160401b0381811683821601908111156124415761244161544d565b6001600160a01b038416815260208101839052608081016132a46040830184614c54565b6000600182016157845761578461544d565b5060010190565b60006001600160401b0382166002600160401b031981016157ae576157ae61544d565b60010192915050565b6001600160401b039290921682526001600160a01b0316602082015260400190565b600084516157eb8184602089016154ef565b919091019283525060601b6001600160601b0319166020820152603401919050565b600060ff821660ff81036157ae576157ae61544d565b634e487b7160e01b600052600160045260246000fd5b6000826158485761584861547a565b500690565b60006020828403121561585f57600080fd5b81518015158114613f6c57600080fdfe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd476e0956cda88cad152e89927e53611735b61a5c762d1428573c6931b0a5efcb01424c535f5349475f545259414e44494e4352454d454e545f4c49515549444154452b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5a164736f6c634300081a000a", - "linkReferences": {}, - "deployedLinkReferences": {} -} diff --git a/config_defaults.py b/config_defaults.py deleted file mode 100644 index a8de692..0000000 --- a/config_defaults.py +++ /dev/null @@ -1,66 +0,0 @@ -# Default configuration options for SENT staking website backend. -# -# To override settings add `config.whatever = ...` into `config.py`; this file should not be -# modified and simply contains the default values. -# -# To override things that are specific to mainnet/testnet/etc. add `config.whatever = ...` lines -# into `mainnet.py`/`testnet.py`/etc. -import logging - -# LMQ RPC endpoint of oxend; can be a unix socket 'ipc:///path/to/oxend.sock' or a tcp socket -# 'tcp://127.0.0.1:5678'. mainnet_rpc/testnet_rpc/devnet_rpc are selected based on whether the -# backend is running through the mainnet.py, testnet.py, or devnet.py application script. - -# SQLite database used for persistent data, such as shorted registration URL tokens. - -B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" - -class Backend: - sqlite_db: str = 'sent-backend.db' - thread_pool_max_workers: int = 10 - stale_time_seconds: int = 30 - rpc: str = '' - oxen_wallet_regex: str = '' - reward_rate_pool_addr: str = '0x0000000000000000000000000000000000000000' - sn_contrib_factory_addr: str = '0x0000000000000000000000000000000000000000' - sn_rewards_addr: str = '0x0000000000000000000000000000000000000000' - sn_token_addr: str = '0x0000000000000000000000000000000000000000' - provider_url: str = 'http://localhost:8545' # Default hardhat private chain node address - log_level = logging.INFO - -# Session mainnet contracts -mainnet_backend = Backend() -mainnet_backend.sqlite_db = 'ssb-mainnet.db' -mainnet_backend.rpc = 'ipc://oxend/mainnet.sock' -mainnet_backend.oxen_wallet_regex = f'L[{B58_ALPHABET}]{{94}}"' - -# Session testnet contracts -testnet_backend = Backend() -testnet_backend.sqlite_db = 'ssb-testnet.db' -testnet_backend.rpc = 'ipc://oxend/testnet.sock' -testnet_backend.oxen_wallet_regex = f"T[{B58_ALPHABET}]{{96}}" - -# Session devnet.v3 contracts -devnet_backend = Backend() -devnet_backend.sqlite_db = 'ssb-devnet.db' -devnet_backend.rpc = 'ipc://oxend/devnet.sock' -devnet_backend.oxen_wallet_regex = f"dV[{B58_ALPHABET}]{{95}}" -devnet_backend.reward_rate_pool_addr = '0xb515C61DE12f28eE908a905b930aFb80B9bAd7cf' -devnet_backend.sn_contrib_factory_addr = '0x0000000000000000000000000000000000000000' -devnet_backend.sn_rewards_addr = '0x75Dc11700b2D03902FCb5Ca7aFd6A859a1Fa25Cb' -devnet_backend.sn_token_addr = '0x0000000000000000000000000000000000000000' -devnet_backend.provider_url = 'https://sepolia-rollup.arbitrum.io/rpc' - -# Session stagenet.v3 contracts -stagenet_backend = Backend() -stagenet_backend.sqlite_db = 'ssb-stagenet.db' -stagenet_backend.rpc = 'ipc://oxend/stagenet.sock' -stagenet_backend.oxen_wallet_regex = f"ST[{B58_ALPHABET}]{{95}}" -stagenet_backend.reward_rate_pool_addr = '0x38cD8D3F93d591C18cf26B3Be4CB2c872aC37953' -stagenet_backend.sn_contrib_factory_addr = '0x66d0D4f71267b3150DafF7bD486AC5E097E7E4C6' -stagenet_backend.sn_rewards_addr = '0x4abfFB7f922767f22c7aa6524823d93FDDaB54b1' -stagenet_backend.sn_token_addr = '0x70c1f36C9cEBCa51B9344121D284D85BE36CD6bB' -stagenet_backend.provider_url = 'https://sepolia-rollup.arbitrum.io/rpc' - -# Assign the active backend to be used in the sent-staking-backend -backend = stagenet_backend diff --git a/contracts/service_node_contribution.py b/contracts/service_node_contribution.py deleted file mode 100644 index 90badbf..0000000 --- a/contracts/service_node_contribution.py +++ /dev/null @@ -1,132 +0,0 @@ -from web3 import Web3 -from abi_manager import ABIManager - -class ContributorContractInterface: - """ Parent class to handle Web3 connection and load ABI for contracts. """ - - def __init__(self, provider_url): - """ - Initialize the connection to the Ethereum provider. - :param provider_url: URL of the Ethereum node to connect to. - """ - self.web3 = Web3(Web3.HTTPProvider(provider_url)) - manager = ABIManager() - self.abi = manager.load_abi('ServiceNodeContribution') - - def get_contract_instance(self, contract_address): - """ - Create an instance of a contract at a given address. - :param contract_address: Address of the contract to interact with. - :return: Web3 Contract object. - """ - contract = self.web3.eth.contract(address=Web3.to_checksum_address(contract_address), abi=self.abi) - return ServiceNodeContribution(contract) - -class ServiceNodeContribution: - """ Child class to interact with specific Service Node Contribution contracts. """ - - def __init__(self, contract): - """ - Initialize the contract interaction class with the contract. - :param contract: Web3 Contract object. - """ - self.contract = contract - - def get_contributor_contribution(self, contributor_address): - """ - Get the contribution amount of a specific contributor. - :param contributor_address: Address of the contributor. - :return: Contribution amount of the specified contributor. - """ - return self.contract.functions.contributions(Web3.to_checksum_address(contributor_address)).call() - - def status(self): - """ - Check if the service node is finalized. - :return: True if the service node is finalized, otherwise False. - """ - return self.contract.functions.status().call() - - def is_cancelled(self): - """ - Check if the service node has been cancelled. - :return: True if the service node has been cancelled, otherwise False. - """ - return self.contract.functions.cancelled().call() - - def total_contribution(self): - """ - Get the total amount of contributions received. - :return: Total contributions amount. - """ - return self.contract.functions.totalContribution().call() - - def contributor_count(self): - """ - Get the number of contributors. - :return: Number of contributors. - """ - return len(self.contract.functions.contributorAddresses().call()) - - def minimum_contribution(self): - """ - Get the minimum contribution required. - :return: Minimum contribution amount. - """ - return self.contract.functions.minimumContribution().call() - - def get_bls_pubkey(self): - """ - Get the BLS public key. - :return: BLS public key, in hex. - """ - pks = self.contract.functions.blsPubkey().call() - return "0x{:0128x}".format((pks[0] << 256) + pks[1]) - - def get_service_node_params(self): - """ - Get the parameters of the service node. - :return: Dictionary containing service node parameters. - """ - params = self.contract.functions.serviceNodeParams().call() - return { - 'serviceNodePubkey': f"{params[0]:032x}", - 'serviceNodeSignature': f"{params[1]:032x}{params[2]:032x}", - 'fee': params[3] - } - - def get_operator(self): - """ - returns the service node operator - """ - return self.contract.functions.operator().call() - - def get_contributions(self): - contributions = self.contract.functions.getContributions().call() - # (address[] memory addrs, address[] memory beneficiaries, uint256[] memory contribs) - addresses = contributions[0] - beneficiaries = contributions[1] - contributions = contributions[2] - - contributions_list = [] - for i in range(len(addresses)): - contributions_list.append({ - "address": addresses[i], - "amount": contributions[i], - "beneficiary": beneficiaries[i] - }) - return contributions_list - - -# Example usage: -# provider_url = 'http://127.0.0.1:8545' -# contract_address = '0x...' - -# contract_interface = ContributorContractInterface(provider_url) -# service_node = contract_interface.get_contract_instance(contract_address) - -# Fetch and display data from the contract -# print("Total Contribution:", service_node.total_contribution()) -# print("Is Finalized:", service_node.is_finalized()) -# print("Is Cancelled:", service_node.is_cancelled()) -# print("Minimum Contribution Required:", service_node.minimum_contribution()) diff --git a/contracts/service_node_contribution_factory.py b/contracts/service_node_contribution_factory.py deleted file mode 100644 index f384a27..0000000 --- a/contracts/service_node_contribution_factory.py +++ /dev/null @@ -1,59 +0,0 @@ -from web3 import Web3 -from abi_manager import ABIManager - -class ServiceNodeContributionFactory: - def __init__(self, provider_url, contract_address): - """ - Initialize the connection to the ServiceNodeContributionFactory contract. - - :param provider_url: URL of the Ethereum node to connect to. - :param contract_address: Address of the deployed ServiceNodeContributionFactory contract. - """ - self.web3 = Web3(Web3.HTTPProvider(provider_url)) - self.contract_address = Web3.to_checksum_address(contract_address) - manager = ABIManager() - abi = manager.load_abi('ServiceNodeContributionFactory') - self.contract = self.web3.eth.contract(address=self.contract_address, abi=abi) - self.last_contribution_event_height = 0 - - def max_contributors(self): - """ - Calls the view function to get the maximum number of contributors. - - :return: Maximum number of contributors as integer. - """ - return self.contract.functions.maxContributors().call() - - def designated_token(self): - """ - Calls the view function to get the designated token address. - - :return: Address of the designated token as string. - """ - return self.contract.functions.SENT().call() - - def get_new_contribution_contract_events(self, from_block='latest'): - """ - Retrieves the events of new contribution contracts deployed. - - :param from_block: The block number to start looking for events. - :return: List of events. - """ - return self.contract.events.NewServiceNodeContributionContract.get_logs(from_block=from_block) - - def get_latest_contribution_contract_events(self): - """ - Retrieves the latest events of new contribution contracts deployed. keeping track of when last called - - :return: List of events. - """ - events = self.get_new_contribution_contract_events(self.last_contribution_event_height) - self.last_contribution_event_height = self.web3.eth.block_number - return events - -# Example usage: -# factory_interface = ServiceNodeContributionFactory('http://127.0.0.1:8545', '0x...') -# max_contributors = factory_interface.max_contributors() -# designated_token = factory_interface.designated_token() -# new_contribution_contracts = factory_interface.get_new_contribution_contract_events() - diff --git a/devnet.py b/devnet.py deleted file mode 100644 index 4cec498..0000000 --- a/devnet.py +++ /dev/null @@ -1,6 +0,0 @@ -from sent import app, config -import oxenmq - -config.devnet = True - -config.oxend_rpc = 'ipc://oxend/devnet.sock' diff --git a/mainnet.py b/mainnet.py deleted file mode 100644 index 259348a..0000000 --- a/mainnet.py +++ /dev/null @@ -1,4 +0,0 @@ -from sent import app, config -import oxenmq - -config.oxend_rpc = 'ipc://oxend/mainnet.sock' diff --git a/omq.py b/omq.py deleted file mode 100644 index 2d95d48..0000000 --- a/omq.py +++ /dev/null @@ -1,81 +0,0 @@ -import oxenmq -import config -import json -import sys -from datetime import datetime, timedelta - -omq, oxend = None, None -def omq_connection(): - global omq, oxend - if omq is None: - omq = oxenmq.OxenMQ(log_level=oxenmq.LogLevel.warn) - omq.max_message_size = 200*1024*1024 - omq.start() - if oxend is None: - oxend_rpc = config.backend.rpc - oxend = omq.connect_remote(oxenmq.Address(oxend_rpc)) - return (omq, oxend) - -cached = {} -cached_args = {} -cache_expiry = {} - -class FutureJSON(): - """Class for making a OMQ JSON RPC request that uses a future to wait on the result, and caches - the results for a set amount of time so that if the same endpoint with the same arguments is - requested again the cache will be used instead of repeating the request. - - Cached values are indexed by endpoint and optional key, and require matching arguments to the - previous call. The cache_key should generally be a fixed value (*not* an argument-dependent - value) and can be used to provide multiple caches for different uses of the same endpoint. - Cache entries are *not* purged, they are only replaced, so using dynamic data in the key would - result in unbounded memory growth. - - omq - the omq object - oxend - the oxend omq connection id object - endpoint - the omq endpoint, e.g. 'rpc.get_info' - cache_seconds - how long to cache the response; can be None to not cache it at all - cache_key - fixed string to enable different caches of the same endpoint - args - if not None, a value to pass (after converting to JSON) as the request parameter. Typically a dict. - fail_okay - can be specified as True to make failures silent (i.e. if failures are sometimes expected for this request) - timeout - maximum time to spend waiting for a reply - """ - - def __init__(self, omq, oxend, endpoint, cache_seconds=5, *, cache_key='', args=None, fail_okay=False, timeout=10): - self.endpoint = endpoint - self.cache_key = self.endpoint + cache_key - self.fail_okay = fail_okay - if args is not None: - args = json.dumps(args).encode() - if self.cache_key in cached and cached_args[self.cache_key] == args and cache_expiry[self.cache_key] >= datetime.now(): - self.json = cached[self.cache_key] - self.args = None - self.future = None - else: - self.json = None - self.args = args - self.future = omq.request_future(oxend, self.endpoint, [] if self.args is None else [self.args], timeout=timeout) - self.cache_seconds = cache_seconds - - def get(self): - """If the result is already available, returns it immediately (and can safely be called multiple times. - Otherwise waits for the result, parses as json, and caches it. Returns None if the request fails""" - if self.json is None and self.future is not None: - try: - result = self.future.get() - self.future = None - if result[0] != b'200': - raise RuntimeError("Request for {} failed: got {}".format(self.endpoint, result)) - self.json = json.loads(result[1]) - if self.cache_seconds is not None: - cached[self.cache_key] = self.json - cached_args[self.cache_key] = self.args - cache_expiry[self.cache_key] = datetime.now() + timedelta(seconds=self.cache_seconds) - except RuntimeError as e: - if not self.fail_okay: - print("Something getting wrong: {}".format(e), file=sys.stderr) - self.future = None - - return self.json - - diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..eef2ade --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +python_files = *_tests.py diff --git a/requirements.txt b/requirements.txt index 6517afa..81e6e30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,13 @@ -oxenmq==1.0.5 +oxenmq>=1.0.5 Flask==3.0.3 -oxenc==1.0.4 +oxenc>=1.0.4 PyNaCl==1.5.0 eth-utils==5.0.0 Werkzeug==3.0.4 -uWSGI==2.0.26 -web3==7.2.0 -eth-typing==5.0.0 \ No newline at end of file +web3==7.11.1 +eth-typing==5.0.0 +eth_abi==5.1.0 +requests==2.32.3 +attrs==24.2.0 +pytest==8.3.4 +py-solc-x==2.0.3 \ No newline at end of file diff --git a/run_fetcher.py b/run_fetcher.py new file mode 100644 index 0000000..46d51cd --- /dev/null +++ b/run_fetcher.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +import src.app_fetcher \ No newline at end of file diff --git a/schema.sqlite b/schema.sqlite deleted file mode 100644 index cd29d8c..0000000 --- a/schema.sqlite +++ /dev/null @@ -1,32 +0,0 @@ - -PRAGMA journal_mode=WAL; - -CREATE TABLE registrations ( - id INTEGER PRIMARY KEY NOT NULL, - pubkey_ed25519 BLOB NOT NULL, - pubkey_bls BLOB NOT NULL, - sig_ed25519 BLOB NOT NULL, - sig_bls BLOB NOT NULL, - operator BLOB NOT NULL, - contract BLOB, - timestamp FLOAT NOT NULL DEFAULT ((julianday('now') - 2440587.5)*86400.0), /* unix epoch */ - - CHECK(length(pubkey_ed25519) == 32), - CHECK(length(pubkey_bls) == 64), - CHECK(length(sig_ed25519) == 64), - CHECK(length(sig_bls) == 128), - CHECK(length(operator) == 20), - CHECK(contract IS NULL OR length(contract) == 20) -); -CREATE INDEX registrations_operator_idx ON registrations(operator); -CREATE UNIQUE INDEX registration_pk_multi_idx ON registrations(pubkey_ed25519, contract IS NULL); - -CREATE TABLE contribution_contracts ( - id INTEGER PRIMARY KEY NOT NULL, - contract_address TEXT NOT NULL, - status INTEGER DEFAULT 1, - timestamp FLOAT NOT NULL DEFAULT ((julianday('now') - 2440587.5)*86400.0), /* unix epoch */ - - CHECK(length(contract_address) == 42) -- Assuming Ethereum addresses -); -CREATE UNIQUE INDEX contribution_contract_address_idx ON contribution_contracts(contract_address); diff --git a/scripts/install-local-deps.sh b/scripts/install-local-deps.sh new file mode 100755 index 0000000..8fe94fc --- /dev/null +++ b/scripts/install-local-deps.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +mkdir dep-tmp +git clone https://github.com/oxen-io/oxen-pyoxenc ./dep-tmp/oxenc +git clone https://github.com/oxen-io/oxen-pyoxenmq ./dep-tmp/oxenmq +pip install ./dep-tmp/oxenc ./dep-tmp/oxenmq + +rm -rf dep-tmp + +pip install -r requirements.txt diff --git a/scripts/install-system-deps.sh b/scripts/install-system-deps.sh new file mode 100755 index 0000000..dc8dda5 --- /dev/null +++ b/scripts/install-system-deps.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e + +sudo curl -so /etc/apt/trusted.gpg.d/oxen.gpg https://deb.oxen.io/pub.gpg +echo "deb https://deb.oxen.io $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/oxen.list +sudo apt update +sudo apt install build-essential python3-pip python3-dev pybind11-dev liboxenc-dev liboxenmq-dev python3.12-venv diff --git a/scripts/setup-venv.sh b/scripts/setup-venv.sh new file mode 100755 index 0000000..a030d01 --- /dev/null +++ b/scripts/setup-venv.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +python3 -m venv .venv +. .venv/bin/activate diff --git a/sent.py b/sent.py deleted file mode 100644 index a57efe8..0000000 --- a/sent.py +++ /dev/null @@ -1,2113 +0,0 @@ -#!/usr/bin/env python3 -from time import perf_counter - -import asyncio -from concurrent.futures import ThreadPoolExecutor -import uuid - -import flask -import string -import time -import oxenc -import sqlite3 -import re -import nacl.hash -import nacl.bindings as sodium -import eth_utils -import subprocess -import config -import datetime - -from itertools import chain -from eth_typing import ChecksumAddress -from typing import TypedDict, Callable, Any, Union -from functools import partial -from werkzeug.routing import BaseConverter -from nacl.signing import VerifyKey -from omq import FutureJSON, omq_connection -from timer import timer - -from contracts.reward_rate_pool import RewardRatePoolInterface -from contracts.service_node_contribution import ContributorContractInterface -from contracts.service_node_contribution_factory import ServiceNodeContributionFactory -from contracts.service_node_rewards import ServiceNodeRewardsInterface, ServiceNodeRewardsRecipient - -TOKEN_NAME = "SENT" - -class WalletInfo(): - def __init__(self): - self.rewards = 0 # Atomic SENT - self.contract_rewards = 0 - self.contract_claimed = 0 - -def oxen_rpc_get_accrued_rewards(omq, oxend) -> FutureJSON: - result = FutureJSON(omq, oxend, 'rpc.get_accrued_rewards', args={'addresses': []}) - return result - -def oxen_rpc_bls_rewards_request(omq, oxend, eth_address: str) -> FutureJSON: - eth_address_for_rpc = eth_address.lower() - if eth_address_for_rpc.startswith("0x"): - eth_address_for_rpc = eth_address_for_rpc[2:] - result = FutureJSON(omq, oxend, 'rpc.bls_rewards_request', args={'address': eth_address_for_rpc}) - return result - -def oxen_rpc_bls_exit_liquidation(omq, oxend, ed25519_pubkey: bytes, liquidate: bool) -> FutureJSON: - return FutureJSON(omq, oxend, 'rpc.bls_exit_liquidation_request', args={'pubkey': ed25519_pubkey.hex(), 'liquidate': liquidate}) - -def get_oxen_rpc_bls_exit_liquidation_list(omq, oxend): - return FutureJSON(omq, oxend, 'rpc.bls_exit_liquidation_list') - -class App(flask.Flask): - def __init__(self): - super().__init__(__name__) - self.logger.setLevel(config.backend.log_level) - - self.service_node_rewards = ServiceNodeRewardsInterface(config.backend.provider_url, config.backend.sn_rewards_addr) - self.reward_rate_pool = RewardRatePoolInterface(config.backend.provider_url, config.backend.reward_rate_pool_addr) - self.service_node_contribution_factory = ServiceNodeContributionFactory(config.backend.provider_url, config.backend.sn_contrib_factory_addr) - self.service_node_contribution = ContributorContractInterface(config.backend.provider_url) - - self.bls_pubkey_to_contract_id_map: dict[str, int] = {} # (BLS public key -> contract_id) - - self.wallet_to_sn_map: dict[ChecksumAddress, set[int]] = {} # (0x wallet address -> Set of contract_id's of stakes they are contributors to) - self.contract_id_to_sn_map: dict[int, dict] = {} # (contract_id -> Oxen RPC get_service_nodes result (augmented w/ extra metadata like SN contract ID)) - - self.wallet_to_exitable_sn_map: dict[ChecksumAddress, set[int]] = {} # (0x wallet address -> Set of contract_id's of SN's they can liquidate/exit) - self.contract_id_to_exitable_sn_map: dict[int, dict] = {} # (contract_id -> SNInfo) - - self.tmp_db_trigger_wallet_addresses: set[ChecksumAddress] = set() # Wallet addresses that have triggered a db get between scheduled times - self.tracked_wallet_addresses: set[ChecksumAddress] = set() # Tracked wallet addresses to fetch data from the db for - self.wallet_to_historical_stakes_map: dict[ChecksumAddress, set[int]] = {} # (0x wallet address -> Set of contract_ids) - self.contract_id_to_historical_stakes_map: dict[int, Stake] = {} # (contract_id -> Stake Info) - - - self.contributors = {} - - self.contracts_stale_timestamp = 0 - self.contracts = None - - self.wallet_map = {} # (Binary ETH wallet address -> WalletInfo) - git_rev = subprocess.run(["git", "rev-parse", "--short=9", "HEAD"], stdout=subprocess.PIPE, text=True) - self.git_rev = git_rev.stdout.strip() if git_rev.returncode == 0 else "(unknown)" - - sql = sqlite3.connect(config.backend.sqlite_db) - cursor = sql.cursor() - cursor.execute("PRAGMA journal_mode=WAL") - cursor.execute("PRAGMA foreign_keys=ON") - - cursor.execute(""" - CREATE TABLE IF NOT EXISTS registrations ( - id INTEGER PRIMARY KEY NOT NULL, - pubkey_ed25519 BLOB NOT NULL, - pubkey_bls BLOB NOT NULL, - sig_ed25519 BLOB NOT NULL, - sig_bls BLOB NOT NULL, - operator BLOB NOT NULL, - contract BLOB, - timestamp FLOAT NOT NULL DEFAULT ((julianday('now') - 2440587.5)*86400.0), /* unix epoch */ - - CHECK(length(pubkey_ed25519) == 32), - CHECK(length(pubkey_bls) == 64), - CHECK(length(sig_ed25519) == 64), - CHECK(length(sig_bls) == 128), - CHECK(length(operator) == 20), - CHECK(contract IS NULL OR length(contract) == 20) - ) - """) - - cursor.execute(""" - CREATE INDEX IF NOT EXISTS registrations_operator_idx ON registrations(operator); - """) - - cursor.execute(""" - CREATE UNIQUE INDEX IF NOT EXISTS registration_pk_multi_idx ON registrations(pubkey_ed25519, contract IS NULL); - """) - - cursor.execute(""" - CREATE TABLE IF NOT EXISTS contribution_contracts ( - contract_address BLOB PRIMARY KEY NOT NULL, - pubkey_ed25519 BLOB NOT NULL, - pubkey_bls BLOB NOT NULL, - sig_ed25519 BLOB NOT NULL, - operator_address TEXT NOT NULL, - fee INTEGER NOT NULL, - status INTEGER NOT NULL, - total_contributions INTEGER NOT NULL DEFAULT 0, - - timestamp FLOAT NOT NULL DEFAULT ((julianday('now') - 2440587.5)*86400.0), /* unix epoch */ - - CHECK(length(contract_address) == 20) - ); - """) - - cursor.execute(""" - CREATE UNIQUE INDEX IF NOT EXISTS contribution_contract_address_idx ON contribution_contracts(contract_address); - """) - - cursor.execute(""" - CREATE TABLE IF NOT EXISTS contribution_contracts_contributions ( - contract_address BLOB NOT NULL, - address BLOB NOT NULL, - beneficiary_address BLOB NOT NULL, - amount INTEGER NOT NULL, - reserved INTEGER, - - CHECK(length(address) == 20), - - FOREIGN KEY (contract_address) REFERENCES contribution_contracts(contract_address), - PRIMARY KEY (contract_address, address) - ); - """) - - cursor.execute(""" - CREATE UNIQUE INDEX IF NOT EXISTS idx_contribution_contracts_contributions_contract_address_address ON contribution_contracts_contributions(contract_address, address); - """) - - cursor.execute(""" - CREATE UNIQUE INDEX IF NOT EXISTS idx_contribution_contracts_contributions_contract_address_address_amount ON contribution_contracts_contributions(contract_address, address, amount); - """) - - - cursor.execute(""" - CREATE TABLE IF NOT EXISTS stakes ( - id INTEGER PRIMARY KEY NOT NULL, /* Contract ID */ - last_updated INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), - - pubkey_bls BLOB NOT NULL, - deregistration_unlock_height INTEGER, - earned_downtime_blocks INTEGER, - last_reward_block_height INTEGER, - last_uptime_proof INTEGER, - operator_address BLOB NOT NULL, - operator_fee INTEGER, - requested_unlock_height INTEGER, - service_node_pubkey BLOB NOT NULL, - state TEXT NOT NULL - - CHECK(length(operator_address) == 20) - - ) - """) - - cursor.execute(""" - CREATE TABLE IF NOT EXISTS stake_contributions ( - contract_id INTEGER NOT NULL, - address BLOB NOT NULL, - amount INTEGER NOT NULL, - reserved INTEGER, - - CHECK(length(address) == 20), - - FOREIGN KEY (contract_id) REFERENCES stakes(id), - PRIMARY KEY (contract_id, address) - ); - """) - - cursor.execute(""" - CREATE UNIQUE INDEX IF NOT EXISTS idx_stake_contributions_contract_id_address ON stake_contributions(contract_id, address); - """) - - cursor.execute(""" - CREATE UNIQUE INDEX IF NOT EXISTS idx_stake_contributions_contract_id_address_amount ON stake_contributions(contract_id, address, amount); - """) - - - cursor.close() - sql.close() - - -app = App() - -def get_sql(): - if "db" not in flask.g: - flask.g.sql = sqlite3.connect(config.backend.sqlite_db) - return flask.g.sql - -def date_now_str() -> str: - result = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] - return result - -# Validates that input is 64 hex bytes and converts it to 32 bytes. -class Hex64Converter(BaseConverter): - def __init__(self, url_map): - super().__init__(url_map) - self.regex = "[0-9a-fA-F]{64}" - - def to_python(self, value): - return bytes.fromhex(value) - - def to_url(self, value): - return value.hex() - - -eth_regex = "0x[0-9a-fA-F]{40}" -class EthConverter(BaseConverter): - def __init__(self, url_map): - super().__init__(url_map) - self.regex = eth_regex - - -class OxenConverter(BaseConverter): - def __init__(self, url_map): - super().__init__(url_map) - self.regex = config.backend.oxen_wallet_regex - - -class OxenEthConverter(BaseConverter): - def __init__(self, url_map): - super().__init__(url_map) - self.regex = f"{eth_regex}|{config.backend.oxen_wallet_regex}" - - -app.url_map.converters["hex64"] = Hex64Converter -app.url_map.converters["eth_wallet"] = EthConverter -app.url_map.converters["oxen_wallet"] = OxenConverter -app.url_map.converters["either_wallet"] = OxenEthConverter - - -def get_sns_future(omq, oxend) -> FutureJSON: - return FutureJSON( - omq, - oxend, - "rpc.get_service_nodes", - args={ - "all": False, - "fields": { - x: True - for x in ( - "service_node_pubkey", - "requested_unlock_height", - "last_reward_block_height", - "active", - "pubkey_bls", - "funded", - "earned_downtime_blocks", - "service_node_version", - "contributors", - "total_contributed", - "total_reserved", - "staking_requirement", - "portions_for_operator", - "operator_address", - "pubkey_ed25519", - "last_uptime_proof", - "state_height", - "swarm_id", - "is_removable", - "is_liquidatable", - "operator_fee" - ) - }, - }, - ) - -def get_sns(sns_future, info_future): - info = info_future.get() - awaiting_sns, active_sns, inactive_sns = [], [], [] - sn_states = sns_future.get() - sn_states = ( - sn_states["service_node_states"] if "service_node_states" in sn_states else [] - ) - for sn in sn_states: - sn["contribution_open"] = sn["staking_requirement"] - sn["total_reserved"] - sn["contribution_required"] = ( - sn["staking_requirement"] - sn["total_contributed"] - ) - sn["num_contributions"] = sum( - len(x["locked_contributions"]) - for x in sn["contributors"] - if "locked_contributions" in x - ) - - if sn["active"]: - active_sns.append(sn) - elif sn["funded"]: - sn["decomm_blocks_remaining"] = max(sn["earned_downtime_blocks"], 0) - sn["decomm_blocks"] = info["height"] - sn["state_height"] - inactive_sns.append(sn) - else: - awaiting_sns.append(sn) - return awaiting_sns, active_sns, inactive_sns - - -def hexify(container): - """ - Takes a dict or list and mutates it to change any `bytes` values in it to str hex representation - of the bytes, recursively. - """ - if isinstance(container, dict): - it = container.items() - elif isinstance(container, list): - it = enumerate(container) - else: - return - - for i, v in it: - if isinstance(v, bytes): - container[i] = v.hex() - else: - hexify(v) - - -def get_timers_hours(network_type: str): - match network_type: - case 'testnet' | 'stagenet' | 'devnet' | 'localdev' | 'fakechain': - return { - 'deregistration_lock_duration_hours': 48, - 'unlock_duration_hours': 24, - } - case 'mainnet': - return { - 'deregistration_lock_duration_hours': 30 * 24, - 'unlock_duration_hours': 15 * 24, - } - case _: - raise ValueError(f"Unknown network type {network_type}") - - -@app.route("/timers/") -def fetch_network_timers(network_type: str = None): - if network_type is None: - return json_response(get_timers_hours(get_info().get('nettype'))) - else: - return json_response(get_timers_hours(network_type)) - - -# Target block time in seconds -TARGET_BLOCK_TIME = 120 - - -def blocks_in(seconds: int): - """ - Mimics the behavior of the oxend `blocks_in` function. - """ - return int(seconds / TARGET_BLOCK_TIME) - - -def get_info() -> dict: - omq, oxend = omq_connection() - info: dict | None = FutureJSON(omq, oxend, "rpc.get_info").get() - blk_header_result: dict | None = FutureJSON(omq, oxend, 'rpc.get_last_block_header', args={'fill_pow_hash': False, 'get_tx_hashes': False }).get() - - result: dict = {} - result['nettype'] = info['nettype'] - result['hard_fork'] = info['hard_fork'] - result['version'] = info['version'] - result['block_hash'] = info['top_block_hash'] - result['staking_requirement'] = info['staking_requirement'] - result['max_stakers'] = info['max_contributors'] - result['min_operator_contribution'] = info['min_operator_contribution'] - - blk_header = blk_header_result['block_header'] - result['block_timestamp'] = blk_header['timestamp'] - result['block_height'] = blk_header['height'] - result['block_hash'] = blk_header['hash'] - return result - - -def json_response(vals): - """ - Takes a dict, adds some general info fields to it, and jsonifies it for a flask route function - return value. The dict gets passed through `hexify` first to convert any bytes values to hex. - """ - - hexify(vals) - - return flask.jsonify({**vals, "network": get_info(), "t": time.time()}) - - -def get_frequently_update_contract_details(address: str): - try: - contract_interface = app.service_node_contribution.get_contract_instance(address) - - contributions = contract_interface.get_contributions() - status = contract_interface.status() - - total_contributions = 0 - for contribution in contributions: - amount = contribution.get('amount') - total_contributions += amount - - return address, status, contributions, total_contributions - except Exception as e: - app.logger.error("Error occurred while updating contract info: {}".format(e)) - return None - - -def get_base_contract_details(address: str): - contract_interface = app.service_node_contribution.get_contract_instance(address) - # Fetch statuses and other details - # TODO: this does 3 network requests one after the other, we need to improve this - operator = contract_interface.get_operator() - pubkey_bls = contract_interface.get_bls_pubkey() - service_node_params = contract_interface.get_service_node_params() - service_node_pubkey = service_node_params.get('serviceNodePubkey') - service_node_signature = service_node_params.get('serviceNodeSignature') - fee = service_node_params.get('fee') - - # TODO: this does 2 network requests one after the other, we need to improve this - _, status, contributions, total_contributions = get_frequently_update_contract_details(address) - - return address, operator, pubkey_bls, service_node_pubkey, service_node_signature, fee, status, contributions, total_contributions - - -get_frequently_update_contract_details_loop = asyncio.get_event_loop() - -async def update_contributor_contracts(addresses): - results = [] - with ThreadPoolExecutor(max_workers=config.backend.thread_pool_max_workers) as executor: - get_frequently_update_contract_details_loop = asyncio.get_event_loop() - futures = [ - get_frequently_update_contract_details_loop.run_in_executor( - executor, - get_frequently_update_contract_details, - address - ) for address in addresses - ] - for future in asyncio.as_completed(futures): - try: - # This will raise an exception if the thread raised an exception - result = await future - results.append(result) - - except Exception as e: - app.logger.error("Error occurred in thread: {}".format(e)) - return results - - -get_base_contract_details_loop = asyncio.get_event_loop() - - -async def get_base_contract_details_contracts(addresses): - results = [] - with ThreadPoolExecutor(max_workers=config.backend.thread_pool_max_workers) as executor: - get_base_contract_details_loop = asyncio.get_event_loop() - futures = [ - get_base_contract_details_loop.run_in_executor( - executor, - get_base_contract_details, - address - ) for address in addresses - ] - for future in asyncio.as_completed(futures): - try: - # This will raise an exception if the thread raised an exception - result = await future - if result is None: - continue - results.append(result) - except Exception as e: - app.logger.error("Error occurred in thread: {}".format(e)) - return results - - -@timer(30) -def process_new_contribution_contracts(signum): - app.logger.info("{} Process new contribution contracts start".format(date_now_str())) - perf_start = perf_counter() - - new_contracts = app.service_node_contribution_factory.get_latest_contribution_contract_events() - - app.logger.debug('Found {} new contract events'.format(len(new_contracts))) - - addresses = [] - for event in new_contracts: - contract_address = event.args.contributorContract - if contract_address is None: - continue - addresses.append(contract_address) - - results = get_base_contract_details_loop.run_until_complete(get_base_contract_details_contracts(addresses)) - - with app.app_context(), get_sql() as sql: - cursor = sql.cursor() - for details in results: - if details is None: - app.logger.warning("No details for new contract") - continue - - address, operator, pubkey_bls, service_node_pubkey, service_node_signature, fee, status, contributions, total_contributions = details - contract_address_bytes = address_to_bytes(address) - - cursor.execute( - """ - INSERT INTO contribution_contracts (contract_address, pubkey_ed25519, pubkey_bls, sig_ed25519, operator_address, fee, status, total_contributions) VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT (contract_address) DO NOTHING - """, - (contract_address_bytes, service_node_pubkey, pubkey_bls, service_node_signature, operator, fee, status, - total_contributions) - ) - - for contributor in contributions: - stake_address = address_to_bytes(contributor.get('address')) - beneficiary_address = address_to_bytes(contributor.get('beneficiary')) - amount = contributor.get('amount') - - cursor.execute( - """ - INSERT INTO contribution_contracts_contributions (contract_address, address, beneficiary_address, amount) VALUES (?, ?, ?, ?) - """, - (contract_address_bytes, stake_address, beneficiary_address, amount) - ) - - sql.commit() - - perf_end = perf_counter() - perf_diff = perf_end - perf_start - - app.logger.info("{} Process new contribution contracts end, took: {}s".format(date_now_str(), perf_diff)) - - -# // Track the status of the multi-contribution contract. At any point in the -# // contract's lifetime, `reset` can be invoked to set the contract back to -# // `WaitForOperatorContrib`. -# enum Status { -# // Contract is initialised w/ no contributions. Call `contributeFunds` -# // to transition into `OpenForPublicContrib` -# WaitForOperatorContrib, # 0 -# -# // Contract has been initially funded by operator. Public and reserved -# // contributors can now call `contributeFunds`. When the contract is -# // collaterialised with exactly the staking requirement, the contract -# // transitions into `WaitForFinalized` state. -# OpenForPublicContrib, # 1 -# -# // Operator must invoke `finalizeNode` to transfer the tokens and the -# // node registration details to the `stakingRewardsContract` to -# // transition to `Finalized` state. -# WaitForFinalized, # 2 -# -# // Contract interactions are blocked until `reset` is called. -# Finalized # 3 -# } - -def parse_contributor_contract_status(status: int): - if status == 0 or status == 1: - return "awaiting_contributors" - elif status == 2: - return "finalizing" - elif status == 3: - return "finalized" - else: - raise ValueError(f"Invalid contributor contract status: {status}") - - -CONTRACT_STATUS_UPDATE_TIME = 30 - - -@timer(CONTRACT_STATUS_UPDATE_TIME) -def update_contract_details(signum): - app.logger.info("{} Update Contract Statuses Start".format(date_now_str())) - perf_start = perf_counter() - - addresses = [] - with app.app_context(), get_sql() as sql: - cursor = sql.cursor() - cursor.execute("SELECT contract_address FROM contribution_contracts") - for address, in cursor: - addresses.append(eth_format(address)) - - result = get_frequently_update_contract_details_loop.run_until_complete(update_contributor_contracts(addresses)) - - with app.app_context(), get_sql() as sql: - cursor = sql.cursor() - for details in result: - if details is None: - app.logger.warning("No details for contract") - continue - address, status, contributions, total_contributions = details - contract_address_bytes = address_to_bytes(address) - cursor.execute( - """ - UPDATE contribution_contracts SET status = ?, total_contributions=? WHERE contract_address = ? - """, - (status, total_contributions, contract_address_bytes) - ) - - for contributor in contributions: - stake_address = address_to_bytes(contributor.get('address')) - beneficiary_address = address_to_bytes(contributor.get('beneficiary')) - amount = contributor.get('amount') - - cursor.execute( - """ - INSERT OR REPLACE INTO contribution_contracts_contributions (contract_address, address, beneficiary_address, amount) VALUES (?, ?, ?, ?) - """, - (contract_address_bytes, stake_address, beneficiary_address, amount) - ) - - sql.commit() - - perf_end = perf_counter() - perf_diff = perf_end - perf_start - - if perf_diff > CONTRACT_STATUS_UPDATE_TIME: - app.logger.warning("{} Update Contract Statuses Finish, took: {} seconds".format(date_now_str(), perf_diff)) - else: - app.logger.info("{} Update Contract Statuses Finish, took: {} seconds".format(date_now_str(), perf_diff)) - - -def get_contribution_contracts(): - if time.time() < app.contracts_stale_timestamp and app.contracts is not None: - return app.contracts - - contracts = {} - with app.app_context(), get_sql() as sql: - cursor = sql.cursor() - cursor.execute( - "SELECT contract_address, pubkey_ed25519, pubkey_bls, sig_ed25519, operator_address, fee, status, total_contributions FROM contribution_contracts") - for contract_address, service_node_pubkey, pubkey_bls, service_node_signature, operator, fee, status, total_contributions in cursor: - contracts[contract_address] = { - "service_node_pubkey": service_node_pubkey, - "pubkey_bls": pubkey_bls, - "service_node_signature": service_node_signature, - "operator": operator, - "fee": fee, - "contract_state": parse_contributor_contract_status(status), - "total_contributions": total_contributions - } - - cursor.execute( - "SELECT contract_address, address, beneficiary_address, amount FROM contribution_contracts_contributions") - for contract_address, address, beneficiary_address, amount in cursor: - if contract_address not in contracts: - continue - contracts[contract_address].setdefault("contributors", []).append({ - "address": address, - "beneficiary": beneficiary_address, - "amount": amount - }) - - app.contracts = contracts - app.contracts_stale_timestamp = time.time() + config.backend.stale_time_seconds - return app.contracts - - -@timer(int(TARGET_BLOCK_TIME*3/4)) -def fetch_service_nodes(signum): - app.logger.info("{} Update SN Start".format(date_now_str())) - omq, oxend = omq_connection() - - # Generate new state - sn_info_list = get_sns_future(omq, oxend).get()["service_node_states"] - wallet_to_sn_map = {} - sn_map = {} - - if len(app.bls_pubkey_to_contract_id_map) == 0: - app.logger.warning("{} bls_pubkey_to_contract_id_map is empty, fetching contract ids".format(date_now_str())) - update_service_node_contract_ids(None) - - for sn_info in sn_info_list: - # Add the SN contract ID to the sn_info dict - pubkey_bls = sn_info.get('pubkey_bls') - if pubkey_bls is None: - app.logger.warning(f"pubkey_bls is None for sn_info SN: {sn_info}") - continue - contract_id = app.bls_pubkey_to_contract_id_map.get(pubkey_bls) - if contract_id is None: - app.logger.warning(f"Contract ID not found for sn_info SN with BLS pubkey: {pubkey_bls}") - continue - - sn_info["contract_id"] = contract_id - requested_unlock_height = sn_info.get('requested_unlock_height') - sn_info['requested_unlock_height'] = requested_unlock_height if requested_unlock_height != 0 else None - sn_map[contract_id] = sn_info - - contributors = {c["address"]: c["amount"] for c in sn_info["contributors"]} - # Creating (wallet -> [SN's the wallet owns]) table - for wallet_key in contributors.keys(): - # TODO: Validate we want to allow wallet_key to not go through eth_format if len == 40 - formatted_wallet_key = eth_format(wallet_key) if len(wallet_key) == 40 else wallet_key - - if formatted_wallet_key is None: - app.logger.warning(f"Wallet key is None for sn_info SN: {sn_info}") - continue - - wallet_to_sn_map.setdefault(formatted_wallet_key, []).append(contract_id) - - # Apply the new state if there are any - if len(sn_map) > 0: - app.logger.debug(f"Adding {len(sn_map)} service node info to the contract_id_to_sn_map") - app.contract_id_to_sn_map = sn_map - - app.logger.debug(f"Adding {len(wallet_to_sn_map)} wallet to service node map") - app.wallet_to_sn_map = wallet_to_sn_map - - # Get list of SNs that can be liquidated/exited - exit_liquidation_list_json = get_oxen_rpc_bls_exit_liquidation_list(omq, oxend).get() - - exitable_sns = {} - wallet_to_exitable_sn_map = {} - - if exit_liquidation_list_json is not None: - net_info = get_info() - net_type = net_info.get('nettype') - timers = get_timers_hours(net_type) - - for entry in exit_liquidation_list_json: - sn_info = entry.get('info') - - pubkey_bls = sn_info.get('bls_public_key') - if pubkey_bls is None: - app.logger.warning(f"bls_public_key is None for exit_liquidation_list_json SN: {sn_info}") - continue - - contract_id = app.bls_pubkey_to_contract_id_map.get(pubkey_bls) - if contract_id is None: - # If there is no contract ID it means this node has exited the smart contract and this event is being - # confirmed by oxend. This is the last state we'll get for this node from oxend. - # TODO: look at implementing some logic to add the node data to a dict that checks to make sure the db - # is properly updated with the final data we'll receive from oxend about this node. - app.logger.warning(f"Contract ID not found for exit_liquidation_list_json SN with BLS pubkey: {pubkey_bls}") - continue - - sn_info['pubkey_bls'] = pubkey_bls - sn_info['contract_id'] = contract_id - - for item in sn_info.get('contributors'): - if 'version' in item: - item.pop('version') - - exit_type = entry.get('type') - sn_info['exit_type'] = exit_type - sn_info['deregistration_unlock_height'] = entry.get('height') + blocks_in( - timers.get('unlock_duration_hours') * 3600) if exit_type == 'deregister' else None - - requested_unlock_height = sn_info.get('requested_unlock_height') - sn_info['requested_unlock_height'] = requested_unlock_height if requested_unlock_height != 0 else None - - sn_info['service_node_pubkey'] = entry.get('service_node_pubkey') - sn_info['liquidation_height'] = entry.get('liquidation_height') - exitable_sns[contract_id] = sn_info - - for contributor in sn_info.get('contributors'): - wallet_str = eth_format(contributor.get('address')) - - if wallet_str is None: - app.logger.warning(f"Wallet str is None for exit_liquidation_list_json SN: {sn_info}") - continue - - wallet_to_exitable_sn_map.setdefault(wallet_str, set()).add(contract_id) - - # Apply the new state if there are any - if len(exitable_sns) > 0: - app.logger.debug(f"Adding {len(exitable_sns)} exitable SN info to the contract_id_to_exitable_sn_map") - app.contract_id_to_exitable_sn_map = exitable_sns - - app.logger.debug(f"Adding {len(wallet_to_exitable_sn_map)} wallet to exitable SN map") - app.wallet_to_exitable_sn_map = wallet_to_exitable_sn_map - - # Get the accrued rewards values for each wallet - accrued_rewards_json = oxen_rpc_get_accrued_rewards(omq, oxend).get() - if accrued_rewards_json['status'] != 'OK': - app.logger.warning("{} Update SN early exit, accrued rewards request failed: {}".format( - date_now_str(), - accrued_rewards_json)) - return - - balances_key = 'balances' - if balances_key not in accrued_rewards_json: - app.logger.warning("{} Update SN early exit, accrued rewards request failed, 'balances' key was missing: {}".format( - date_now_str(), - accrued_rewards_json)) - return - - # Populate (Binary ETH wallet address -> accrued_rewards) table - for address_hex, rewards in accrued_rewards_json[balances_key].items(): - # Ignore non-ethereum addresses (e.g. left oxen rewards, not relevant) - trimmed_address_hex = address_hex[2:] if address_hex.startswith('0x') else address_hex - if len(trimmed_address_hex) != 40: - continue - - # Convert the address to bytes - address_key = bytes.fromhex(trimmed_address_hex) - - # Create the info for the wallet if it doesn't exist - if address_key not in app.wallet_map: - app.wallet_map[address_key] = WalletInfo() - - # We only update the rewards queried from the Oxen network - # Contract rewards are loaded on demand and cached. - # - # TODO It appears that doing the contract call is quite slow. - app.wallet_map[address_key].rewards = rewards - - app.logger.info("{} Update SN finished".format(date_now_str())) - - -@app.route("/info") -def network_info(): - """ - Do-nothing endpoint that can be called to get just the "network" and "t" values that are - included in every actual endpoint when you don't have any other endpoint to invoke. - """ - return json_response({}) - -def get_rewards_dict_for_wallet(eth_wal): - wallet_str = eth_format(eth_wal) - - # Convert the wallet string into bytes if it is a hex (eth address) - wallet_key = wallet_str - if eth_wal is not None: - trimmed_wallet_str = wallet_str[2:] if wallet_str.startswith('0x') else wallet_str - wallet_key = bytes.fromhex(str(trimmed_wallet_str)) - - # Retrieve the rewards earned by the wallet - result = app.wallet_map[wallet_key] if wallet_key in app.wallet_map else WalletInfo() - - # Query the amount of rewards committed/claimed currently on the contract - # - # NOTE: This is done on demand because it appears to be quite slow, - # iterating the list of wallets in one shot is quite expensive. The result - # is cached in the contract layer to avoid these expensive calls. - # - # This call is completely bypassed if the wallet is not in our wallet map - # which is populated from the Oxen rewards DB. The Oxen DB is the - # authoritative list and this prevents an actor from spamming random - # wallets to bloat out the python runtime memory usage. - if result.rewards > 0: - contract_recipient = app.service_node_rewards.recipients(wallet_key) - app.wallet_map[wallet_key].contract_rewards = contract_recipient.rewards - app.wallet_map[wallet_key].contract_claimed = contract_recipient.claimed - - return result - - -def generate_uuid(original_id): - return str(uuid.uuid5(uuid.NAMESPACE_DNS, original_id)) - - -class Contributor(TypedDict): - address: bytes - amount: int - reserved: int - -class Stake(TypedDict): - contract_id: int | None - contributors: list[Contributor] - deregistration_unlock_height: int | None - earned_downtime_blocks: int - last_reward_block_height: int | None - last_uptime_proof: int | None - operator_address: bytes - operator_fee: int | None - pubkey_bls: bytes - requested_unlock_height: int | None - service_node_pubkey: bytes - staked_balance: int | None - state: str - # Only on multi-contributor contracts - contract: str | None - -class ErrorResponse: - def __init__(self, message: str): - self.error = message - -def parse_stake_info( - stake: dict, - wallet_address: ChecksumAddress, - confirmed_exited: bool = False, -) -> Stake | ErrorResponse: - """ - Parses stake information and returns a standardised dictionary of stake info. - - Args: - stake (dict): The stake data containing various stake attributes. - wallet_address (str): The wallet address of the user. - confirmed_exited (bool, optional): Flag indicating if the stake has been confirmed as exited. Defaults to False. - - Exceptions: - ValueError: If the stake state cannot be determined. - - Returns: - dict: A dictionary containing the parsed stake information. - """ - state = None - deregistration_unlock_height = None - - try: - # Handles exit events - if 'exit_type' in stake: - exit_type = stake.get('exit_type') - if exit_type == 'exit': - state = ( - "Awaiting Exit" - if stake.get('contract_id') and not confirmed_exited - else "Exited" - ) - elif exit_type == 'deregister': - state = "Deregistered" - deregistration_unlock_height = stake.get('deregistration_unlock_height') - else: - raise ValueError(f"Invalid exit type {exit_type}") - # Handles contract events - elif 'contract_state' in stake: - contract_state = stake.get('contract_state') - if contract_state == 'awaiting_contributors': - state = "Awaiting Contributors" - elif contract_state == 'cancelled': - state = "Cancelled" - elif contract_state == 'finalized': - raise ValueError("Finalized nodes must be filtered out before reaching this point") - else: - raise ValueError(f"Invalid contract state {contract_state}") - # Handles running node info - elif 'active' in stake and 'funded' in stake: - state = ( - 'Decommissioned' - if not stake.get("active") and stake.get("funded") - else 'Running' - ) - elif 'state' in stake: - current_state = stake.get('state') - if current_state == 'Deregistered': - deregistration_unlock_height = stake.get('deregistration_unlock_height') - if confirmed_exited and current_state == 'Awaiting Exit': - state = 'Exited' - else: - state = current_state - else: - raise ValueError("Unable to determine node state") - except ValueError as e: - base_msg = f"Value Error while parsing stake state for stake: \n {stake}" - app.logger.error(f"{base_msg} \n Exception: {e}") - return ErrorResponse(base_msg) - except Exception as e: - base_msg = f"Exception while parsing stake state for stake: \n {stake}" - app.logger.error(f"{base_msg} \n Exception: {e}") - return ErrorResponse(base_msg) - - # Process contributors and calculate staked balance - contributors = stake.get('contributors', []) - staked_balance = sum( - contributor.get('amount') - for contributor in contributors - if eth_format(contributor.get('address')) == wallet_address - ) or None - - # `stake-${stake.contract_id}-${stake.service_node_pubkey}-${stake.last_uptime_proof}`; - contract_id = stake.get('contract_id') - contract = stake.get('contract') - contract_formatted = eth_format(contract) if contract is not None else None - pubkey_bls = stake.get('pubkey_bls') - - # TODO: Investigate the best data to use for id generation. these ids MUST be unique for each stake. - # - # A pubkey_bls can have multiple stakes over time, but a pubkey_bls and (contract_id or contract) can only have - # one stake ever. The pubkey_bls needs to be used as its possible for a stake to not have a contract_id or - # contract when it exits - unique_id = generate_uuid("{}-{}".format(pubkey_bls, contract_id if contract_formatted is None else contract_formatted)) - - return { - 'unique_id': unique_id, - 'contract_id': contract_id, - 'contract': contract_formatted, - 'contributors': contributors, - 'deregistration_unlock_height': deregistration_unlock_height, - 'earned_downtime_blocks': stake.get('earned_downtime_blocks'), - 'last_reward_block_height': stake.get('last_reward_block_height'), - 'last_uptime_proof': stake.get('last_uptime_proof'), - 'liquidation_height': stake.get('liquidation_height'), - 'operator_address': stake.get('operator_address'), - 'operator_fee': stake.get('operator_fee'), - 'pubkey_bls': pubkey_bls, - 'requested_unlock_height': stake.get('requested_unlock_height'), - 'service_node_pubkey': stake.get('service_node_pubkey'), - 'staked_balance': staked_balance, - 'state': state, - 'exited': confirmed_exited or state == 'Exited', - } - - -@app.route("/stakes/") -def get_stakes(eth_wal: str): - try: - if not eth_wal or not eth_utils.is_address(eth_wal): - raise ValueError("Invalid wallet address") - - wallet_address = eth_format(eth_wal) - app.tracked_wallet_addresses.add(wallet_address) - - # A contract id can only appear once across the lists - added_contract_ids = set() - - parse_errors = [] - - app.logger.debug(f"Fetching stakes for {wallet_address}") - app.logger.debug(f"wallet_to_sn_map len: {len(app.wallet_to_sn_map)}") - app.logger.debug(f"contract_id_to_sn_map len: {len(app.contract_id_to_sn_map)}") - app.logger.debug(f"wallet_to_exitable_sn_map len: {len(app.wallet_to_exitable_sn_map)}") - app.logger.debug(f"contract_id_to_exitable_sn_map len: {len(app.contract_id_to_exitable_sn_map)}") - - def handle_stakes( - address_to_stakes_map: dict[ChecksumAddress, set[int]], - contract_id_to_stake_map: dict[int, Stake], - output_list: list[Stake], - confirmed_exited=False, - ): - app.logger.debug(f"added_contract_ids: {added_contract_ids}") - for contract_id in address_to_stakes_map.get(wallet_address, []): - app.logger.debug(f"contract_id: {contract_id}") - if contract_id not in added_contract_ids: - stake = contract_id_to_stake_map.get(contract_id) - parsed_stake = parse_stake_info(stake, wallet_address, confirmed_exited) - if isinstance(parsed_stake, ErrorResponse): - parse_errors.append({ - 'contract_id': contract_id, - 'error': parsed_stake.error - }) - else: - output_list.append(parsed_stake) - added_contract_ids.add(contract_id) - - stakes = [] - handle_stakes(app.wallet_to_exitable_sn_map, app.contract_id_to_exitable_sn_map, stakes) - handle_stakes(app.wallet_to_sn_map, app.contract_id_to_sn_map, stakes) - - if wallet_address not in app.wallet_to_historical_stakes_map: - # NOTE: This db call is only triggered once per wallet address, this is reset after the scheduled db read. - get_db_stakes_for_wallet(wallet_address) - - historical_stakes = [] - handle_stakes(app.wallet_to_historical_stakes_map, app.contract_id_to_historical_stakes_map, historical_stakes, - confirmed_exited=True) - - contracts = [ - { - "contract": addr, - **details - } - for addr, details in get_contribution_contracts().items() - if details.get('contract_state') == 'awaiting_contributors' and details.get('operator') == wallet_address - ] - - if len(app.bls_pubkey_to_contract_id_map) == 0: - app.logger.warning("{} bls_pubkey_to_contract_id_map is empty, fetching contract ids".format(date_now_str())) - update_service_node_contract_ids(None) - - - for contract in contracts: - pubkey_bls = contract.get('pubkey_bls') - if pubkey_bls is None: - app.logger.warning(f"pubkey_bls is None for contract: {contract}") - continue - contract_id = app.bls_pubkey_to_contract_id_map.get(pubkey_bls) - contract['contract_id'] = contract_id - contract['operator_fee'] = contract.get('fee') - contract['operator_address'] = contract.get('operator') - - stakes.append(parse_stake_info(contract, wallet_address)) - - return json_response({ - "contracts": contracts, - "historical_stakes": historical_stakes, - "stakes": stakes, - "wallet": vars(get_rewards_dict_for_wallet(wallet_address)), - "error_stakes": parse_errors if len(parse_errors) > 0 else None - }) - except ValueError as e: - app.logger.error(f"Exception: {e}") - return flask.abort(400, e) - except Exception as e: - app.logger.error(f"Exception: {e}") - return flask.abort(500, e) - - -@app.route("/nodes") -def get_nodes(): - """ - Returns a list of all nodes that are running. - """ - nodes = [] - for node in app.contract_id_to_sn_map.values(): - nodes.append(parse_stake_info(node, node['operator_address'])) - return json_response({"nodes": nodes}) - - -# export enum NODE_STATE { -# RUNNING = 'Running', -# AWAITING_CONTRIBUTORS = 'Awaiting Contributors', -# CANCELLED = 'Cancelled', -# DECOMMISSIONED = 'Decommissioned', -# DEREGISTERED = 'Deregistered', -# AWAITING_EXIT = 'Awaiting Exit', -# EXITED = 'Exited', -# } -@app.route("/nodes/") -@app.route("/nodes/") -def get_nodes_for_wallet(oxen_wal=None, eth_wal=None): - assert oxen_wal is not None or eth_wal is not None - wallet_str = eth_format(eth_wal) if eth_wal is not None else oxen_wal - - sns = [] - nodes = [] - for sn_index in app.wallet_to_sn_map.get(wallet_str, []): - sn_info = app.contract_id_to_sn_map[sn_index] - sns.append(sn_info) - balance = {c["address"]: c["amount"] for c in sn_info["contributors"]}.get(wallet_str, 0) - state = 'Decommissioned' if not sn_info["active"] and sn_info["funded"] else 'Running' - nodes.append({ - 'balance': balance, - 'contributors': sn_info["contributors"], - 'last_uptime_proof': sn_info["last_uptime_proof"], - 'contract_id': sn_info["contract_id"], - 'operator_address': sn_info["operator_address"], - 'operator_fee': sn_info["operator_fee"], - 'requested_unlock_height': sn_info["requested_unlock_height"], - 'last_reward_block_height':sn_info["last_reward_block_height"], - 'service_node_pubkey': sn_info["service_node_pubkey"], - 'pubkey_bls': sn_info["pubkey_bls"], - 'decomm_blocks_remaining': max(sn_info["earned_downtime_blocks"], 0), - 'state': state, - }) - - contracts = [] - if wallet_str in app.contributors: - for address in app.contributors[wallet_str]: - details = app.contracts[address] - contracts.append({ - 'contract_address': address, - 'details': details - }) - - # Setup the result - result = json_response({ - "wallet": vars(get_rewards_dict_for_wallet(wallet_str)), - "service_nodes": sns, - "contracts": contracts, - "nodes": nodes, - }) - - return result - -@app.route("/nodes/open") -def get_contributable_contracts(): - nodes = get_contribution_contracts() - return json_response({ - "nodes": [ - { - "contract": addr, - **details - } - for addr, details in nodes.items() - if details.get('contract_state') == 'awaiting_contributors' - ] - }) - -@app.route("/rewards/", methods=["GET", "POST"]) -def get_rewards(eth_wal: str): - if flask.request.method == "GET": - result = json_response({ - "wallet": vars(get_rewards_dict_for_wallet(eth_wal)), - }) - return result - - if flask.request.method == "POST": - omq, oxend = omq_connection() - try: - response = oxen_rpc_bls_rewards_request(omq, oxend, eth_format(eth_wal)).get() - if response is None: - return flask.abort(504) # Gateway timeout - if 'status' in response: - response.pop('status') - if 'address' in response: - response.pop('address') - result = json_response({ - 'result': response - }) - return result - except TimeoutError: - return flask.abort(408) # Request timeout - - return flask.abort(405) # Method not allowed - -@app.route("/exit/") -def get_exit(ed25519_pubkey: bytes): - omq, oxend = omq_connection() - try: - response = oxen_rpc_bls_exit_liquidation(omq, oxend, ed25519_pubkey, liquidate=False).get() - if response is None: - return flask.abort(504) # Gateway timeout - if 'status' in response: - response.pop('status') - result = json_response({ - 'result': response - }) - return result - except TimeoutError: - return flask.abort(408) # Request timeout - -@app.route("/exit_liquidation_list") -def get_exit_liquidation_list(): - try: - array = [] - for item in app.contract_id_to_exitable_sn_map.values(): - array.append(item) - - result = json_response({ - 'result': array - }) - return result - except TimeoutError: - return flask.abort(408) # Request timeout - -@app.route("/liquidation/") -def get_liquidation(ed25519_pubkey: bytes): - omq, oxend = omq_connection() - try: - response = oxen_rpc_bls_exit_liquidation(omq, oxend, ed25519_pubkey, liquidate=True).get() - if response is None: - return flask.abort(504) # Gateway timeout - if 'status' in response: - response.pop('status') - result = json_response({ - 'result': response - }) - return result - except TimeoutError: - return flask.abort(408) # Request timeout - - -def handle_stakes_row( - wallet_to_historical_stakes_map: dict[ChecksumAddress, set[int]], - contract_id_to_historical_stakes_map: dict[int, Stake], - sql_cur: sqlite3.Cursor, -): - for row in sql_cur: - ( - contract_id, - last_updated, - pubkey_bls, - deregistration_unlock_height, - earned_downtime_blocks, - last_reward_block_height, - last_uptime_proof, - operator_address, - operator_fee, - requested_unlock_height, - service_node_pubkey, - state, - contributor_address, - contributor_amount, - ) = row - - - contributor_adr = eth_format(contributor_address) - - if contract_id not in contract_id_to_historical_stakes_map: - stake = { - 'pubkey_bls': pubkey_bls, - 'contract_id': contract_id, - 'deregistration_unlock_height': deregistration_unlock_height, - 'earned_downtime_blocks': earned_downtime_blocks, - 'last_reward_block_height': last_reward_block_height, - 'last_uptime_proof': last_uptime_proof, - 'operator_address': eth_format(operator_address), - 'operator_fee': operator_fee, - 'requested_unlock_height': requested_unlock_height, - 'service_node_pubkey': service_node_pubkey, - 'state': state, - 'contributors': [], - } - contract_id_to_historical_stakes_map[contract_id] = stake - else: - stake = contract_id_to_historical_stakes_map[contract_id] - - # Add contributor info - contributor = { - 'address': contributor_adr, - 'amount': contributor_amount, - } - stake['contributors'].append(contributor) - wallet_to_historical_stakes_map.setdefault(contributor_adr, set()).add(contract_id) - - -def get_db_stakes_for_wallet(wallet_address: ChecksumAddress): - """ - This exists to get the stakes for a wallet that is not in the tracked list. This should only be used when we need - the data but the timed database read hasn't executed yet with the address in the tracked list. A wallet address - can only call this once in between scheduled database read times. - """ - app.logger.debug("{} get_db_stakes_for_wallet: {}".format(date_now_str(), wallet_address)) - if wallet_address in app.tmp_db_trigger_wallet_addresses: - return - - app.tmp_db_trigger_wallet_addresses.add(wallet_address) - - with app.app_context(), get_sql() as sql: - cur = sql.cursor() - cur.execute( - """ - SELECT s.*, - sc_contributors.address AS contributor_address, - sc_contributors.amount AS contributor_amount - FROM stakes s - JOIN stake_contributions sc_requestor ON sc_requestor.contract_id = s.id - JOIN stake_contributions sc_contributors ON sc_contributors.contract_id = s.id - WHERE sc_requestor.address = ?; - """, - (address_to_bytes(wallet_address),), - ) - - handle_stakes_row(app.wallet_to_historical_stakes_map, app.contract_id_to_historical_stakes_map, cur) - - -def address_to_bytes(address: str) -> bytes: - if address.startswith("0x"): - return bytes.fromhex(address[2:]) - else: - return bytes.fromhex(address) - - -def chunks(lst, n): - """Yield successive n-sized chunks from lst.""" - for i in range(0, len(lst), n): - yield lst[i:i + n] - - -@timer(15) -def get_db_stakes(signum): - app.logger.info("{} Get stakes db start".format(date_now_str())) - wallet_to_historical_stakes_map: dict[ChecksumAddress, set[int]] = {} # (Wallet address -> Set of contract_ids) - contract_id_to_historical_stakes_map: dict[int, Stake] = {} # (contract_ids -> Stake Info) - - for address_chunk in chunks(list(app.tracked_wallet_addresses), 999): # SQLite default parameter limit is 999 - placeholders = ','.join(['?'] * len(address_chunk)) - with app.app_context(), get_sql() as sql: - cur = sql.cursor() - cur.execute(f""" - SELECT DISTINCT s.*, - sc_contributors.address AS contributor_address, - sc_contributors.amount AS contributor_amount - FROM stakes s - JOIN stake_contributions sc_requestor ON sc_requestor.contract_id = s.id - JOIN stake_contributions sc_contributors ON sc_contributors.contract_id = s.id - WHERE sc_requestor.address IN ({placeholders}); - """, - address_chunk, - ) - - handle_stakes_row(wallet_to_historical_stakes_map, contract_id_to_historical_stakes_map, cur) - - if len(wallet_to_historical_stakes_map) > 0: - app.wallet_to_historical_stakes_map = wallet_to_historical_stakes_map - app.contract_id_to_historical_stakes_map = contract_id_to_historical_stakes_map - - app.tmp_db_trigger_wallet_addresses.clear() - - app.logger.info("{} Get stakes db finish".format(date_now_str())) - - -# Debug function to load all contributor addresses into the tracked wallet addresses set -def load_contributor_addresses_into_tracked_wallet_addresses(): - with app.app_context(), get_sql() as sql: - cur = sql.cursor() - cur.execute("SELECT DISTINCT address FROM stake_contributions") - for row in cur: - app.tracked_wallet_addresses.add(row[0]) - -# Debug function to recover non-contributor stakes. This gets all stakes that are not in the stake_contributions table and adds them to the stake_contributions using the operator address as the contributor address. -def recover_non_contributor_stakes(): - with app.app_context(), get_sql() as sql: - cur = sql.cursor() - added_stakes = set() - cur.execute("SELECT DISTINCT contract_id, address FROM stake_contributions") - for contract_id, address in cur: - added_stakes.add((contract_id, address)) - - cur.execute("SELECT DISTINCT id, operator_address FROM stakes") - stakes_to_recover = set() - for contract_id, operator_address in cur: - if (contract_id, operator_address) in added_stakes: - continue - stakes_to_recover.add((contract_id, operator_address)) - - for contract_id, operator_address in stakes_to_recover: - app.logger.debug(f"Recovering stake for contract_id: {contract_id}, operator_address: {operator_address}") - cur.execute( - """ - INSERT OR REPLACE INTO stake_contributions (contract_id, address, amount) - VALUES (?, ?, ?) - """, - ( - contract_id, - operator_address, - 20000000000000, - ) - ) - -@timer(30) -def update_service_node_contract_ids(signum) -> None: - """ - Update the map of service node contract ids to BLS public keys. This fetches the list of all service nodes from the - Service Node Rewards contract and maps them to their corresponding contract ids. - """ - app.logger.info("{} Updating service node contract ids".format(date_now_str())) - [ids, bls_keys] = app.service_node_rewards.allServiceNodeIDs() - app.logger.debug(f"Added {len(ids)} service node contract ids") - app.bls_pubkey_to_contract_id_map = {f"{x:064x}{y:064x}": contract_id for contract_id, (x, y) in zip(ids, bls_keys)} - app.logger.info("{} Updating service node contract ids finish. Nodes: {}".format(date_now_str(), - len(app.bls_pubkey_to_contract_id_map))) - - -@timer(60) -def insert_updated_db_stakes(signum): - """ - Inserts or updates the stakes in the database. - """ - app.logger.info("{} Insert or update stakes db start".format(date_now_str())) - added_contract_ids = set() - with app.app_context(), get_sql() as sql: - cur = sql.cursor() - for node in chain(app.contract_id_to_exitable_sn_map.values(), app.contract_id_to_sn_map.values()): - stake = parse_stake_info(node, node.get('operator_address')) - contract_id = stake.get('contract_id') - if contract_id in added_contract_ids: - continue - added_contract_ids.add(contract_id) - cur.execute( - """ - INSERT OR REPLACE INTO stakes (id, last_updated, pubkey_bls, deregistration_unlock_height, earned_downtime_blocks, last_reward_block_height, last_uptime_proof, operator_address, operator_fee, requested_unlock_height, service_node_pubkey, state) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - contract_id, - datetime.datetime.now().timestamp(), - stake['pubkey_bls'], - stake['deregistration_unlock_height'], - stake['earned_downtime_blocks'], - stake['last_reward_block_height'], - stake['last_uptime_proof'], - address_to_bytes(stake['operator_address']), - stake['operator_fee'], - stake['requested_unlock_height'], - stake['service_node_pubkey'], - stake['state'], - ) - ) - - # Create the stake contributions entry - for contributor in stake['contributors']: - cur.execute( - """ - INSERT OR REPLACE INTO stake_contributions (contract_id, address, amount) - VALUES (?, ?, ?) - """, - ( - contract_id, - address_to_bytes(contributor['address']), - contributor.get('amount'), - ) - ) - added_contract_ids.clear() - app.logger.info("{} Insert or update stakes db finish".format(date_now_str())) - -# Decodes `x` into a bytes of length `length`. `x` should be hex or base64 encoded, without -# whitespace. Both regular and "URL-safe" base64 are accepted. Padding is optional for base64 -# values. Throws ParseError if the input is invalid or of the wrong size. `length` must be at -# least 5 (smaller byte values are harder or even ambiguous to distinguish between hex and base64). -def decode_bytes(k, x, length): - assert length >= 5 - - hex_len = length * 2 - b64_unpadded = (length * 4 + 2) // 3 - b64_padded = (length + 2) // 3 * 4 - - app.logger.debug(f"{len(x)}, {hex_len}") - if len(x) == hex_len and all(c in string.hexdigits for c in x): - return bytes.fromhex(x) - if len(x) in (b64_unpadded, b64_padded): - if oxenc.is_base64(x): - return oxenc.from_base64(x) - if "-" in x or "_" in x: # Looks like (maybe) url-safe b64 - x = x.replace("/", "_").replace("+", "-") - if oxenc.is_base64(x): - return oxenc.from_base64(x) - raise ParseError(k, f"expected {hex_len} hex or {b64_unpadded} base64 characters") - - -def byte_decoder(length: int): - return partial(decode_bytes, length=length) - - -# Takes a positive integer value required to be between irange[0] and irange[1], inclusive. The -# integer may not be 0-prefixed or whitespace padded. -def parse_int_field(k, v, irange): - if ( - len(v) == 0 - or not all(c in "0123456789" for c in v) - or (len(v) > 1 and v[0] == "0") - ): - raise ParseError(k, "an integer value is required") - v = int(v) - imin, imax = irange - if imin <= v <= imax: - return v - raise ParseError(k, f"expected an integer between {imin} and {imax}") - - -def raw_eth_addr(k, v): - if re.fullmatch(eth_regex, v): - if not eth_utils.is_address(v): - raise ParseError(k, "ETH address checksum failed") - return bytes.fromhex(v[2:]) - raise ParseError(k, "not an ETH address") - - -def eth_format(addr: Union[bytes, str]) -> ChecksumAddress: - try: - return eth_utils.to_checksum_address(addr) - except ValueError: - raise ParseError(addr, "Invalid ETH address") - - -class SNSignatureValidationError(ValueError): - pass - - -def check_reg_keys_sigs(params): - if len( - params["pubkey_ed25519"] - ) != 32 or not sodium.crypto_core_ed25519_is_valid_point(params["pubkey_ed25519"]): - raise SNSignatureValidationError("Ed25519 pubkey is invalid") - if len(params["pubkey_bls"]) != 64: # FIXME: bls pubkey validation? - raise SNSignatureValidationError("BLS pubkey is invalid") - if len(params["operator"]) != 20: - raise SNSignatureValidationError("operator address is invalid") - contract = params.get("contract") - if contract is not None and len(contract) != 20: - raise SNSignatureValidationError("contract address is invalid") - - signed = ( - params["pubkey_ed25519"] - + params["pubkey_bls"] - ) - - try: - VerifyKey(params["pubkey_ed25519"]).verify(signed, params["sig_ed25519"]) - except nacl.exceptions.BadSignatureError: - raise SNSignatureValidationError("Ed25519 signature is invalid") - - # FIXME: BLS verification of pubkey_bls on signed - if False: - raise SNSignatureValidationError("BLS signature is invalid") - - -class ParseError(ValueError): - def __init__(self, field, reason): - self.field = field - super().__init__(f"{field}: {reason}") - - -class ParseMissingError(ParseError): - def __init__(self, field): - super().__init__(field, f"required parameter is missing") - - -class ParseUnknownError(ParseError): - def __init__(self, field): - super().__init__(field, f"unknown parameter") - - -class ParseMultipleError(ParseError): - def __init__(self, field): - super().__init__(field, f"cannot be specified multiple times") - - -def parse_query_params(params: dict[str, Callable[[str, str], Any]]): - """ - Takes a dict of fields and callables such as: - - { - "field": ("out", callable), - ... - } - - where: - - `"field"` is the expected query string name - - `callable` will be invoked as `callable("field", value)` to determined the returned value. - - On error, throws a ParseError with `.field` set to the "field" name that triggered the error. - - Notes: - - callable should throw a ParseError for an unaccept input value. - - if "-field" starts with "-" then the field is optional; otherwise it is an error if not - provided. The "-" is not included in the returned key. - - if "field" ends with "[]" then the value will be an array of values returned by the callable, - and the parameter can be specified multiple times. Otherwise a value can be specified only - once. The "[]" is not included in the returned key. - - you can do both of the above: "-field[]" will allow the value to be provided zero or more - times; the value will be omitted if not present in the input, and an array (under the "field") - key if provided at least once. - """ - - parsed = {} - - param_map = { - k.removeprefix("-").removesuffix("[]"): ( - k.startswith("-"), - k.endswith("[]"), - cb, - ) - for k, cb in params.items() - } - - for k, v in flask.request.values.items(multi=True): - found = param_map.get(k) - if found is None: - raise ParseUnknownError(k) - - _, multi, callback = found - - if multi: - parsed.setdefault(k, []).append(callback(k, v) if callback else v) - elif k not in parsed: - parsed[k] = callback(k, v) if callback else v - else: - raise ParseMultipleError(k) - - for k, p in param_map.items(): - optional = p[0] - if not optional and k not in flask.request.values: - raise ParseMissingError(k) - - return parsed - - -@app.route("/store/", methods=["GET", "POST"]) -def store_registration(sn_pubkey: bytes): - """ - Stores (or replaces) the pubkeys/signatures associated with a service node that are needed to - call the smart contract to create a SN registration. These pubkeys/signatures are stored - indefinitely, allowing the operator to call them up whenever they like to re-submit a - registration for the same node. There is nothing confidential here: the values will be publicly - broadcast as part of the registration process already, and are constructed in such a way that - only the operator wallet can submit a registration using them. - - This works for both solo registrations and multi-registrations: for the latter, a contract - address is passed in the "c" parameter. If omitted, the details are stored for a solo - registration. (One of each may be stored at a time for each pubkey). - - The distinction at the SN layer is that contract registrations sign the contract address while - solo registrations sign the operator address. For submission to the blockchain, a contract - stake requires an additional interaction through a multi-contributor contract while solo - registrations can call the staking contract directly. - """ - - try: - params = parse_query_params( - { - "pubkey_bls": byte_decoder(64), - "sig_ed25519": byte_decoder(64), - "sig_bls": byte_decoder(128), - "-contract": raw_eth_addr, - "operator": raw_eth_addr, - } - ) - - params["pubkey_ed25519"] = sn_pubkey - - check_reg_keys_sigs(params) - except ValueError as e: - raise e - return json_response({"error": f"Invalid registration: {e}"}) - - with get_sql() as sql: - cur = sql.cursor() - cur.execute( - """ - INSERT OR REPLACE INTO registrations (pubkey_ed25519, pubkey_bls, sig_ed25519, sig_bls, operator, contract) - VALUES (?, ?, ?, ?, ?, ?) - """, - ( - sn_pubkey, - params["pubkey_bls"], - params["sig_ed25519"], - params["sig_bls"], - params["operator"], - params.get("contract"), - ), - ) - - params["operator"] = eth_utils.to_checksum_address(params["operator"]) - if "contract" in params: - params["contract"] = eth_utils.to_checksum_address(params["contract"]) - params["type"] = "contract" - else: - params["type"] = "solo" - - return json_response({"success": True, "registration": params}) - - -@app.route("/registrations/") -def sn_pubkey_registrations(sn_pubkey: bytes) -> flask.Response: - """ - Retrieves stored registration(s) for the given service node pubkey. - - This returns an array in the "registrations" field containing either one or two registration - info dicts: a solo registration (if known) and a multi-contributor contract registration (if - known). These are sorted by timestamp of when the registration was last received/updated. - - Fields in each dict: - - "type": either "solo" or "contract" - - "operator": the operator address; for "type": "contract" this is merely informative, for - "type": "solo" this is a signed part of the registration. - - "contract": the contract address, for "type": "contract" and omitted for "type": "solo". - - "pubkey_ed25519": the primary SN pubkey, in hex. - - "pubkey_bls": the SN BLS pubkey, in hex. - - "sig_ed25519": the SN pubkey signed registration signature. - - "sig_bls": the SN BLS pubkey signed registration signature. - - "timestamp": the unix timestamp when this registration was received (or last updated) - - Returns the JSON response with the 'registrations' for the given 'sn_pubkey'. - """ - - reg_array = [] - with get_sql() as sql: - cur = sql.cursor() - cur.execute( - """ - SELECT pubkey_bls, sig_ed25519, sig_bls, operator, contract, timestamp - FROM registrations - WHERE pubkey_ed25519 = ? - ORDER BY timestamp DESC - """, - (sn_pubkey,), - ) - - for pubkey_bls, sig_ed25519, sig_bls, operator, contract, timestamp in cur: - reg_array.append({ - "type": "solo" if contract is None else "contract", - "pubkey_ed25519": sn_pubkey, - "pubkey_bls": pubkey_bls, - "sig_ed25519": sig_ed25519, - "sig_bls": sig_bls, - "operator": operator, - "timestamp": timestamp, - "contract": "" if contract is None else contract, - }) - - result = json_response({"registrations": reg_array}) - return result - -@app.route("/registrations/") -def operator_registrations(operator: str): - """ - Retrieves stored registration(s) for the given 'operator'. - - This returns an array in the "registrations" field containing as many registrations as are - current stored for the given operator wallet, sorted from most to least recently submitted. - - Fields are the same as the version of this endpoint that takes a SN pubkey. - - Returns the JSON response with the 'registrations' for the given 'operator'. - """ - - reg_array = [] - operator_bytes = bytes.fromhex(operator[2:]) - - with get_sql() as sql: - cur = sql.cursor() - cur.execute( - """ - SELECT pubkey_ed25519, pubkey_bls, sig_ed25519, sig_bls, contract, timestamp - FROM registrations - WHERE operator = ? - ORDER BY timestamp DESC - """, - (operator_bytes,), - ) - for pubkey_ed25519, pubkey_bls, sig_ed25519, sig_bls, contract, timestamp in cur: - reg_array.append({ - "type": "solo" if contract is None else "contract", - "pubkey_ed25519": pubkey_ed25519, - "pubkey_bls": pubkey_bls, - "sig_ed25519": sig_ed25519, - "sig_bls": sig_bls, - "operator": operator, - "timestamp": timestamp, - "contract": "" if contract is None else contract, - }) - - result = json_response({'registrations': reg_array}) - return result - - -def check_stakes(stakes, total, stakers, max_stakers): - if len(stakers) != len(stakes): - raise ValueError(f"s and S have different lengths") - if len(stakers) < 1: - raise ValueError(f"at least one s/S value pair is required") - if len(stakers) > max_stakers: - raise ValueError(f"too many stakers ({len(stakers)} > {max_stakers})") - if sum(stakes) > total: - raise ValueError(f"total stake is too large ({sum(stakes)} > total)") - if len(set(stakers)) != len(stakers): - raise ValueError(f"duplicate staking addresses in staker list") - - remaining_stake = total - remaining_spots = max_stakers - - for i in range(len(stakes)): - reqd = remaining_stake // (4 if i == 0 else remaining_spots) - if stakes[i] < reqd: - raise ValueError( - "reserved stake [i] ({stakers[i]}) is too low ({stakes[i]} < {reqd})" - ) - remaining_stake -= stakes[i] - remaining_spots -= 1 - -def format_currency(units: int, decimal: int = 9): - """ - Formats an atomic currency unit to `decimal` decimal places. The conversion is lossless (i.e. - it does not use floating point math or involve any truncation or rounding - """ - base = 10**decimal - app.logger.debug(f"units: {units}, base: {base}, decimal: {decimal}, {units//base}") - frac = units % base - frac = "" if frac == 0 else f".{frac:0{decimal}d}".rstrip("0") - return f"{units // base}{frac}" - - -def parse_currency(k, val: str, decimal: int = 9): - """ - Losslessly parses a currency value such as 1.23 into an atomic integer value such as 1000000023. - """ - pieces = val.split(".") - if len(pieces) > 2 or not all(re.fullmatch(r"\d+", p) for p in pieces): - raise ParseError(k, "Invalid currency amount") - whole = int(pieces[0]) - if len(pieces) > 1: - frac = pieces[1] - if len(frac) > decimal: - frac = frac[0:decimal] - elif len(frac) < decimal: - frac = frac.ljust(decimal, "0") - frac = int(frac) - else: - frac = 0 - - return whole * 10**decimal + frac - - -def error_response(code, **err): - """ - Error codes that can be returned to a client when validating registration details. The `code` - is a short string that uniquely defines the error; some errors have extra parameters (passed - into the `err` kwargs). This method formats the error, then returns a dict such as: - - { "code": "short_code", "error": "English string", **err } - - This is returned, typically as an "error" key, by various endpoints. - - As a special value, if a `detail` key is present in err then the usual error will have ": - {detail}" appended to it (the detail will also be passed along separately). - """ - - err["code"] = code - match code: - case "bad_request": - msg = "Invalid request parameters" - case "invalid_op_addr": - msg = "Invalid operator address" - case "invalid_op_stake": - msg = "Invalid/unparseable operator stake" - case "wrong_op_stake": - # For a solo node that doesn't contribute the exact requirement - msg = f"Invalid operator stake: exactly {format_currency(err['required'])} {TOKEN_NAME} is required for a solo node" - case "insufficient_op_stake": - network_info = get_info() - msg = f"Insufficient operator stake: at least {format_currency(err['minimum'])} ({err['minimum'] / network_info['staking_requirement'] * 100}%) is required" - case "invalid_contract_addr": - msg = "Invalid contract address" - case "invalid_res_addr": - msg = f"Invalid reserved contributor address {err['index']}: {err['address']}" - case "invalid_res_stake": - msg = f"Invalid/unparseable reserved contributor amount for contributor {err['index']} ({err['address']})" - case "insufficient_res_stake": - msg = f"Insufficient reserved contributor stake: contributor {err['index']} ({err['address']}) must contribute at least {format_currency(err['minimum'])}" - case "too_much": - # for multi-contributor (solo node would get wrong_op_stake instead) - msg = f"Total node reserved contributions are too large: {format_currency(err['total'])} exceeds the maximum stake {format_currency(err['maximum'])}" - case "too_many": - msg = f"Too many reserved contributors: only {err['max_contributors']} contributor spots are possible" - case "invalid_fee": - msg = "Invalid fee" - case "signature": - msg = "Invalid service node registration pubkeys/signatures" - case _: - msg = None - - err["error"] = f"{msg}: {err['detail']}" if "detail" in err else msg - - return json_response({"error": err}) - - -@app.route("/validate") -def validate_registration(): - """ - Validates a registration including fee, stakes, and reserved spot requirements. This does not - use stored registration info at all; all information has to be submitted as part of the request. - The data is not stored. - - Parameters for both types of stakes: - - "pubkey_ed25519" - - "pubkey_bls" - - "sig_ed25519" - - "sig_bls" - The above are as provided by oxend for the registration. Can be hex or base64. - - - "operator" -- the operator wallet address - - "stake" -- the amount the operator will stake. For a solo stake, this must be exactly equal - to the staking requirement, but for a multi-contribution node it can be less. - - For a multi-contribution node the following must additionally be passed: - - "contract" -- the ETH address of the multi-contribution staking contract for this node. - - "reserved" -- optional list of reserved contributor wallets. - - "res_stake" -- list of reserved contributor stakes. This must be the same length and order as - `"reserved"`. - - Various checks are performed to look for registration errors; if no errors are found then the - result contains the key "success": true. Otherwise the key "error" will be set to an error dict - indicating the error that was detected. See `error_response` for details. - """ - - network_info = get_info() - max_stake = network_info['staking_requirement'] - min_operator_stake = network_info['min_operator_stake'] - max_stakers = network_info['max_stakers'] - - try: - params = parse_query_params( - { - "pubkey_ed25519": byte_decoder(32), - "pubkey_bls": byte_decoder(64), - "sig_ed25519": byte_decoder(64), - "sig_bls": byte_decoder(128), - "-contract": raw_eth_addr, - "operator": raw_eth_addr, - "stake": parse_currency, - "-res_addr[]": None, - "-res_stake[]": None, - "-fee": None, - } - ) - except (ParseMissingError, ParseUnknownError, ParseMultipleError) as e: - return error_response("bad_request", field=e.field, detail=str(e)) - except ParseError as e: - code = None - match e.field: - case f if f.startswith("pubkey_") or f.startswith("sig_"): - return error_response("signature", field=f, detail=str(e)) - case "operator": - return error_response("invalid_op_addr", detail=str(e)) - case "stake": - return error_response("invalid_op_stake") - case "contract": - return error_response("invalid_contract_addr") - case f: - return error_response("bad_request", field=f, detail=str(e)) - - try: - check_reg_keys_sigs(params) - except SNSignatureValidationError as e: - return error_response("signature", detail=str(e)) - - solo = "contract" not in params - - for k in ("addr", "stake"): - params.setdefault(f"res_{k}", []) - - if solo and params["res_addr"]: - return error_response( - "invalid_contract_addr", - detail="the contract address is required for multi-contributor registrations", - ) - - if solo and "fee" in params: - return error_response( - "invalid_fee", detail="fee is not applicable to a solo node registration" - ) - elif "fee" not in params: - return error_response( - "invalid_fee", - detail="fee is required for a multi-contribution registration", - ) - else: - fee = params["fee"] - fee = int(fee) if re.fullmatch(r"\d+", fee) else -1 - if not 0 <= fee <= 10000: - return error_response( - "invalid_fee", - detail="fee must be an integer between 0 and 10000 (= 100.00%)", - ) - - if len(params["res_addr"]) != len(params["res_stake"]): - return error_response( - "bad_request", - field="res_addr", - detail="mismatched reserved address/stake lists", - ) - - reserved = [] - for i, (addr, stake) in enumerate(zip(params["res_addr"], params["res_stake"])): - try: - eth = raw_eth_addr("res_addr", addr) - except ValueError: - return error_response("invalid_res_addr", address=eth_format(addr), index=i+1) - try: - amt = parse_currency("res_stake", stake) - except ValueError: - return error_response( - "invalid_res_stake", address=eth_format(addr), index=i+1 - ) - - reserved.append((eth, amt)) - - total_reserved = params["stake"] + sum(stake for _, stake in reserved) - if solo: - if total_reserved != max_stake: - return error_response( - "wrong_op_stake", stake=total_reserved, required=max_stake - ) - else: - if params["stake"] < min_operator_stake: - return error_response( - "insufficient_op_stake", stake=params["stake"], minimum=min_operator_stake - ) - if total_reserved > max_stake: - return error_response("too_much", total=total_reserved, maximum=max_stake) - if 1 + len(reserved) > max_stakers: - return error_response("too_many", max_contributors=max_stakers - 1) - - remaining_stake = max_stake - params["stake"] - remaining_spots = max_stakers - 1 - - for i, (addr, amt) in enumerate(reserved): - # integer math ceiling: - min_contr = (remaining_stake + remaining_spots - 1) // remaining_spots - if amt < min_contr: - return error_response( - "insufficient_res_stake", - index=i+1, - address=eth_format(addr), - minimum=min_contr, - ) - remaining_stake -= amt - remaining_spots -= 1 - - res = {"success": True} - - if not solo: - res["remaining_contribution"] = remaining_stake - res["remaining_spots"] = remaining_spots - res["remaining_min_contribution"] = ( - remaining_stake + remaining_spots - 1 - ) // remaining_spots - - return json_response(res) - -def bootstrap(): - update_service_node_contract_ids(None) - -bootstrap() diff --git a/session-token-contracts b/session-token-contracts new file mode 160000 index 0000000..4cbf9bb --- /dev/null +++ b/session-token-contracts @@ -0,0 +1 @@ +Subproject commit 4cbf9bb2e11b5714a64a12efbdd1933a675dd4b1 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app_events.py b/src/app_events.py new file mode 100644 index 0000000..f2a214c --- /dev/null +++ b/src/app_events.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +import csv + +from src import config +from src.web3client.event_ws import init_ws_event_scanner, EventScannerConfig, VestingContractDetails + +vesting_contract_details = [] +if config.backend.vesting_contract_details_csv is not None: + data = list(csv.reader(open(config.backend.vesting_contract_details_csv))) + header = data[0] + assert header == ["beneficiary", "vestingAddress", "amount", "start", "end", "transferableBeneficiary", "revoker"] + vesting_contract_details = [] + for row in data[1:]: + row.extend([config.backend.addr_token, config.backend.addr_sn_rewards, config.backend.addr_sn_contrib_factory]) + vesting_contract_details.append(VestingContractDetails(*row)) + +config = EventScannerConfig( + log_level=config.backend.log_level, + enable_perf=config.backend.performance_logging, + log_level_generic=config.backend.log_level_generic, + genesis_block=config.backend.genesis_block, + contrib_factory_start_block=config.backend.contrib_factory_start_block, + ws_max_run_depth=config.backend.ws_max_run_depth, + ws_providers=config.backend.ws_providers, + ws_max_size=config.backend.ws_max_size, + addr_token=config.backend.addr_token, + addr_sn_contrib_factory=config.backend.addr_sn_contrib_factory, + addr_sn_rewards=config.backend.addr_sn_rewards, + addr_reward_rate_pool=config.backend.addr_reward_rate_pool, + refresh_block_interval=config.backend.refresh_block_interval, + get_logs_cap=config.backend.get_logs_cap, + sqlite_db=config.backend.sqlite_db, + sqlite_schema=config.backend.sqlite_schema, + db_reset_events_on_startup=config.backend.db_reset_events_on_startup, + db_reset_contrib_on_startup=config.backend.db_reset_contrib_on_startup, + reset_vesting_contracts_on_startup=config.backend.reset_vesting_contracts_on_startup, + vesting_contract_details=vesting_contract_details, + ws_watch_token_events=config.backend.ws_watch_token_events, +) + +if __name__ == "__main__": + init_ws_event_scanner(config) diff --git a/src/app_fetcher.py b/src/app_fetcher.py new file mode 100644 index 0000000..29c2c5a --- /dev/null +++ b/src/app_fetcher.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +from src import config +from src.staking.fetcher import App + +app = App("fetcher") +app.run() diff --git a/src/app_price.py b/src/app_price.py new file mode 100644 index 0000000..fb1b1e0 --- /dev/null +++ b/src/app_price.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +from src import config +from src.price.app import create_app, PriceAppConfig + +price_config = PriceAppConfig( + name="price_api", + log_level=config.backend.log_level, + enable_perf=config.backend.performance_logging, + sqlite_db=config.backend.prices_sqlite_db, + sqlite_schema=config.backend.prices_sqlite_schema, + coingecko_api_key=config.backend.coingecko_api_key, + coingecko_api_url=config.backend.coingecko_api_url, + coingecko_api_token_ids=config.backend.coingecko_api_token_ids, + price_poll_rate_seconds=config.backend.prices_api_refetch_interval_seconds, + default_token=config.backend.prices_api_default_token, + enable_price_fetcher=config.backend.enable_price_fetcher, + coingecko_precision=config.backend.coingecko_precision, +) + +app = create_app(price_config) diff --git a/src/app_registration.py b/src/app_registration.py new file mode 100644 index 0000000..bf2c980 --- /dev/null +++ b/src/app_registration.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +from src import config +from src.registration.app import create_app, RegistrationAppConfig + +registration_config = RegistrationAppConfig( + name="registration_api", + log_level=config.backend.log_level, + enable_perf=config.backend.performance_logging, + sqlite_db=config.backend.registration_sqlite_db, + sqlite_schema=config.backend.registration_sqlite_schema, + api_rate_limit=config.backend.registration_api_rate_limit, + api_rate_limit_period=config.backend.registration_api_rate_limit_period, +) + +app = create_app(registration_config) \ No newline at end of file diff --git a/src/app_snapshot.py b/src/app_snapshot.py new file mode 100644 index 0000000..6658e57 --- /dev/null +++ b/src/app_snapshot.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +from src import config +from src.snapshot.app import create_app, SnapshotAppConfig + +snapshot_app_config = SnapshotAppConfig( + name="snapshot", + log_level=config.backend.log_level, + enable_perf=config.backend.performance_logging, + sqlite_db=config.backend.sqlite_db, + log_level_generic=config.backend.log_level_generic, + cache_stale_time_seconds=config.backend.stale_time_seconds, + sqlite_db_snapshot=config.backend.sqlite_db_snapshot, + snapshot_on_startup=config.backend.snapshot_on_startup, + snapshot_time_interval_seconds=config.backend.snapshot_time_interval_seconds, +) + +app = create_app(snapshot_app_config) \ No newline at end of file diff --git a/src/app_staking.py b/src/app_staking.py new file mode 100644 index 0000000..0965b4d --- /dev/null +++ b/src/app_staking.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +from src import config +from src.staking.app import create_app, StakingAppConfig + +staking_app_config = StakingAppConfig( + name="staking_api", + log_level=config.backend.log_level, + enable_perf=config.backend.performance_logging, + sqlite_db=config.backend.sqlite_db, + sqlite_schema=config.backend.sqlite_schema, + sqlite_db_registrations=config.backend.registration_sqlite_db, + sqlite_schema_registrations=config.backend.registration_sqlite_schema, + rpc_api_cache=config.backend.rpc_api_cache, + rpc_shared=config.backend.rpc_shared, + rpc_shared_cache=config.backend.rpc_shared_cache, + rpc_api_usage_logging=config.backend.rpc_api_usage_logging, + log_level_generic=config.backend.log_level_generic, + cache_stale_time_seconds=config.backend.stale_time_seconds, + disable_db_file_rewrite=config.backend.disable_db_file_rewrite, + stale_time_seconds_contract_abis=config.backend.stale_time_seconds_contract_abis, + rpc_api_usage_logging_interval=config.backend.rpc_api_usage_logging_interval, +) + +app = create_app(staking_app_config) \ No newline at end of file diff --git a/config.py b/src/config.py similarity index 78% rename from config.py rename to src/config.py index f0510d4..c2e75ed 100644 --- a/config.py +++ b/src/config.py @@ -1,4 +1,4 @@ -from config_defaults import * +from .config_defaults import * # Local settings. Changes to this file are meant for a local installation (and should not be # committed to git). @@ -12,3 +12,6 @@ # backend = stagenet_backend # backend.provider_url = 'tcp://127.0.0.1:6786' + +# backend.log_level = CUSTOM_LOG_LEVELS["SILLY"] +# backend.log_level = logging.INFO \ No newline at end of file diff --git a/src/config_defaults.py b/src/config_defaults.py new file mode 100644 index 0000000..49f883e --- /dev/null +++ b/src/config_defaults.py @@ -0,0 +1,179 @@ +# Default configuration options for the session token staking website backend. +# +# To override settings add `config.whatever = ...` into `config.py`; this file should not be +# modified and simply contains the default values. +# +# To override things that are specific to mainnet/testnet/etc. add `config.whatever = ...` lines +# into `mainnet.py`/`testnet.py`/etc. +import logging + +# LMQ RPC endpoint of oxend; can be a unix socket 'ipc:///path/to/oxend.sock' or a tcp socket +# 'tcp://127.0.0.1:5678'. mainnet_rpc/testnet_rpc/devnet_rpc are selected based on whether the +# backend is running through the mainnet.py, testnet.py, or devnet.py application script. + +# SQLite database used for persistent data, such as shorted registration URL tokens. + +B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + + +class Backend: + """ + SHARED CONFIG + """ + log_level = logging.INFO + log_level_generic = None # Logs from other packages will use log_level if this is not set + performance_logging: bool = False + oxen_wallet_regex: str = "" + rpc_shared: list[str] = "" + rpc_shared_cache: int = 2 + + """ + WEB3 Config + """ + web3_provider_urls: list[str] = ["http://localhost:8545"] # Default hardhat private chain node address) + web3_caller_address: str | None = None + web3_private_key: str | None = None + addr_reward_rate_pool: str = "0x11f040E89dFAbBA9070FFE6145E914AC68DbFea0" + addr_token: str = "0x10Ea9E5303670331Bdddfa66A4cEA47dae4fcF3b" + addr_sn_contrib: str = "0x0000000000000000000000000000000000000000" + addr_sn_contrib_factory: str = "0x8129bE2D5eF7ACd39483C19F28DE86b7EF19DBCA" + addr_sn_rewards: str = "0xC2B9fC251aC068763EbDfdecc792E3352E351c00" + # All block scanning will take this as the starting block, no log events can occur before a contract is deployed + # so this is the first block that can be used to scan for events. Using 0 significantly slows down the scan. + genesis_block: int = 336099450 + contrib_factory_start_block: int = 336099450 + # How many blocks until getLogs can be called + refresh_block_interval: int = 8 # Measured in Arb blocks + # Max block range for a getLogs call + get_logs_cap: int = 100000 + + """ + DB CONFIG + """ + sqlite_db: str = "ssb.db" + sqlite_schema: str = "src/staking/schema.sql" + disable_db_file_rewrite: bool = False + db_reset_events_on_startup: bool = False + db_reset_contrib_on_startup: bool = False + + """ + API CONFIG + """ + rpc_api_cache: int = 2 + rpc_api_usage_logging: bool = False + rpc_api_usage_logging_interval: int = 300 + stale_time_seconds: int = 5 + stale_time_seconds_contract_abis: int = 300 + + """ + REGISTRATION CONFIG + """ + # NOTE: This can be the same DB as the main API, but you must manually run the registrations/schema.sql script in + # the main db so it can be populated with the required tables. + registration_sqlite_db: str = "ssb-registrations.db" + registration_sqlite_schema: str = "src/registration/schema.sql" + # Creates a request per period limit by IP address (default is 100 requests per hour) + registration_api_rate_limit: int = 100 + registration_api_rate_limit_period: int = 3600 + + """ + FETCHER CONFIG + """ + abi_dir = "src/web3client/abis" + # Arbitrum runs at ~4 blocks per second, and the rpc node has a limit of 30m, so scan for 120 blocks + arbitrum_rescan_safety_blocks: int = 60 + arbitrum_scan_start_chunk_size: int = 20 + refresh_rate_seconds_arbitrum: int = 10 + max_time_keeper_events: int = 10_000 + rpc_fetcher_cache: int = 1 + rpc_fetcher_usage_logging: bool = False + write_rpc_fail_reasons_to_file: bool = False + + """ + VESTING + """ + vesting_contract_details_csv: None | str = None + reset_vesting_contracts_on_startup: bool = False + + """ + WEB SOCKETS + """ + # This will disable Arbitrum event scanning in the fetcher + ws_enabled: bool = True + ws_providers: list[str] = [] + # The default ws size is set to 1GB to ensure all bootstrapping event scans go through, this value is extremely large + # to ensure it isn't exceeded in the future, as with time the size of the full network event scan will increase. if + # your Arbitrum node doesn't support high enough websocket sizes you should download the snapshot database and start + # running from there. TODO: add event scan chunking to allow for smaller websocket sizes + ws_max_size: int = 1_000_000_000 + # The maximum depth of the event scanner bootstrap loop. This is to prevent infinite loops in the event scanner. + # While it shouldn't be possible for this to happen, it is a safety measure. + ws_max_run_depth: int = 10 + # Only enable this if you want to track Approve and Transfer events for the token contract + ws_watch_token_events: bool = False + + """ + SNAPSHOT CONFIG + """ + sqlite_db_snapshot: str = "static/backend-snapshot.db" + snapshot_time_interval_seconds: int = 600 + snapshot_on_startup: bool = False + + """ + PRICE FETCHER CONFIG + """ + enable_price_fetcher: bool = False + coingecko_api_key: str = "" + coingecko_api_url: str = "https://api.coingecko.com/api" + coingecko_api_token_ids: list[str] = ["ethereum", "chainflip"] + coingecko_api_currencies: list[str] = ["usd", "aud"] + coingecko_precision: int = 9 + + # Creates a request per period limit by IP address (default is 10 requests per 10 minutes) + prices_api_rate_limit: int = 10 + prices_api_rate_limit_period: int = 600 + prices_sqlite_db: str = "ssb-prices.db" + prices_sqlite_schema: str = "src/price/schema.sql" + prices_api_default_token: str = "ethereum" + prices_api_refetch_interval_seconds:int = 300 + +# Session mainnet contracts +mainnet_backend = Backend() +mainnet_backend.oxen_wallet_regex = f'L[{B58_ALPHABET}]{{94}}"' +mainnet_backend.rpc_shared = "ipc://oxend/mainnet.sock" +mainnet_backend.sqlite_db = "ssb-mainnet.db" +mainnet_backend.ws_providers = ["ws://10.24.0.1/arb/ws"] +mainnet_backend.web3_provider_urls = ["http://10.24.0.1/arb"] + +# Session testnet contracts +testnet_backend = Backend() +testnet_backend.rpc_shared = "ipc://oxend/testnet.sock" +testnet_backend.sqlite_db = "ssb-testnet.db" + +# Session devnet.v3 contracts +devnet_backend = Backend() +devnet_backend.addr_reward_rate_pool = "0xb515C61DE12f28eE908a905b930aFb80B9bAd7cf" +devnet_backend.addr_sn_contrib = "0x0000000000000000000000000000000000000000" +devnet_backend.addr_sn_contrib_factory = "0x0000000000000000000000000000000000000000" +devnet_backend.addr_sn_rewards = "0x75Dc11700b2D03902FCb5Ca7aFd6A859a1Fa25Cb" +devnet_backend.oxen_wallet_regex = f"dV[{B58_ALPHABET}]{{95}}" +devnet_backend.rpc_shared = "ipc://oxend/devnet.sock" +devnet_backend.sqlite_db = "ssb-devnet.db" +devnet_backend.web3_provider_urls = ["https://sepolia-rollup.arbitrum.io/rpc"] + +# Session stagenet.v3 contracts +stagenet_backend = Backend() +stagenet_backend.addr_reward_rate_pool = "0xaAD853fE7091728dac0DAa7b69990ee68abFC636" +stagenet_backend.addr_token = "0x7D7fD4E91834A96cD9Fb2369E7f4EB72383bbdEd" +stagenet_backend.addr_sn_contrib_factory = "0x36Ee2Da54a7E727cC996A441826BBEdda6336B71" +stagenet_backend.addr_sn_rewards = "0x9d8aB00880CBBdc2Dcd29C179779469A82E7be35" +stagenet_backend.oxen_wallet_regex = f"ST[{B58_ALPHABET}]{{95}}" +stagenet_backend.rpc_shared = "tcp://localhost:6786" +stagenet_backend.sqlite_db = "ssb-stagenet.db" +stagenet_backend.ws_providers = ["ws://10.24.0.1/arb_sepolia/ws"] +stagenet_backend.web3_provider_urls = ["http://10.24.0.1/arb_sepolia"] +stagenet_backend.genesis_block = 114500919 +stagenet_backend.contrib_factory_start_block = 142785134 + +# Assign the active backend to be used in the sent-staking-backend +backend = stagenet_backend diff --git a/src/config_validate.py b/src/config_validate.py new file mode 100644 index 0000000..bee0fa4 --- /dev/null +++ b/src/config_validate.py @@ -0,0 +1,57 @@ +import logging + +from eth_utils import is_checksum_address + +from .log import Log +from .oxen.rpc import OxenRPC +from .util import is_not_empty_string +from .web3client.client import Web3Client + +log = Log("config_validate").logger + +def validate_log_config(conf): + if conf.log_level < logging.INFO: + log.warning( + "Log level is set to {} which is less than INFO. This is not recommended for production.".format( + conf.log_level + ) + ) + elif conf.log_level > logging.ERROR: + log.warning( + "Log level is set to {} which is greater than ERROR. This is not recommended for production.".format( + conf.log_level + ) + ) + +def validate_contract_addresses(conf): + assert is_checksum_address(conf.addr_token), "addr_token is not a valid checksum address" + assert is_checksum_address(conf.addr_sn_contrib_factory), "addr_sn_contrib_factory is not a valid checksum address" + assert is_checksum_address(conf.addr_sn_rewards), "addr_sn_rewards is not a valid checksum address" + assert is_checksum_address(conf.addr_reward_rate_pool), "addr_reward_rate_pool is not a valid checksum address" + +def validate_web3_client(conf): + assert conf.web3_provider_urls is not None and len( + conf.web3_provider_urls + ) > 0, "web3_provider_urls is not set in config.py" + + for web3_provider_url in conf.web3_provider_urls: + assert is_not_empty_string(web3_provider_url), "web3_provider_urls is not set properly in config.py" + + web3_client = Web3Client( + conf.web3_provider_urls, + conf.web3_caller_address, + conf.web3_private_key, + log, + ) + + block_number = web3_client.web3.eth.block_number + log.debug("Config validation block number: {}".format(block_number)) + assert block_number is not None, "Failed to get block number from web3 provider" + +def validate_oxen_rpc(conf): + rpc = OxenRPC(log, conf.rpc_shared, 0) + res = rpc.get_info().get() + log.debug("Config validation rpc response status: {}".format(res.get("status"))) + assert ( + res is not None and res.get("status") == "OK" + ), "Oxen RPC ping to {} failed with response: {}".format(conf.rpc_shared, res) diff --git a/src/db/__init__.py b/src/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/db/read.py b/src/db/read.py new file mode 100644 index 0000000..f9c5a24 --- /dev/null +++ b/src/db/read.py @@ -0,0 +1,20 @@ +import sqlite3 + +from ..log import Log + +class DBReader: + def __init__(self, db_path: str, log_level: int, perf: bool = False, disable_db_file_rewrite: bool = False): + self.log = Log("db_reader", log_level, enable_perf=perf).logger + + if not disable_db_file_rewrite: + if not db_path.startswith("file:"): + db_path = "file:" + db_path + + if not db_path.endswith("?mode=ro"): + db_path = db_path + "?mode=ro" + + self.log.info(f"Connecting to db at {db_path}") + self.db_path = db_path + + def connect(self): + return sqlite3.connect(self.db_path, uri=True) diff --git a/src/db/util.py b/src/db/util.py new file mode 100644 index 0000000..9332bc6 --- /dev/null +++ b/src/db/util.py @@ -0,0 +1,45 @@ +import logging +import sqlite3 + + +def init_db(db_path: str, schema_path: str): + assert db_path is not None and len(db_path) > 0 + assert schema_path is not None and len(schema_path) > 0 + with sqlite3.connect(db_path) as conn: + with open(schema_path, "r") as file: + schema_sql = file.read() + conn.executescript(schema_sql) + + +def is_db_initialized(db_path: str): + with sqlite3.connect(db_path) as conn: + cursor = conn.cursor() + cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") + db_tables = cursor.fetchall() + logging.debug(f"Database tables: {db_tables}") + return len(db_tables) > 0 + + +SQLITE_MAX_INT = 2**63 - 1 # 9,223,372,036,854,775,807 +SQLITE_MIN_INT = -(2**63) # -9,223,372,036,854,775,808 + + +def assert_all_dict_values_are_within_sqlite_integer_range(node: dict): + for key, value in node.items(): + if isinstance(value, int): + assert ( + SQLITE_MIN_INT <= value <= SQLITE_MAX_INT + ), f"Integer value {value} for key '{key}' in dict is out of SQLite integer range." + + +def sql_connect_in_read_mode(db_path: str): + if not db_path.startswith("file:"): + db_path = "file:" + db_path + + if not db_path.endswith("?mode=ro"): + db_path = db_path + "?mode=ro" + + return sqlite3.connect(db_path, uri=True) + +def sql_connect_in_write_mode(db_path: str): # Maybe dubious, but perhaps good for API symmetry, I'll defer to you + return sqlite3.connect(db_path) \ No newline at end of file diff --git a/src/log/__init__.py b/src/log/__init__.py new file mode 100644 index 0000000..f024c09 --- /dev/null +++ b/src/log/__init__.py @@ -0,0 +1,56 @@ +import logging + +from .perf import PerformanceLogger +from .util import add_logging_level + +CUSTOM_LOG_LEVELS = {"SILLY": 1, "PERFORMANCE": 69} +for label, lvl in CUSTOM_LOG_LEVELS.items(): + add_logging_level(label, lvl) + + +class CustomFormatter(logging.Formatter): + + green = "\x1b[32;20m" + blue = "\x1b[36;20m" + grey = "\x1b[38;20m" + yellow = "\x1b[33;20m" + red = "\x1b[31;20m" + bold_red = "\x1b[31;1m" + purple = "\x1b[35;1m" + reset = "\x1b[0m" + format = "%(asctime)s | %(name)s | %(levelname)s | %(message)s (%(filename)s:%(lineno)d)" + + FORMATS = { + CUSTOM_LOG_LEVELS["SILLY"]: green + format + reset, + logging.DEBUG: blue + format + reset, + logging.INFO: grey + format + reset, + logging.WARNING: yellow + format + reset, + logging.ERROR: red + format + reset, + logging.CRITICAL: bold_red + format + reset, + 69: purple + format + reset, + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + + +class Log: + def __init__(self, name, initial_level=CUSTOM_LOG_LEVELS["SILLY"], enable_perf=False): + self.name = name + self.logger = logging.getLogger(name) + self.logger.setLevel(initial_level) + self.logger.propagate = False + + ch = logging.StreamHandler() + ch.setLevel(initial_level) + ch.setFormatter(CustomFormatter()) + self.logger.addHandler(ch) + + self.logger.perf = PerformanceLogger(self.logger, enable_perf) + + def set_level(self, level: str) -> None: + named_level = logging.getLevelName(level) + self.logger.info("Setting log level to {}".format(named_level)) + self.logger.setLevel(level) diff --git a/src/log/perf.py b/src/log/perf.py new file mode 100644 index 0000000..4e19cf1 --- /dev/null +++ b/src/log/perf.py @@ -0,0 +1,67 @@ +import logging +import time +from copy import copy + + +class PerformanceLogger: + def __init__(self, logger: logging = None, enabled=True): + if enabled: + self.logger = logger + self.start = self._start_enabled + self.end = self.end_enabled + self.end_timer = self.end_timer_enabled + self.times = {} + self.cpu_times = {} + self.orphaned_event_age_ns = 3600 * 10**9 # 1 hour + self.check_for_orphans_interval = 3600 # 1 hour + self.last_orphan_prune = 0 + else: + self.logger = None + self.start = self._noop + self.end = self._noop + self.end_timer = self._noop + + def _start_enabled(self, label): + self.times[label] = time.perf_counter_ns() + self.cpu_times[label] = time.process_time_ns() + + def end_enabled(self, label): + elapsed_ms, elapsed_cpu_ms = self.end_timer(label) + self._log_end(label, elapsed_ms, elapsed_cpu_ms) + + def end_timer_enabled(self, label): + start_time = self.times.pop(label, None) + start_time_cpu = self.cpu_times.pop(label, None) + + + if start_time is not None and start_time_cpu is not None: + process_time_ns = time.perf_counter_ns() + self._cleanup_orphans(process_time_ns) + + elapsed_ms = (process_time_ns - start_time) / 1e6 + elapsed_cpu_ms = (time.process_time_ns() - start_time_cpu) / 1e6 + return elapsed_ms, elapsed_cpu_ms + return None, None + + def _log_end(self, label, elapsed_ms, elapsed_cpu_ms): + if self.logger is None: + return + + if elapsed_ms is not None and elapsed_cpu_ms is not None: + self.logger.performance( + f"Elapsed time for '{label}': {elapsed_ms:.6f} ms ({elapsed_cpu_ms:.6f} cpu ms)" + ) + else: + self.logger.performance(f"No start time recorded for label '{label}'") + + def _cleanup_orphans(self, process_time_ns): + if len(self.times) > 0 and process_time_ns - self.last_orphan_prune > self.check_for_orphans_interval: + self.last_orphan_prune = process_time_ns + + # NOTE: must be a copy as the dictionary is modified during iteration + for label, start in copy(self.times).items(): + if process_time_ns > start + self.orphaned_event_age_ns: + self.times.pop(label) + + def _noop(self, *args, **kwargs): + pass diff --git a/src/log/time_keeper.py b/src/log/time_keeper.py new file mode 100644 index 0000000..226e361 --- /dev/null +++ b/src/log/time_keeper.py @@ -0,0 +1,113 @@ +import logging +import statistics +import time + +from ..log import PerformanceLogger +from ..util import format_seconds, format_ms + + +class TimeKeeper: + def __init__(self, logger: logging, max_events=10_000): + self.max_events = max_events + assert self.max_events > 100, "max_events must be greater than 100 to be meaningful" + assert self.max_events < 10e6, "max_events must be less than 10e6 to avoid memory issues" + + self.logger = logger + + self.time_keeper_app_alive = time.time() + self.exec_timestamps = {} + + self.exec_durations = {} + self.exec_cpu_durations = {} + + self.perf = PerformanceLogger(logger, enabled=True) + + def add(self, name: str): + self.exec_timestamps.setdefault(name, []).append(time.time()) + self.perf.start(name) + + def end(self, name: str): + elapsed_ms, elapsed_cpu_ms = self.perf.end_timer(name) + self.exec_durations.setdefault(name, []).append(elapsed_ms) + self.exec_cpu_durations.setdefault(name, []).append(elapsed_cpu_ms) + + def get(self, name: str): + return ( + self.exec_timestamps[name], + self.exec_durations[name], + self.exec_cpu_durations[name], + ) + + def log_time_keeper(self): + self.logger.info( + "Time keeper: app alive for {} seconds".format( + format_seconds(time.time() - self.time_keeper_app_alive, 0) + ) + ) + for task, timestamps in self.exec_timestamps.items(): + deltas = [] + for i in range(len(timestamps)): + if i == 0: + continue + deltas.append(timestamps[i] - timestamps[i - 1]) + + n_deltas = len(deltas) + + self.logger.info("Time keeper: task '{}', n: {}".format(task, len(timestamps))) + if n_deltas > 0: + self.logger.info( + "- Intervals: min: {}s, max: {}s, avg: {}s, median: {}s, stddev: {}s".format( + format_seconds(min(deltas)), + format_seconds(max(deltas)), + format_seconds(sum(deltas) / n_deltas), + format_seconds(statistics.median(deltas)) if n_deltas > 1 else "N/A", + format_seconds(statistics.stdev(deltas)) if n_deltas > 1 else "N/A", + ) + ) + else: + self.logger.info("- More than one timestamp is required for interval stats") + + durations = self.exec_durations[task] + n_durations = len(durations) + if n_durations > 0: + self.logger.info( + "- Durations: min: {}ms, max: {}ms, avg: {}ms, median: {}ms, stddev: {}ms".format( + format_ms(min(durations)), + format_ms(max(durations)), + format_ms(sum(durations) / n_durations), + format_ms(statistics.median(durations)) if n_durations > 1 else "N/A", + format_ms(statistics.stdev(durations)) if n_durations > 1 else "N/A", + ) + ) + else: + self.logger.info("- More than one duration is required for duration stats") + + cpu_durations = self.exec_cpu_durations[task] + n_cpu_durations = len(cpu_durations) + if len(cpu_durations) > 0: + self.logger.info( + "- CPU Durations: min: {}ms, max: {}ms, avg: {}ms, median: {}ms, stddev: {}ms".format( + format_ms(min(cpu_durations)), + format_ms(max(cpu_durations)), + format_ms(sum(cpu_durations) / n_cpu_durations), + format_ms(statistics.median(cpu_durations)) if n_durations > 1 else "N/A", + ( + format_ms(statistics.stdev(cpu_durations)) + if n_cpu_durations > 1 + else "N/A" + ), + ) + ) + else: + self.logger.info("- More than one cpu duration is required for cpu duration stats") + + self.cleanup() + + def cleanup(self): + # NOTE: each array can vary in length as if an error occurs, exec_timestamps will be longer than durations + if len(self.exec_timestamps) > self.max_events: + self.exec_timestamps = self.exec_timestamps[-self.max_events :] + if len(self.exec_durations) > self.max_events: + self.exec_durations = self.exec_durations[-self.max_events :] + if len(self.exec_cpu_durations) > self.max_events: + self.exec_cpu_durations = self.exec_cpu_durations[-self.max_events :] diff --git a/src/log/util.py b/src/log/util.py new file mode 100644 index 0000000..0614a8f --- /dev/null +++ b/src/log/util.py @@ -0,0 +1,52 @@ +import logging + + +def add_logging_level(level_name: str, level_num: int, method_name=None): + """ + Comprehensively adds a new logging level to the `logging` module and the + currently configured logging class. + + `levelName` becomes an attribute of the `logging` module with the value + `levelNum`. `methodName` becomes a convenience method for both `logging` + itself and the class returned by `logging.getLoggerClass()` (usually just + `logging.Logger`). If `methodName` is not specified, `levelName.lower()` is + used. + + To avoid accidental clobberings of existing attributes, this method will + raise an `AttributeError` if the level name is already an attribute of the + `logging` module or if the method name is already present + + Example + ------- + >>> addLoggingLevel('TRACE', logging.DEBUG - 5) + >>> logging.getLogger(__name__).setLevel("TRACE") + >>> logging.getLogger(__name__).trace('that worked') + >>> logging.trace('so did this') + >>> logging.TRACE + 5 + + """ + if not method_name: + method_name = level_name.lower() + + if hasattr(logging, level_name): + raise AttributeError("{} already defined in logging module".format(level_name)) + if hasattr(logging, method_name): + raise AttributeError("{} already defined in logging module".format(method_name)) + if hasattr(logging.getLoggerClass(), method_name): + raise AttributeError("{} already defined in logger class".format(method_name)) + + # This method was inspired by the answers to Stack Overflow post + # http://stackoverflow.com/q/2183233/2988730, especially + # http://stackoverflow.com/a/13638084/2988730 + def logForLevel(self, message, *args, **kwargs): + if self.isEnabledFor(level_num): + self._log(level_num, message, args, **kwargs) + + def logToRoot(message, *args, **kwargs): + logging.log(level_num, message, *args, **kwargs) + + logging.addLevelName(level_num, level_name) + setattr(logging, level_name, level_num) + setattr(logging.getLoggerClass(), method_name, logForLevel) + setattr(logging, method_name, logToRoot) diff --git a/make-fake-reg.py b/src/make-fake-reg.py similarity index 100% rename from make-fake-reg.py rename to src/make-fake-reg.py diff --git a/src/oxen/__init__.py b/src/oxen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/oxen/omq.py b/src/oxen/omq.py new file mode 100644 index 0000000..86083ab --- /dev/null +++ b/src/oxen/omq.py @@ -0,0 +1,343 @@ +import logging +from collections import defaultdict + +import oxenmq +import json +import sys +from datetime import datetime, timedelta + +def format_table(table: list[list[str| float | int]]): + """ + Formats a table of values into a string. + :param table: list of lists of values + :return: string + """ + # stringify every value in the table + table = [[str(v) for v in row] for row in table] + # find the maximum width of each column + column_widths = [max(len(row[i]) for row in table) for i in range(len(table[0]))] + # pad each column to the maximum width + padded_table = [[row[i].ljust(column_widths[i]) for i in range(len(row))] for row in table] + # join each row with a space separator + padded_table = [" ".join(row) for row in padded_table] + # join each row with a newline separator + return "\n".join(padded_table) + +def bin_histogram_timestamps(timestamps, bin_size_seconds): + """ + Bins a histogram of timestamps into a histogram of bins of size 'bin_size_seconds'. + + :param timestamps: list of timestamps + :param bin_size_seconds: size of each bin in seconds + :return: dict of bins to counts + """ + bins = defaultdict(int) + for t in timestamps: + t_int = int(t) + bin_key = t_int // bin_size_seconds + bins[bin_key] += 1 + + max_count_adjusted = 0 + max_count_timestamp = 0 + for k, v in bins.items(): + if v > max_count_adjusted: + max_count_adjusted = v / bin_size_seconds + max_count_timestamp = k * bin_size_seconds + + return bins, max_count_timestamp, max_count_adjusted + +class RPCUsageTracker: + def __init__(self, enabled: bool, log: logging): + self.log = log + if enabled: + self.log.warning("RPC usage tracking enabled. This is not recommended for production use.") + self.uses_executed = {} + self.uses_success = {} + self.uses_failed = {} + self.uses_cached = {} + self.fail_reasons = {} + + self.add_executed = self.add_executed_enabled + self.add_success = self.add_success_enabled + self.add_failed = self.add_failed_enabled + self.add_cached = self.add_cached_enabled + self.log_usage = self.log_usage_enabled + + else: + self.add_executed = self._noop + self.add_success = self._noop + self.add_failed = self._noop + self.add_cached = self._noop + self.log_usage = self._noop + + def _noop(self, *args, **kwargs): + pass + + def add_executed_enabled(self, endpoint: str): + if endpoint not in self.uses_executed: + self.uses_executed[endpoint] = 0 + self.uses_executed[endpoint] += 1 + + def add_success_enabled(self, endpoint: str): + self.uses_success.setdefault(endpoint, []).append(datetime.now().timestamp()) + + def add_failed_enabled(self, endpoint: str, reason: str): + self.uses_failed.setdefault(endpoint, []).append(datetime.now().timestamp()) + self.fail_reasons.setdefault(endpoint, []).append(reason) + + def add_cached_enabled(self, endpoint: str): + self.uses_cached.setdefault(endpoint, []).append(datetime.now().timestamp()) + + def log_usage_enabled(self, msg: str = ""): + unique_endpoints: dict[str, dict[str, list[float]]] = {} + + g_executed = 0 + g_successes = 0 + g_failures = 0 + g_cached = 0 + + for endpoint, timestamps in self.uses_success.items(): + unique_endpoints.setdefault(endpoint, {"success": [], "failed": [], "cached": []}) + unique_endpoints[endpoint]["success"].extend(timestamps) + + for endpoint, timestamps in self.uses_failed.items(): + unique_endpoints.setdefault(endpoint, {"success": [], "failed": [], "cached": []}) + unique_endpoints[endpoint]["failed"].extend(timestamps) + + for endpoint, timestamps in self.uses_cached.items(): + unique_endpoints.setdefault(endpoint, {"success": [], "failed": [], "cached": []}) + unique_endpoints[endpoint]["cached"].extend(timestamps) + + for endpoint, timestamps in unique_endpoints.items(): + log_lines = [] + stats_success = timestamps["success"] + stats_failed = timestamps["failed"] + stats_cached = timestamps["cached"] + + total_success = len(stats_success) + total_failure = len(stats_failed) + total_cached = len(stats_cached) + + + g_successes += total_success + g_failures += total_failure + g_cached += total_cached + + total_executed = self.uses_executed.get(endpoint, 0) + g_executed += total_executed + + total_completed = total_success + total_failure + total_cached + if total_completed == 0: + continue + + success_rate = total_success / total_completed + failure_rate = total_failure / total_completed + cache_rate = total_cached / total_completed + + fail_reasons = self.fail_reasons.get(endpoint, []) + + fail_timeout = 0 + fail_other = 0 + for reason in fail_reasons: + if reason == "Timeout": + fail_timeout += 1 + else: + fail_other += 1 + + fail_timeout_rate = fail_timeout / total_failure if total_failure > 0 else 0 + fail_other_rate = fail_other / total_failure if total_failure > 0 else 0 + + timestamps_all = stats_success + stats_failed + stats_cached + timestamps_all.sort() + + ts_first = timestamps_all[0] + ts_last = timestamps_all[-1] + total_time_seconds = ts_last - ts_first + + if total_time_seconds == 0: + continue + + current_time = datetime.now().timestamp() + + # ---- 1. Average RPS over all time ---- + rps_avg = total_completed / total_time_seconds + + # ---- 2. Average RPS in the last hour ---- + one_hour_ago = current_time - 3600 + last_hour_timestamps = [ts for ts in timestamps_all if ts >= one_hour_ago] + # If you want a simple “count / 3600”, do: + rps_avg_last_hour = len(last_hour_timestamps) / 3600.0 + + # ---- 3. Average RPS in the last 10 minutes (600 seconds) ---- + ten_minutes_ago = current_time - 600 + last_10m_timestamps = [ts for ts in last_hour_timestamps if ts >= ten_minutes_ago] + rps_avg_last_10_minutes = len(last_10m_timestamps) / 600.0 + + # ---- 4. Average RPS in the last 1 minute (60 seconds) ---- + one_minute_ago = current_time - 60 + last_1m_timestamps = [ts for ts in last_10m_timestamps if ts >= one_minute_ago] + rps_avg_last_1_minute = len(last_1m_timestamps) / 60.0 + + # ---- 5. Peak RPS over the past hour ---- + (h_1h_sec, rps_peak_time_1h_sec, rps_peak_1h_sec) = bin_histogram_timestamps(last_hour_timestamps, bin_size_seconds=1) + (h_1h_min, rps_peak_time_1h_min, rps_peak_1h_min) = bin_histogram_timestamps(last_hour_timestamps, bin_size_seconds=60) + + # ---- 6. Peak RPS over the past hour binned every 10 minutes---- + (h_10m_sec, rps_peak_time_10m_sec, rps_peak_10m_sec) = bin_histogram_timestamps(last_10m_timestamps, bin_size_seconds=1) + (h_10m_min, rps_peak_time_10m_min, rps_peak_10m_min) = bin_histogram_timestamps(last_10m_timestamps, bin_size_seconds=60) + + log_lines.append(f"\n{endpoint} | {total_executed} executed | {total_completed} completed ({success_rate:.2%} success ({total_success}) | {failure_rate:.2%} failure ({total_failure}) | {cache_rate:.2%} cached ({total_cached}))") + log_lines.append(f"Timeout failures: {fail_timeout_rate:.2%} of failures ({fail_timeout} / {total_failure}))") + log_lines.append(f"Other failures: {fail_other_rate:.2%} of failures ({fail_other} / {total_failure}))") + + stats = [ + ["Period", "#", "RPS", "Peak RPS (1s bin)", "Peak RPS Time (1s bin)", "Peak RPS (1m bin)", + "Peak RPS Time (1m bin)"], + [f"Life ({total_time_seconds:.0f}s)", f"{total_completed}", f"{rps_avg:.2f}", "", "", "", ""], + [f"< 1h", len(last_hour_timestamps), f"{rps_avg_last_hour:.2f}", f"{rps_peak_1h_sec:.2f}", + rps_peak_time_1h_sec, f"{rps_peak_1h_min:.2f}", rps_peak_time_1h_min], + [f"< 10m", len(last_10m_timestamps), f"{rps_avg_last_10_minutes:.2f}", f"{rps_peak_10m_sec:.2f}", + rps_peak_time_10m_sec, f"{rps_peak_10m_min:.2f}", rps_peak_time_10m_min], + [f"< 1m", len(last_1m_timestamps), f"{rps_avg_last_1_minute:.2f}", "", "", "", ""]] + + log_lines.append(format_table(stats)) + + # self.log.info(f" Requests in past {total} ({total_success} success, {total_failure} failure, {total_cached} cached)") + # self.log.info(f" Avg requests per second: {rps_avg:.2f} avg (last hour: {rps_avg_last_hour:.2f}, last 10m: {rps_avg_last_10_minutes:.2f})") + msg += ("\n".join(log_lines)) + + g_completed = g_successes + g_failures + g_cached + g_success_rate = g_successes / g_completed if g_completed > 0 else 0 + g_failure_rate = g_failures / g_completed if g_completed > 0 else 0 + g_cache_rate = g_cached / g_completed if g_completed > 0 else 0 + + header_msg = f"\nRPC usage tracking | {g_executed} executed | {g_completed} completed ({g_success_rate:.2%} success ({g_successes}) | {g_failure_rate:.2%} failure ({g_failures}) | {g_cache_rate:.2%} cached ({g_cached}))" + self.log.info(header_msg + msg) + + def write_failure_reasons_to_file(self, filename: str): + with open(filename, "w") as f: + for endpoint, timestamps in self.fail_reasons.items(): + f.write(f"{endpoint}\n") + for reason in timestamps: + f.write(f" {reason}\n") + +omq, oxend = None, None + + +def omq_connection(oxend_rpc): + global omq, oxend + if omq is None: + omq = oxenmq.OxenMQ(log_level=oxenmq.LogLevel.warn) + omq.max_message_size = 200 * 1024 * 1024 + omq.start() + if oxend is None: + oxend = omq.connect_remote(oxenmq.Address(oxend_rpc)) + return (omq, oxend) + + +cached = {} +cached_args = {} +cache_expiry = {} + + +class FutureJSON: + """Class for making a OMQ JSON RPC request that uses a future to wait on the result, and caches + the results for a set amount of time so that if the same endpoint with the same arguments is + requested again the cache will be used instead of repeating the request. + + Cached values are indexed by endpoint and optional key, and require matching arguments to the + previous call. The cache_key should generally be a fixed value (*not* an argument-dependent + value) and can be used to provide multiple caches for different uses of the same endpoint. + Cache entries are *not* purged, they are only replaced, so using dynamic data in the key would + result in unbounded memory growth. + + omq - the omq object + oxend - the oxend omq connection id object + endpoint - the omq endpoint, e.g. 'rpc.get_info' + cache_seconds - how long to cache the response; can be None to not cache it at all + cache_key - fixed string to enable different caches of the same endpoint + args - if not None, a value to pass (after converting to JSON) as the request parameter. Typically a dict. + fail_okay - can be specified as True to make failures silent (i.e. if failures are sometimes expected for this request) + timeout - maximum time to spend waiting for a reply + """ + + def __init__( + self, + omq, + oxend, + endpoint, + cache_seconds=5, + *, + cache_key="", + args=None, + fail_okay=False, + timeout=10, + rpc_usage_tracker: RPCUsageTracker = RPCUsageTracker(False, None), + ): + if timeout is None: + timeout = 10 + self.endpoint = endpoint + self.cache_key = self.endpoint + cache_key + self.fail_okay = fail_okay + if args is not None: + args = json.dumps(args).encode() + if ( + self.cache_key in cached + and cached_args[self.cache_key] == args + and cache_expiry[self.cache_key] >= datetime.now() + ): + self.json = cached[self.cache_key] + self.args = None + self.future = None + else: + self.json = None + self.args = args + self.future = omq.request_future( + oxend, self.endpoint, [] if self.args is None else [self.args], request_timeout=timedelta(seconds=timeout) + ) + self.cache_seconds = cache_seconds + self.rpc_usage_tracker = rpc_usage_tracker + + def get(self): + """If the result is already available, returns it immediately (and can safely be called multiple times. + Otherwise waits for the result, parses as json, and caches it. Returns None if the request fails + """ + self.rpc_usage_tracker.add_executed(self.endpoint) + if self.json is None and self.future is not None: + try: + result = self.future.get() + self.future = None + if result[0] != b"200": + raise RuntimeError( + "Request for {} failed: got {}".format(self.endpoint, result) + ) + self.json = json.loads(result[1]) + if self.cache_seconds is not None: + cached[self.cache_key] = self.json + cached_args[self.cache_key] = self.args + cache_expiry[self.cache_key] = datetime.now() + timedelta( + seconds=self.cache_seconds + ) + self.rpc_usage_tracker.add_success(self.endpoint) + except RuntimeError as e: + if not self.fail_okay: + print("Something getting wrong: {}".format(e), file=sys.stderr) + self.future = None + self.rpc_usage_tracker.add_failed(self.endpoint, e) + + except TimeoutError as e: + if not self.fail_okay: + print("Timeout: {}".format(e), file=sys.stderr) + self.future = None + self.rpc_usage_tracker.add_failed(self.endpoint, "Timeout") + + except Exception as e: + if not self.fail_okay: + print("Something getting wrong: {}".format(e), file=sys.stderr) + self.future = None + self.rpc_usage_tracker.add_failed(self.endpoint, e) + else: + self.rpc_usage_tracker.add_cached(self.endpoint) + + return self.json + diff --git a/src/oxen/rpc.py b/src/oxen/rpc.py new file mode 100644 index 0000000..92ec927 --- /dev/null +++ b/src/oxen/rpc.py @@ -0,0 +1,196 @@ +import logging +from typing import TypedDict +from dataclasses import dataclass + +from ..oxen.omq import FutureJSON, omq_connection, RPCUsageTracker + + +class ServiceNodeContributor(TypedDict): + address: str + amount: int + beneficiary: str + locked_contributions: list[int] + + +class ServiceNode(TypedDict): + active: bool + contract_id: int + # In separate table + contributors: list[ServiceNodeContributor] + decommission_count: int + earned_downtime_blocks: int + funded: bool + is_liquidatable: bool + is_removable: bool + last_reward_block_height: int + last_reward_transaction_index: int + last_uptime_proof: int + lokinet_version: list[int] + operator_address: str + operator_fee: int + payable: bool + portions_for_operator: int + pubkey_bls: str + pubkey_ed25519: str + pubkey_x25519: str + public_ip: str + pulse_votes: dict[str, list[int]] | None + quorumnet_port: int + registration_height: int + registration_hf_version: int + requested_unlock_height: int + service_node_pubkey: str + service_node_version: list[int] + staking_requirement: int + state_height: int + storage_lmq_port: int + storage_port: int + storage_server_version: list[int] + swarm: str + swarm_id: int + total_contributed: int + + +@dataclass +class NetworkInfo: + block_hash: str + block_height: int + hard_fork: int + immutable_block_hash: str + immutable_block_height: int + max_stakers: int + min_operator_contribution: int + nettype: str + pulse_target_timestamp: int + staking_requirement: int + version: str + + +class OxenRPC: + def __init__(self, logger: logging, rpc_url: str, cache_seconds: float | None = None, usage_tracking: bool = False): + self.log = logger + self.rpc_url = rpc_url + self.cache_seconds = cache_seconds + self.usage_tracker = RPCUsageTracker(usage_tracking, self.log) + + def FutureJSON(self,endpoint: str, args: dict | None = None, cache_seconds: float | None = None, timeout: int | None = None): + omq, oxend = omq_connection(self.rpc_url) + return FutureJSON( + omq=omq, + oxend=oxend, + endpoint=endpoint, + args=args, + cache_seconds=cache_seconds if cache_seconds is not None else self.cache_seconds, + timeout=timeout, + rpc_usage_tracker=self.usage_tracker, + ) + + def get_accrued_rewards(self) -> FutureJSON: + return self.FutureJSON( + "rpc.get_accrued_rewards", + args={"addresses": []}, + ) + + def bls_rewards_request(self, eth_address: str) -> FutureJSON: + eth_address_for_rpc = eth_address.lower() + if eth_address_for_rpc.startswith("0x"): + eth_address_for_rpc = eth_address_for_rpc[2:] + result = self.FutureJSON( + "rpc.bls_rewards_request", + args={"address": eth_address_for_rpc, "height": 0}, + timeout=30, + ) + return result + + def bls_exit_liquidation_request(self, ed25519_pubkey: bytes, liquidate: bool) -> FutureJSON: + return self.FutureJSON( + "rpc.bls_exit_liquidation_request", + args={"pubkey": ed25519_pubkey.hex(), "liquidate": liquidate}, + timeout=30, + ) + + def bls_exit_liquidation_list(self) -> FutureJSON: + return self.FutureJSON( + "rpc.bls_exit_liquidation_list", + ) + + def get_info(self) -> FutureJSON: + return self.FutureJSON( + "rpc.get_info", + ) + + def get_last_block_header(self) -> FutureJSON: + return self.FutureJSON( + "rpc.get_last_block_header", + args={"fill_pow_hash": False, "get_tx_hashes": False}, + ) + + def get_block_headers_range(self, start_height, end_height): + return self.FutureJSON( + "rpc.get_block_headers_range", + args={ + "start_height": start_height, + "end_height": end_height, + "get_tx_hashes": False, + "fill_pow_hash": False, + }, + ) + def get_hard_fork_info(self, height: int) -> FutureJSON: + return self.FutureJSON( + "rpc.hard_fork_info", + args={"height": height} + ) + + + def get_service_nodes(self) -> FutureJSON: + return self.FutureJSON( + "rpc.get_service_nodes", + args={ + "all": True, + # TODO: decide if we want to reduce the number of fields returned, this needs to match the database too + # "fields": { + # x: True + # for x in ( + # "service_node_pubkey", + # "requested_unlock_height", + # "last_reward_block_height", + # "active", + # "pubkey_bls", + # "funded", + # "earned_downtime_blocks", + # "service_node_version", + # "contributors", + # "total_contributed", + # "total_reserved", + # "staking_requirement", + # "portions_for_operator", + # "operator_address", + # "pubkey_ed25519", + # "last_uptime_proof", + # "state_height", + # "swarm_id", + # "is_removable", + # "is_liquidatable", + # "operator_fee" + # ) + # }, + }, + ) + + def get_network_info_from_network(self): + info = self.get_info().get() + self.log.silly("get_network_info_from_network info: {}".format(info)) + + return NetworkInfo( + block_hash=info.get("top_block_hash", "NA"), + block_height=info.get("height", 0), + hard_fork=info.get("hard_fork", "NA"), + immutable_block_hash=info.get("immutable_block_hash", "NA"), + immutable_block_height=info.get("immutable_height", 0), + max_stakers=info.get("max_contributors", 0), + min_operator_contribution=info.get("min_operator_contribution", 0), + nettype=info.get("nettype", "NA"), + pulse_target_timestamp=info.get("pulse_target_timestamp", 0), + staking_requirement=info.get("staking_requirement", 0), + version=info.get("version", "NA"), + ) diff --git a/src/price/__init__.py b/src/price/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/price/app.py b/src/price/app.py new file mode 100644 index 0000000..bac9907 --- /dev/null +++ b/src/price/app.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +import time +from dataclasses import dataclass +from math import trunc +from numbers import Number + +import flask + +from ..util.flask_utils import FlaskApp, json_response, FlaskAppConfig +from .coingecko import CoinGeckoTokenPriceRequest +from .read import get_latest_price, get_prices_since +from .dataclasses import PriceDB +from .write import write_prices_to_db +from ..db.util import is_db_initialized, init_db + + +@dataclass +class PriceAppConfig(FlaskAppConfig): + + enable_price_fetcher: bool = False + + # Flask App Config + disable_db_file_rewrite: bool = False + sqlite_db: str = None + sqlite_schema: str = None + coingecko_api_key: str = None + coingecko_api_url: str = None + coingecko_api_token_ids: list[str] = None + coingecko_precision: int = None + + # Route Config + price_poll_rate_seconds: int = None + default_token: str = None + + +class App(FlaskApp): + def __init__(self, config: PriceAppConfig): + super().__init__(config) + self.app_config = config + + if config.enable_price_fetcher: + self.log.info(f"Price fetcher enabled, fetching from {config.coingecko_api_url}") + if not is_db_initialized(config.sqlite_db): + self.log.info(f"Initializing database {config.sqlite_db} with schema {config.sqlite_schema}") + init_db(config.sqlite_db, config.sqlite_schema) + else: + self.log.info("Price fetcher disabled. Set enable_price_fetcher to True to enable price fetcher.") + + if not config.default_token: + config.default_token = config.coingecko_api_token_ids[0] + self.log.warning(f"No default token set, using {config.default_token}") + else: + self.log.info(f"Default token set to {config.default_token}") + + self.db_path = config.sqlite_db + + if config.enable_price_fetcher: + self.token_price_request = CoinGeckoTokenPriceRequest( + logger=self.log, + key=config.coingecko_api_key, + url=config.coingecko_api_url, + token_ids=config.coingecko_api_token_ids, + include_market_cap=True, + include_last_updated_at=True, + precision=config.coingecko_precision, + ) + + self.price_poll_rate_seconds = config.price_poll_rate_seconds if config.price_poll_rate_seconds is not None else 0 + + + @staticmethod + def get_token_price_cache_key(token: str): + return f"price-{token}-all" + + @staticmethod + def get_token_prices_cache_key_range(token: str, timestamp: int, resolution: int): + return f"price-range-{token}-{timestamp}-{resolution}" + + + def get_price_for_token_cached(self, token: str) -> PriceDB | None: + key = self.get_token_price_cache_key(token) + value = self.cache.get_cached_only(key) + + if value is None: + value = get_latest_price(self.db_path, token) + + if value is None: + return None + + self.cache.set_cache_value(key, value, invalidate_timestamp=value.fetched_at + self.app_config.price_poll_rate_seconds) + + return value + + + def get_token_price_info(self, token: str = None): + if token is None: + token = self.app_config.default_token + + if token not in self.app_config.coingecko_api_token_ids: + return flask.abort(404, f"Token {token} not found!") + + data = self.get_price_for_token_cached(token) + + if data is None: + return flask.abort(500, f"Failed to fetch price for token {token}") + + stale_time = trunc(self.cache.get_stale_timestamp(self.get_token_price_cache_key(token))) + + return { + "usd": data.price, + "usd_market_cap": data.market_cap, + "t_price": data.updated_at, + "t_stale": stale_time, + } + + def get_prices_for_token_cached(self, token: str, range_seconds: int, resolution_seconds: int) -> list[PriceDB]: + key = self.get_token_prices_cache_key_range(token, range_seconds, resolution_seconds) + value = self.cache.get_cached_only(key) + + if value is None: + value = get_prices_since(self.db_path, token, trunc(time.time()) - range_seconds) + + if len(value) > 0: + self.cache.set_cache_value(key, value, invalidate_timestamp=value[0].updated_at + self.app_config.price_poll_rate_seconds) + + resolved_prices = [] + + # NOTE: prices are in descending order or when they were updated + + next_price_resolution = value[0].updated_at + 1 + for price in value: + if price.updated_at > next_price_resolution: + continue + + resolved_prices.append({ + "price": price.price, + "t": price.updated_at, + }) + next_price_resolution = price.updated_at - resolution_seconds + + return resolved_prices + +def create_app(config: PriceAppConfig) -> App: + app = App(config) + + if config.api_rate_limit is not None and config.api_rate_limit_period is not None: + @app.before_request + def rate_limit(): + return app.req_limiter.rate_limit() + + """ + ////////////////////////////////////////////////////////////// + // // + // Price Endpoints // + // // + ////////////////////////////////////////////////////////////// + """ + + @app.route("/price") + def route_get_token_price(): + return json_response({ + "price": app.get_token_price_info() + }) + + @app.route("/price/") + def route_get_token_price_for_token(token: str): + return json_response({ + "price": app.get_token_price_info(token) + }) + + def get_token_price_range_response(token: str, range_seconds: int, resolution_seconds: int): + return json_response({ + "prices": app.get_prices_for_token_cached(token, range_seconds, resolution_seconds), + }) + + @app.route("/prices//") + def route_get_token_prices_for_token_range(token: str, period: str): + match period: + case "1h": + return get_token_price_range_response(token, 60 * 60, config.price_poll_rate_seconds) + case "1d": + return get_token_price_range_response(token, 60 * 60 * 24, config.price_poll_rate_seconds) + case "7d": + return get_token_price_range_response(token, 60 * 60 * 24 * 7, 60 * 60) + case "30d": + return get_token_price_range_response(token, 60 * 60 * 24 * 30, 1) + case _: + return flask.abort(400, f"Invalid period {period}") + + + if config.enable_price_fetcher: + app.log.info("Polling for price info every {} seconds".format(app.price_poll_rate_seconds)) + try: + from uwsgidecorators import timer + except ModuleNotFoundError as e: + if e.name == "uwsgi": + app.log.error("uwsgi is not installed, run with uwsgi or disable price polling") + raise e + + @timer(app.price_poll_rate_seconds) + def fetch_token_price_info(signum): + app.logger.info("Fetch token price info start") + data = app.token_price_request.get() + formatted_data = app.token_price_request.format_for_db(data) + write_prices_to_db(app.db_path, formatted_data) + app.logger.info("Fetch token price info finish") + + fetch_token_price_info(None) + + return app diff --git a/src/price/coingecko.py b/src/price/coingecko.py new file mode 100644 index 0000000..622d06d --- /dev/null +++ b/src/price/coingecko.py @@ -0,0 +1,99 @@ +import logging +import time + +import requests + +from .dataclasses import PriceDB + + +class CoinGeckoTokenPriceRequest: + def __init__(self, logger: logging, key: str, url: str, token_ids: list[str], include_market_cap: bool = True, include_last_updated_at: bool = True, precision: int = 9): + self.log = logger + self.token_ids = token_ids + self.headers = { + "accept": "application/json", + "x-cg-demo-api-key": key + } + + query_params = { + "ids": "%2C".join(token_ids), + "vs_currencies": "%2C".join(['usd']), + } + + if include_market_cap: + query_params["include_market_cap"] = "true" + + if include_last_updated_at: + query_params["include_last_updated_at"] = "true" + + if precision is not None: + assert precision > 0 + query_params["precision"] = precision + + query_string = "&".join([f"{key}={value}" for key, value in query_params.items()]) + self.url = f"{url}/v3/simple/price?{query_string}" + + def get(self): + """ + Fetch the latest token price info. Uses the params set in the constructor. + + Response looks like: + { + "arbitrum": { + "usd": 0.704886, + "usd_market_cap": 3061960433.734907, + "aud": 1.12, + "aud_market_cap": 4859220977.76168, + "last_updated_at": 1737685901 + }, + "ethereum": { + "usd": 3305.97, + "usd_market_cap": 398379929097.7145, + "last_updated_at": 1737685896 + } + } + """ + response = requests.get(self.url, headers=self.headers) + + if response.ok: + return response.json() + + self.log.warning("Fetch token price info error: {}".format(response)) + return None + + def format_for_db(self, response: dict): + """ + Converts the response from the CoinGecko API to a format that can be stored in the database. + + Example response: + { + "arbitrum": { + "usd": 0.704886, + "usd_market_cap": 3061960433.734907, + "last_updated_at": 1737685901 + }, + "ethereum": { + "usd": 3305.97, + "usd_market_cap": 398379929097.7145, + "last_updated_at": 1737685896 + } + } + """ + result = [] + + for token in self.token_ids: + if token not in response: + self.log.warning(f"Token {token} not found in CoinGecko API response") + continue + + fetched_at = int(time.time()) + updated_at = response[token].get("last_updated_at", None) + market_cap = response[token].get(f"usd_market_cap", None) + price = response[token].get("usd", None) + if price is None: + self.log.warning(f"USD price not found in CoinGecko API response for token {token}") + + result.append(PriceDB(token=token, price=price, market_cap=market_cap, updated_at=updated_at, fetched_at=fetched_at)) + + return result + diff --git a/src/price/dataclasses.py b/src/price/dataclasses.py new file mode 100644 index 0000000..2caaa13 --- /dev/null +++ b/src/price/dataclasses.py @@ -0,0 +1,10 @@ +from attr import dataclass + + +@dataclass +class PriceDB: + token: str + price: float + market_cap: float + updated_at: int + fetched_at: int diff --git a/src/price/read.py b/src/price/read.py new file mode 100644 index 0000000..39cfe9c --- /dev/null +++ b/src/price/read.py @@ -0,0 +1,29 @@ +from contextlib import closing + +from .dataclasses import PriceDB +from ..db.util import sql_connect_in_read_mode + + +def get_latest_price(db_path: str, token: str): + with closing(sql_connect_in_read_mode(db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM prices WHERE token = ? ORDER BY fetched_at DESC LIMIT 1 + """, + (token,), + ) + price = PriceDB(*cursor.fetchone()) + return price + +def get_prices_since(db_path: str, token: str, timestamp: int): + with closing(sql_connect_in_read_mode(db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM prices WHERE token = ? AND updated_at >= ? ORDER BY updated_at DESC + """, + (token, timestamp), + ) + prices = [PriceDB(*price) for price in cursor.fetchall()] + return prices diff --git a/src/price/schema.sql b/src/price/schema.sql new file mode 100644 index 0000000..b28c594 --- /dev/null +++ b/src/price/schema.sql @@ -0,0 +1,13 @@ +PRAGMA journal_mode=WAL; + +CREATE TABLE prices ( + token TEXT NOT NULL, + price FLOAT NOT NULL, + market_cap FLOAT NOT NULL, + updated_at INTEGER NOT NULL, + fetched_at INTEGER NOT NULL +); + +CREATE INDEX prices_updated_at_idx ON prices(updated_at DESC); +CREATE INDEX prices_token_at_idx ON prices(token, updated_at DESC); +CREATE INDEX prices_token_fetched_at_idx ON prices(token, fetched_at DESC); diff --git a/src/price/write.py b/src/price/write.py new file mode 100644 index 0000000..cd3afc2 --- /dev/null +++ b/src/price/write.py @@ -0,0 +1,27 @@ +from contextlib import closing + +from .dataclasses import PriceDB +from ..db.util import sql_connect_in_write_mode + + +def write_prices_to_db(db_path: str, prices: list[PriceDB]): + with closing(sql_connect_in_write_mode(db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + cursor.executemany( + """ + INSERT INTO prices (token, price, market_cap, updated_at, fetched_at) + VALUES (?, ?, ?, ?, ?) + """, + ( + ( + price.token, + price.price, + price.market_cap, + price.updated_at, + price.fetched_at, + ) + for price in prices + ), + ) + connection.commit() diff --git a/src/registration/__init__.py b/src/registration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/registration/app.py b/src/registration/app.py new file mode 100644 index 0000000..a070bd2 --- /dev/null +++ b/src/registration/app.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +from dataclasses import dataclass +import eth_utils + +from ..util.flask_utils import FlaskApp, json_response, FlaskAppConfig +from ..db.util import is_db_initialized, init_db +from ..registration.read import DBReaderRegistrations +from ..registration.validation import check_reg_keys_sigs +from ..registration.write import DBWriterRegistrations +from ..util.parse import ( + parse_query_params, + byte_decoder, + EthConverter, + Hex64Converter, + raw_eth_addr, +) +@dataclass +class RegistrationAppConfig(FlaskAppConfig): + # Flask App Config + sqlite_db: str = None + sqlite_schema: str = None + + # Route Config + price_poll_rate_seconds: int = None + default_token: str = None + default_currency: str = None + +class App(FlaskApp): + def __init__(self, config: RegistrationAppConfig): + super().__init__(config) + + if not is_db_initialized(config.sqlite_db): + self.log.info( + "Initializing database {} with schema {}".format( + config.sqlite_db, config.sqlite_schema + ) + ) + init_db( + config.sqlite_db, config.sqlite_schema + ) + + self.db_reader = DBReaderRegistrations( + db_path=config.sqlite_db, + log_level=config.log_level, + perf=config.enable_perf, + ) + self.db_writer = DBWriterRegistrations( + db_path=config.sqlite_db, + log_level=config.log_level, + perf=config.enable_perf, + ) + + self.allowed_contract_names = set() + + +def create_app(config: RegistrationAppConfig) -> App: + app = App(config) + + @app.before_request + def rate_limit(): + return app.req_limiter.rate_limit() + + app.url_map.converters["hex64"] = Hex64Converter + app.url_map.converters["eth_wallet"] = EthConverter + + @app.route("/info") + def get_network_info(): + return json_response() + + """ + ////////////////////////////////////////////////////////////// + // // + // Registration Endpoints // + // // + ////////////////////////////////////////////////////////////// + """ + + # NOTE: the /api prefix route here is to allow for local testing + + @app.route("/api/store/", methods=["GET", "POST"]) + @app.route("/registrations/", methods=["POST"]) + @app.route("/store/", methods=["GET", "POST"]) + def store_registration(sn_pubkey: bytes): + """ + Stores (or replaces) the pubkeys/signatures associated with a service node that are needed to + call the smart contract to create a SN registration. These pubkeys/signatures are stored + indefinitely, allowing the operator to call them up whenever they like to re-submit a + registration for the same node. There is nothing confidential here: the values will be publicly + broadcast as part of the registration process already, and are constructed in such a way that + only the operator wallet can submit a registration using them. + + This works for both solo registrations and multi-registrations: for the latter, a contract + address is passed in the "c" parameter. + + The distinction at the SN layer is that contract registrations sign the contract address while + solo registrations sign the operator address. For submission to the blockchain, a contract + stake requires an additional interaction through a multi-contributor contract while solo + registrations can call the staking contract directly. + """ + try: + # TODO: replace with network type validation + def check_network(k,v): + return v + params = parse_query_params( + { + "network": check_network, + "pubkey_bls": byte_decoder(64), + "sig_ed25519": byte_decoder(64), + "sig_bls": byte_decoder(128), + "operator": raw_eth_addr, + } + ) + + params["pubkey_ed25519"] = sn_pubkey + + check_reg_keys_sigs(params) + except ValueError as e: + app.log.exception(e) + return json_response({"error": f"Invalid registration: {e}"}) + + app.db_writer.write_registration_to_db(params) + + params["operator"] = eth_utils.to_checksum_address(params["operator"]) + params["contract"] = ( + eth_utils.to_checksum_address(params["contract"]) if "contract" in params else None + ) + + return json_response({"success": True, "registration": params}) + + return app diff --git a/src/registration/app_tests.py b/src/registration/app_tests.py new file mode 100644 index 0000000..557d48d --- /dev/null +++ b/src/registration/app_tests.py @@ -0,0 +1,68 @@ +import time +import pytest + +from ..registration.app import create_app, RegistrationAppConfig +from ..util.config_import import import_config +config = import_config() + +registration_config = RegistrationAppConfig( + name="registration_api", + log_level=config.backend.log_level, + enable_perf=config.backend.performance_logging, + sqlite_db=config.backend.registration_sqlite_db, + sqlite_schema=config.backend.registration_sqlite_schema, + api_rate_limit=config.backend.registration_api_rate_limit, + api_rate_limit_period=config.backend.registration_api_rate_limit_period, +) + +app = create_app(registration_config) + +@pytest.fixture() +def client(): + return app.test_client() + + +@pytest.mark.skip(reason="This is a utility function called by other tests") +def test_rate_limit(client, endpoint, method, reset=True, rate_limit=60, rate_limit_period=60): + if reset: + app.req_limiter.store = {} + app.req_limiter.max_reqs_per_sec = rate_limit + app.req_limiter.rate_limit_period = rate_limit_period + + req_fn = getattr(client, method) + + for _ in range(rate_limit): + r = req_fn(endpoint) + assert r.status_code == 200, "Responds with 200 when rate limit is not exceeded" + + r = req_fn(endpoint) + assert r.status_code == 429, "Responds with 429 when rate limit is exceeded" + + +def test_rate_limit_get(client): + test_rate_limit(client, "/info", "get") + + +def test_rate_limit_post(client): + test_rate_limit(client, f"/store/0000000000000000000000000000000000000000000000000000000000000000", "post") + + +def test_rate_limit_resets(client): + rate_limit_period = 1 + test_rate_limit(client, "/info", "get", rate_limit_period=rate_limit_period) + time.sleep(rate_limit_period) + test_rate_limit(client, "/info", "get", rate_limit_period=rate_limit_period, reset=False) + +def test_rate_limit_separate_ips(client): + test_rate_limit(client, "/info", "get") + + ip = list(app.req_limiter.store.keys())[0] + ip_info = app.req_limiter.store[ip] + + app.req_limiter.store = { + '1.1.1.1': ip_info + } + + test_rate_limit(client, "/info", "get", reset=False) + + assert len(app.req_limiter.store.keys()) == 2 diff --git a/src/registration/read.py b/src/registration/read.py new file mode 100644 index 0000000..6740802 --- /dev/null +++ b/src/registration/read.py @@ -0,0 +1,54 @@ +import sqlite3 +from contextlib import closing + +from ..staking.dataclasses import Registration +from ..log import Log + +class DBReaderRegistrations: + def __init__(self, db_path: str, log_level: int, perf: bool = False): + self.db_path = db_path + self.log = Log("db_reader", log_level, enable_perf=perf).logger + + def get_registrations(self): + self.log.perf.start("get_registrations") + with closing(sqlite3.connect(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM registrations ORDER BY timestamp DESC + """ + ) + registrations = [Registration(*registration) for registration in cursor.fetchall()] + self.log.debug("Registrations: {}".format(len(registrations))) + self.log.perf.end("get_registrations") + return registrations + + def get_registrations_for_operator(self, operator: bytes): + self.log.perf.start("get_registrations_for_operator") + with closing(sqlite3.connect(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM registrations WHERE operator = ? ORDER BY timestamp DESC + """, + (operator,), + ) + registrations = [Registration(*registration) for registration in cursor.fetchall()] + self.log.debug("Registrations: {}".format(len(registrations))) + self.log.perf.end("get_registrations_for_operator") + return registrations + + def get_registrations_by_pubkey(self, pubkey: bytes): + self.log.perf.start("get_registrations_by_pubkey") + with closing(sqlite3.connect(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM registrations WHERE pubkey_ed25519 = ? ORDER BY timestamp DESC + """, + (pubkey,), + ) + registrations = [Registration(*registration) for registration in cursor.fetchall()] + self.log.debug("Registrations: {}".format(len(registrations))) + self.log.perf.end("get_registrations_by_pubkey") + return registrations diff --git a/src/registration/schema.sql b/src/registration/schema.sql new file mode 100644 index 0000000..02109ae --- /dev/null +++ b/src/registration/schema.sql @@ -0,0 +1,21 @@ + +PRAGMA journal_mode=WAL; + +CREATE TABLE registrations ( + operator BLOB NOT NULL, + pubkey_bls BLOB NOT NULL, + pubkey_ed25519 BLOB NOT NULL, + sig_bls BLOB NOT NULL, + sig_ed25519 BLOB NOT NULL, + timestamp FLOAT NOT NULL DEFAULT ((julianday('now') - 2440587.5)*86400.0), /* unix epoch */ + + CHECK(length(pubkey_ed25519) == 32), + CHECK(length(pubkey_bls) == 64), + CHECK(length(sig_ed25519) == 64), + CHECK(length(sig_bls) == 128), + CHECK(length(operator) == 20), + PRIMARY KEY(pubkey_bls, pubkey_ed25519) +); + +CREATE INDEX registrations_timestamp_idx ON registrations(timestamp DESC); +CREATE INDEX registrations_operator_idx ON registrations(operator, timestamp DESC); diff --git a/src/registration/validation.py b/src/registration/validation.py new file mode 100644 index 0000000..e57a592 --- /dev/null +++ b/src/registration/validation.py @@ -0,0 +1,32 @@ +import nacl.hash +import nacl.bindings as sodium +from nacl.signing import VerifyKey + + +class SNSignatureValidationError(ValueError): + pass + + +def check_reg_keys_sigs(params): + if len(params["pubkey_ed25519"]) != 32 or not sodium.crypto_core_ed25519_is_valid_point( + params["pubkey_ed25519"] + ): + raise SNSignatureValidationError("Ed25519 pubkey is invalid") + if len(params["pubkey_bls"]) != 64: # FIXME: bls pubkey validation? + raise SNSignatureValidationError("BLS pubkey is invalid") + if len(params["operator"]) != 20: + raise SNSignatureValidationError("operator address is invalid") + contract = params.get("contract") + if contract is not None and len(contract) != 20: + raise SNSignatureValidationError("contract address is invalid") + + signed = params["pubkey_ed25519"] + params["pubkey_bls"] + + try: + VerifyKey(params["pubkey_ed25519"]).verify(signed, params["sig_ed25519"]) + except nacl.exceptions.BadSignatureError: + raise SNSignatureValidationError("Ed25519 signature is invalid") + + # FIXME: BLS verification of pubkey_bls on signed + if False: + raise SNSignatureValidationError("BLS signature is invalid") diff --git a/src/registration/write.py b/src/registration/write.py new file mode 100644 index 0000000..1f6e251 --- /dev/null +++ b/src/registration/write.py @@ -0,0 +1,47 @@ +import sqlite3 +from contextlib import closing + +from ..log import Log + + +class DBWriterRegistrations: + def __init__(self, db_path: str, log_level: int, perf: bool = False): + self.db_path = db_path + self.log = Log("db_writer", log_level, enable_perf=perf).logger + + def write_registration_to_db(self, registration): + self.log.perf.start("write_registration_to_db") + with closing(sqlite3.connect(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + + self.log.debug("Inserting {} registration".format(len(registration))) + self.log.perf.start("write_registration_to_db -> insert registration") + + cursor.execute( + """ + INSERT OR REPLACE INTO registrations ( + operator, + pubkey_bls, + pubkey_ed25519, + sig_bls, + sig_ed25519 + ) + VALUES (?, ?, ?, ?, ?) + """, + ( + registration.get("operator"), + registration.get("pubkey_bls"), + registration.get("pubkey_ed25519"), + registration.get("sig_bls"), + registration.get("sig_ed25519"), + ), + ) + + inserted_rows = cursor.rowcount + + self.log.perf.end("write_registration_to_db -> insert registration") + self.log.debug("Inserted {} rows into registration".format(inserted_rows)) + + connection.commit() + self.log.perf.end("write_registration_to_db") diff --git a/src/snapshot/__init__.py b/src/snapshot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/snapshot/app.py b/src/snapshot/app.py new file mode 100644 index 0000000..12aaf9d --- /dev/null +++ b/src/snapshot/app.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +from dataclasses import dataclass +from uwsgidecorators import timer + +from ..staking.snapshot import DBSnapshot +from ..util.flask_utils import FlaskApp, FlaskAppConfig + + +@dataclass +class SnapshotAppConfig(FlaskAppConfig): + sqlite_db: str = None + sqlite_db_snapshot: str = None + snapshot_time_interval_seconds: int = None + snapshot_on_startup: bool = None + + +class App(FlaskApp): + def __init__(self, config: SnapshotAppConfig): + super().__init__(config) + assert config.snapshot_time_interval_seconds >= 1, "Snapshot interval must be at least 1 second" + + self.db_snapshot = DBSnapshot( + source_db_path=config.sqlite_db, + snapshot_db_path=config.sqlite_db_snapshot, + excluded_tables={ + # Staging rows, these are committed to the main db once the immutable height is reached, this can be synced by the end user + "service_nodes_staging", + "service_nodes_contributions_staging", + }, + log_level=config.log_level, + perf=config.enable_perf, + ) + + self.log.info(f"Snapshot task initialized, will run every {config.snapshot_time_interval_seconds} seconds.") + + +def create_app(config) -> App: + app = App(config) + + @timer(config.snapshot_time_interval_seconds) + def snapshot_db(signum): + app.db_snapshot.snapshot() + + if config.snapshot_on_startup: + app.log.info("Snapshotting database on startup, set snapshot_on_startup=False to disable") + snapshot_db(None) + + return app diff --git a/src/staking/__init__.py b/src/staking/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/staking/app.py b/src/staking/app.py new file mode 100644 index 0000000..fb4fdb4 --- /dev/null +++ b/src/staking/app.py @@ -0,0 +1,770 @@ +#!/usr/bin/env python3 +import dataclasses +import math +from dataclasses import dataclass +import statistics + +import flask +import eth_utils +from eth_typing import ChecksumAddress +from uwsgidecorators import timer +from werkzeug.exceptions import GatewayTimeout + +from .dataclasses import ArbitrumInfo, RewardsInfo, DailyRewardInfoNode +from .read import DBReaderStaking +from ..oxen.rpc import OxenRPC +from ..registration.read import DBReaderRegistrations +from ..util.flask_utils import FlaskApp, json_response, FlaskAppConfig +from ..util.parse import Hex64Converter, EthConverter, eth_format, parse_bls_pubkey, parse_ed25519_pubkey +from ..web3client.client import Web3Client +from ..web3client.contracts_ws.service_node_contribution import ServiceNodeContribution + + +@dataclass +class StakingAppConfig(FlaskAppConfig): + # Flask App Config + disable_db_file_rewrite: bool = False + sqlite_db: str = None + sqlite_schema: str = None + + sqlite_db_registrations: str = None + sqlite_schema_registrations: str = None + + rpc_api_cache: int = None + rpc_shared: str = None + rpc_shared_cache: int = None + rpc_api_usage_logging: bool = None + rpc_api_usage_logging_interval: int = None + + stale_time_seconds_contract_abis: int = None + +class App(FlaskApp): + def __init__(self, config: StakingAppConfig): + super().__init__(config) + + self.db_reader = DBReaderStaking( + db_path=config.sqlite_db, + log_level=config.log_level, + perf=config.enable_perf, + ) + self.db_reader_registrations = DBReaderRegistrations( + db_path=config.sqlite_db_registrations, + log_level=config.log_level, + perf=config.enable_perf, + ) + + rpc_cache = ( + config.rpc_api_cache + if config.rpc_api_cache + else config.rpc_shared_cache + ) + + self.rpc = OxenRPC( + logger=self.log, + rpc_url=config.rpc_shared, + cache_seconds=rpc_cache, + usage_tracking=config.rpc_api_usage_logging, + ) + + self.allowed_contract_names = set() + + self.arbitrum_sn_events = {} + self.contribution_contract_events = {} + self.arb_event_next_block = 0 + + self.contribution_contracts = {} + + self.contribution_contract_map = {} + + def get_arbitrum_events(self): + self.log.perf.start("get_arbitrum_sn_events") + missed_events = self.db_reader.get_arbitrum_events(from_block=self.arb_event_next_block, names=["NewServiceNodeV2", "ServiceNodeExitRequest", "ServiceNodeExit", "ServiceNodeLiquidated"]) + latest_block = self.arb_event_next_block - 1 + + for event in missed_events: + if event.block > latest_block: + latest_block = event.block + + if event.name == "NewServiceNodeContributionContract": + address = event.args.get("contributorContract") + if address: + self.contribution_contract_events.setdefault(address, []).append(event) + else: + contract_id = event.args.get("serviceNodeID") + if contract_id: + self.arbitrum_sn_events.setdefault(contract_id, []).append(event) + + missed_contribution_contract_events = self.db_reader.get_arbitrum_events(from_block=self.arb_event_next_block, names=["NewServiceNodeContributionContract", *ServiceNodeContribution.event_names]) + for event in missed_contribution_contract_events: + if event.block > latest_block: + latest_block = event.block + address = event.main_arg + if address: + self.contribution_contract_events.setdefault(address, []).append(event) + + self.arb_event_next_block = latest_block + 1 + + self.log.perf.end("get_arbitrum_sn_events") + return self.arbitrum_sn_events, self.contribution_contract_events + + +def create_app(config: StakingAppConfig) -> App: + app = App(config) + + def get_and_refresh_allowed_contract_names(): + allowed_contract_names = set() + for name in app.db_reader.get_smart_contract_names(): + allowed_contract_names.add(name) + app.allowed_contract_names = allowed_contract_names + return allowed_contract_names + + app.url_map.converters["hex64"] = Hex64Converter + app.url_map.converters["eth_wallet"] = EthConverter + + def get_median_operator_fee_uncached(): + # remove nodes that only have a single contributor + nodes = [n for n in get_nodes_cached() if len(n.contributors) > 1] + return statistics.median([n.operator_fee for n in nodes]) if len(nodes) > 0 else 0 + + def get_median_operator_fee_cached(): + return app.cache.get("median_operator_fee", getter=get_median_operator_fee_uncached, ttl=3600) + + def get_network_info_uncached() -> tuple[dict | None, ArbitrumInfo]: + network_info = app.db_reader.get_network_info() + # arbitrum_info = app.db_reader.get_arbitrum_info() + arbitrum_info = ArbitrumInfo(0, 0, 0, 0) + if network_info is None: + return None, arbitrum_info + network_info = dataclasses.asdict(network_info) + network_info["median_operator_fee"] = get_median_operator_fee_cached() + return network_info, arbitrum_info + + def get_next_block_timestamp_est(): + network_info, arbitrum_info = get_network_info_cached() + return network_info["pulse_target_timestamp"] + + def get_network_info_cached(): + return app.cache.get("network_info", getter=get_network_info_uncached, ttl=1) + + def get_contribution_contracts_for_address_cached(address: str): + return app.cache.get(f"contribution_contracts-{address}", getter=get_related_contribution_contracts_for_eth_address_uncached, getter_args=address, ttl=1) + + def get_vesting_contracts_cached(): + return app.cache.get("vesting_contracts", getter=app.db_reader.get_vesting_contracts, ttl=4) + + def get_vesting_contracts_for_beneficiary_cached(beneficiary: str): + contracts = [] + vesting = get_vesting_contracts_cached() + for contract in vesting: + if eth_format(contract.beneficiary) == beneficiary: + contracts.append(contract) + return contracts + + def json_res(vals, include_network_info=True): + if include_network_info: + network_info, arbitrum_info = get_network_info_cached() + network_info["l2_height"] = arbitrum_info.block + network_info["l2_height_timestamp"] = arbitrum_info.timestamp + vals["network"] = network_info + + return json_response(vals) + + @app.route("/info") + def get_network_info(): + return json_res({}) + + def get_nodes_cached(): + return app.cache.get("nodes", getter=app.db_reader.get_nodes) + + @app.route("/nodes") + def route_get_nodes(): + return json_res({"nodes": get_nodes_cached()}) + + def get_latest_node_version_info_uncached(): + network_info, arbitrum_info = get_network_info_cached() + height = network_info.get("height", 0) + return app.rpc.get_hard_fork_info(height).get() + + def get_latest_node_version_info_cached(): + return app.cache.get("latest_node_version_info", getter=get_latest_node_version_info_uncached, ttl=600) + + @app.route("/hf_info") + def route_get_version_info(): + return json_res({ + "version_info": get_latest_node_version_info_cached(), + }) + + def get_contract_nodes_uncached(): + events_exit = app.db_reader.get_arbitrum_events_by_name("ServiceNodeExit") + sn_ids_exited = set([event.args["serviceNodeID"] for event in events_exit]) + new_seed_events = app.db_reader.get_arbitrum_events_by_name("NewSeededServiceNode") + new_sn_v2_events = app.db_reader.get_arbitrum_events_by_name("NewServiceNodeV2") + node_dict = {} + + for event in new_seed_events: + sn_id = event.args["serviceNodeID"] + node_dict[sn_id] = { + "bls": parse_bls_pubkey(event.args.get("blsPubkey")), + "ed25519": parse_ed25519_pubkey(event.args.get("ed25519Pubkey")), + "in": sn_id not in sn_ids_exited + } + + for event in new_sn_v2_events: + sn_id = event.args["serviceNodeID"] + node_dict[sn_id] = { + "bls": parse_bls_pubkey(event.args.get("pubkey")), + "ed25519": parse_ed25519_pubkey(event.args.get("serviceNode").get("serviceNodePubkey")), + "in": sn_id not in sn_ids_exited + } + + return node_dict + + def get_contract_nodes_cached(): + return app.cache.get("contract_nodes", getter=get_contract_nodes_uncached) + + @app.route("/contract_nodes") + def route_get_contract_nodes(): + return json_res({"nodes": get_contract_nodes_cached()}) + + # TODO: get rid of this + def get_added_bls_keys(): + events_exit = app.db_reader.get_arbitrum_events_by_name("ServiceNodeExit") + sn_ids_exited = set([event.args["serviceNodeID"] for event in events_exit]) + new_seed_events = app.db_reader.get_arbitrum_events_by_name("NewSeededServiceNode") + new_sn_v2_events = app.db_reader.get_arbitrum_events_by_name("NewServiceNodeV2") + contract_id_map = {} + + for event in new_seed_events: + sn_id = event.args["serviceNodeID"] + if sn_id not in sn_ids_exited: + contract_id_map[parse_bls_pubkey(event.args["blsPubkey"])] = sn_id + + for event in new_sn_v2_events: + sn_id = event.args["serviceNodeID"] + if sn_id not in sn_ids_exited: + contract_id_map[parse_bls_pubkey(event.args["pubkey"])] = sn_id + return contract_id_map + + + def get_nodes_bls_keys_cached(): + return app.cache.get("contract_node_bls_keys_added", getter=get_added_bls_keys) + + + @app.route("/nodes/bls") + def route_get_nodes_bls_keys(): + return json_res({"bls_keys": get_nodes_bls_keys_cached()}) + + """ + ////////////////////////////////////////////////////////////// + // // + // Stake Endpoints // + // // + ////////////////////////////////////////////////////////////// + """ + + def get_related_stakes_for_eth_address_uncached(address: ChecksumAddress): + nodes = get_nodes_cached() + + related_nodes = [] + for node in nodes: + for contributor in node.contributors: + try: + if eth_format(contributor.address) == address: + related_nodes.append(node) + except Exception as e: + app.log.exception(e) + continue + + return related_nodes + + def get_related_stakes_for_eth_address_cached(address: ChecksumAddress): + return app.cache.get("related-stakes-{}".format(address), getter=get_related_stakes_for_eth_address_uncached, + getter_args=address) + + # TODO: might make sense to investigate storing contributor and operator addresses in the db as blobs and compare with bytes + @app.route("/stakes/") + @app.route("/nodes/") + def route_get_stakes_for_eth_address(eth_wal: str): + try: + address = eth_format(eth_wal) + return json_res({"stakes": get_related_stakes_for_eth_address_cached(address), + "contracts": get_contribution_contracts_for_address_cached(address), + "vesting": get_vesting_contracts_for_beneficiary_cached(address), + "added_bls_keys": get_nodes_bls_keys_cached()}) + + except ValueError as e: + app.logger.error(f"Exception: {e}") + return flask.abort(400, e) + except Exception as e: + app.logger.error(f"Exception: {e}") + app.logger.exception(e) + return flask.abort(500, e) + + @app.route("/stakes/") + @app.route("/nodes/") + def route_get_stakes_for_sn_pubkey(sn_pubkey: bytes): + try: + nodes = get_nodes_cached() + related_nodes = [node for node in nodes if node.pubkey_ed25519 == sn_pubkey] + return json_res({"stakes": related_nodes}) + + except Exception as e: + app.logger.error(f"Exception: {e}") + return flask.abort(500, e) + + """ + ////////////////////////////////////////////////////////////// + // // + // Contract Endpoints // + // // + ////////////////////////////////////////////////////////////// + """ + + def get_cached_allowed_contract_names(): + return app.cache.get( + "allowed_contract_names", + getter=get_and_refresh_allowed_contract_names, + ttl=config.stale_time_seconds_contract_abis + ) + + @app.route("/contract/names") + def route_get_abi_names(): + return json_res({"names": list(get_cached_allowed_contract_names())}) + + @app.route("/contract/abis") + def route_get_abis(): + return json_res( + {"abis": app.cache.get("abis_all", getter=app.db_reader.get_smart_contract_abis, + ttl=config.stale_time_seconds_contract_abis)} + ) + + @app.route("/contract/addresses") + def get_contract_addresses(): + return json_res( + {"addresses": app.cache.get("addresses_all", getter=app.db_reader.get_smart_contract_addresses)} + ) + + @app.route("/contract/addresses/core") + def get_contract_addresses_core(): + return json_res( + {"addresses": app.cache.get("addresses_core", getter=app.db_reader.get_smart_contract_addresses_core, + ttl=config.stale_time_seconds_contract_abis)} + ) + + + def get_contribution_contracts_uncached(): + contracts = app.db_reader.get_contribution_contracts() + addresses = contracts.keys() + contract_events = app.db_reader.get_arbitrum_events_by_main_args_desc(addresses) + for event in contract_events: + contracts[event.main_arg].events.append(event) + return contracts + + + def get_contribution_contracts_cached(): + return app.cache.get("contracts", getter=get_contribution_contracts_uncached, ttl=2) + + @app.route("/contract/contribution") + def get_open_contract_details(): + return json_res( + {"contracts": list(get_contribution_contracts_cached().values()), "added_bls_keys": get_nodes_bls_keys_cached()} + ) + + def get_contribution_contracts_for_sn_pubkey_uncached(sn_pubkey: bytes): + cached_contracts = get_contribution_contracts_cached().values() + contracts = [contract for contract in cached_contracts if contract.service_node_pubkey == sn_pubkey] + contracts.sort(key=lambda x: x.events[-1].block if len(x.events) > 0 else math.inf, reverse=True) + return contracts[0] if len(contracts) > 0 else None + + @app.route("/contract/contribution/") + def get_contribution_contract_for_sn_pubkey_cached(sn_pubkey: bytes): + key = sn_pubkey.hex() + return json_res( + {"contract": app.cache.get("contract-sn-{}".format(key), + getter=get_contribution_contracts_for_sn_pubkey_uncached, getter_args=key, + ttl=2)} + ) + + def get_related_contribution_contracts_for_eth_address_uncached(eth_wal: str): + contracts = get_contribution_contracts_cached().values() + + related_contracts = [] + for contract in contracts: + if contract.operator_address == eth_wal: + related_contracts.append(contract) + elif contract.contributors is not None: + for contributor in contract.contributors: + if contributor.address == eth_wal: + related_contracts.append(contract) + return related_contracts + + def get_related_contribution_contracts_for_eth_address_cached(eth_wal: str): + return app.cache.get("related-contracts-{}".format(eth_wal), + getter=get_related_contribution_contracts_for_eth_address_uncached, getter_args=eth_wal) + + @app.route("/contract/contribution/") + def get_contribution_contracts_for_wallet(eth_wal: str): + try: + if not eth_wal or not eth_utils.is_address(eth_wal): + raise ValueError("Invalid wallet address") + + return json_res({"contracts": get_related_contribution_contracts_for_eth_address_cached(eth_wal)}) + + except ValueError as e: + app.logger.error(f"Exception: {e}") + return flask.abort(400, e) + except Exception as e: + app.logger.error(f"Exception: {e}") + return flask.abort(500, e) + + @app.route("/contract/abi/") + def get_abi(contract_name: str): + if contract_name not in get_cached_allowed_contract_names(): + return flask.abort(404, f"Contract {contract_name} not found") + + return json_res( + { + "contract": app.cache.get( + "abi-{}".format(contract_name), getter=app.db_reader.get_smart_contract_abi, + getter_args=contract_name, + ttl=config.stale_time_seconds_contract_abis + ) + } + ) + + @app.route("/contract/address/") + def get_contract_address(contract_name: str): + if contract_name not in get_cached_allowed_contract_names(): + return flask.abort(404, f"Contract {contract_name} not found") + + return json_res( + { + "address": app.cache.get( + "address-{}".format(contract_name), + getter=app.db_reader.get_smart_contract_address, + getter_args=contract_name, + ) + } + ) + + """ + ////////////////////////////////////////////////////////////// + // // + // Event Endpoints // + // // + ////////////////////////////////////////////////////////////// + """ + + def get_events_handler(count_limit=500, skip=0): + limit = min(count_limit, 500) + events, limit, skip, total = app.cache.get("events-{}-{}".format(count_limit, skip), + getter=app.db_reader.get_arbitrum_events_page, + getter_args=[limit, skip], + ttl=10) + pagination = {"limit": limit, "skip": skip, "total": total} + + return {"events": events, "pagination": pagination} + + @app.route("/events//") + def get_events(count: int, skip: int): + return json_res(get_events_handler(count, skip)) + + def get_arbitrum_info_uncached(): + return app.db_reader.get_arbitrum_info() + + @app.route("/arbitrum-info") + def get_arbitrum_info(): + return json_res({"info": app.cache.get("arbitrum-info", getter=get_arbitrum_info_uncached)}) + + @app.route("/stake-events/") + def get_stake_events(contract_id: int): + if contract_id < 0: + return flask.abort(400, "Invalid contract ID") + + return json_res({"events": app.cache.get("stake-events-{}".format(contract_id), + getter=app.db_reader.get_arbitrum_events_for_stake_contrat_id, + getter_args=contract_id)}) + + """ + ////////////////////////////////////////////////////////////// + // // + // Exit Endpoints // + // // + ////////////////////////////////////////////////////////////// + """ + + def handle_get_exit_and_liquidation(params: [bytes, bool]): + ed25519_pubkey, liquidate = params[0], params[1] + if ed25519_pubkey not in get_exitable_ed25519_keys_cached(): + return flask.abort(404, f"No exit available for {ed25519_pubkey.hex()}") + + response = app.rpc.bls_exit_liquidation_request(ed25519_pubkey, liquidate).get() + if response is None: + raise GatewayTimeout("Failed to get exit signature") + if "status" in response: + response.pop("status") + + return json_res({"result": response}) + + def handle_get_exit_and_liquidation_cached(params: [bytes, bool]): + try: + return app.cache.get(f"exit-{params[0]}-{params[1]}", getter=handle_get_exit_and_liquidation, + getter_args=params, + invalidate_timestamp=get_next_block_timestamp_est()) + except GatewayTimeout as e: + app.logger.error(f"Exception: {e}") + return flask.abort(504) # Gateway timeout + except TimeoutError: + return flask.abort(408) # Request timeout + except Exception as e: + app.logger.error(f"Exception: {e}") + return flask.abort(500, e) + + @app.route("/exit/") + def route_get_exit(ed25519_pubkey: bytes): + return handle_get_exit_and_liquidation_cached([ed25519_pubkey, False]) + + @app.route("/liquidation/") + def route_get_liquidation(ed25519_pubkey: bytes): + return handle_get_exit_and_liquidation_cached([ed25519_pubkey, True]) + + def get_exit_liquidation_list_uncached(): + return app.rpc.bls_exit_liquidation_list().get() + + def get_exit_liquidation_list_cached(): + return app.cache.get("exit_liquidation_list", getter=get_exit_liquidation_list_uncached, + invalidate_timestamp=get_next_block_timestamp_est()) + + @app.route("/exit_liquidation_list") + def route_get_exit_liquidation_list(): + return json_res( + {"result": get_exit_liquidation_list_cached()} + ) + + def get_exitable_ed25519_keys_uncached(): + return set([bytes.fromhex(x.get("service_node_pubkey")) for x in get_exit_liquidation_list_cached()]) + + def get_exitable_ed25519_keys_cached(): + return app.cache.get("exitable_ed25519_keys", getter=get_exitable_ed25519_keys_uncached, + invalidate_timestamp=get_next_block_timestamp_est()) + + """ + ////////////////////////////////////////////////////////////// + // // + // Rewards Endpoints // + // // + ////////////////////////////////////////////////////////////// + """ + + def get_rewards_signature_uncached(eth_wal: str): + address = eth_format(eth_wal) + response = app.rpc.bls_rewards_request(address).get() + if response is None: + raise TimeoutError("Failed to get rewards signature") + + response.pop("status") if "status" in response else None + response.pop("address") if "address" in response else None + + return response + + def get_rewards_info_cached(): + # We cache all rewards info for all wallets so we don't need to multiple reads in a short period of time + return app.cache.get(f"rewards_info", getter=app.db_reader.get_rewards_info, + invalidate_timestamp=get_next_block_timestamp_est()) + + def get_rewards_info_for_address_cached(eth_wal: str): + address = eth_format(eth_wal) + rewards_info = get_rewards_info_cached() + return rewards_info.get(address, RewardsInfo(address=address, amount=0, lifetime_liquidated_stakes=0, + lifetime_locked_stakes=0, lifetime_rewards=0, + lifetime_unlocked_stakes=0, locked_stakes=0, + timelocked_stakes=0)) + + def get_rewards_info_response(eth_wal: str): + return json_res({"rewards": get_rewards_info_for_address_cached(eth_wal)}) + + def get_rewards_signature_response(eth_wal: str): + try: + return json_res( + {"rewards": app.cache.get(f"rewards-sig-{eth_wal}", getter=get_rewards_signature_uncached, + getter_args=eth_wal, + invalidate_timestamp=get_next_block_timestamp_est())}) + except ValueError as e: + return flask.abort(400, str(e)) + + @app.route("/rewards/", methods=["GET", "POST"]) + def get_rewards(eth_wal: str): + if flask.request.method == "GET": + return app.cache.get(f"rewards-info-response-{eth_wal}", getter=get_rewards_info_response, + getter_args=eth_wal, + invalidate_timestamp=get_next_block_timestamp_est()) + + if flask.request.method == "POST": + try: + return app.cache.get(f"rewards-sig-response-{eth_wal}", getter=get_rewards_signature_response, + getter_args=eth_wal, invalidate_timestamp=get_next_block_timestamp_est()) + except TimeoutError: + # We don't want to cache a 408 response + return flask.abort(408) + + return flask.abort(405) # Method not allowed + + def get_daily_rewards_info(eth_wal: str): + return app.db_reader.get_daily_rewards_info_for_address(eth_wal, 0) + + def get_daily_rewards_info_cached(eth_wal: str): + return app.cache.get(f"daily-rewards-info-{eth_wal}", getter=get_daily_rewards_info, getter_args=eth_wal, + invalidate_timestamp=get_next_block_timestamp_est()) + + @app.route("/daily-rewards/") + def get_daily_rewards(eth_wal: str): + return json_res({"rewards": get_daily_rewards_info_cached(eth_wal)}) + + + """ + ////////////////////////////////////////////////////////////// + // // + // Registration Endpoints // + // // + ////////////////////////////////////////////////////////////// + """ + + @app.route("/registrations/") + def operator_registrations(operator: str): + """ + Retrieves stored registration(s) for the given 'operator'. + + This returns an array in the "registrations" field containing as many registrations as are + currently stored for the given operator wallet, sorted from most to least recently submitted. + + Fields are the same as the version of this endpoint that takes a SN pubkey. + + Returns the JSON response with the 'registrations' for the given 'operator'. + """ + + operator_bytes = bytes.fromhex(operator[2:]) + + return json_res( + { + "registrations": app.cache.get( + f"registrations-op-{operator_bytes}", + getter=app.db_reader_registrations.get_registrations_for_operator, + getter_args=operator_bytes, + ) + } + ) + + @app.route("/registrations/") + def sn_pubkey_registrations(sn_pubkey: bytes) -> flask.Response: + """ + Retrieves stored registration(s) for the given service node pubkey. + + This returns an array in the "registrations" field containing either one or two registration + info dicts: a solo registration (if known) and a multi-contributor contract registration (if + known). These are sorted by timestamp of when the registration was last received/updated. + + Fields in each dict: + - "operator": the operator address. + - "contract": the contract address, for "type": "contract" and omitted for "type": "solo". + - "pubkey_ed25519": the primary SN pubkey, in hex. + - "pubkey_bls": the SN BLS pubkey, in hex. + - "sig_ed25519": the SN pubkey signed registration signature. + - "sig_bls": the SN BLS pubkey signed registration signature. + - "timestamp": the unix timestamp when this registration was received (or last updated) + + Returns the JSON response with the 'registrations' for the given 'sn_pubkey'. + """ + result = json_res( + { + "registrations": app.cache.get( + f"registration-sn-{sn_pubkey}", + getter=app.db_reader_registrations.get_registrations_by_pubkey, + getter_args=sn_pubkey, + ) + } + ) + return result + + """ + ////////////////////////////////////////////////////////////// + // // + // Token Endpoints // + // // + ////////////////////////////////////////////////////////////// + """ + + def get_token_info_uncached(): + net_info, arb_info = get_network_info_uncached() + staking_requirement = net_info.get("staking_requirement") + contract_address = app.db_reader.get_smart_contract_address("Token") + contract_address = eth_utils.to_checksum_address(contract_address) if contract_address is not None else None + + return { + "staking_requirement": staking_requirement, + "staking_reward_pool": arb_info.balance_reward_rate_pool, + "contract_address": contract_address, + } + + @app.route("/token") + def route_get_token_info(): + return json_res({"token": app.cache.get("token_info", getter=get_token_info_uncached)}, + include_network_info=False) + + """ + ////////////////////////////////////////////////////////////// + // // + // Network Endpoints // + // // + ////////////////////////////////////////////////////////////// + """ + + def get_network_info_basic_uncached(): + network_info = app.db_reader.get_network_info() + return { + "network_size": network_info.network_size, + } + + @app.route("/network") + def route_get_network_info(): + return json_res({"network": app.cache.get("network_info_basic", getter=get_network_info_basic_uncached)}) + + """ + ////////////////////////////////////////////////////////////// + // // + // Bootstap // + // // + ////////////////////////////////////////////////////////////// + """ + + get_and_refresh_allowed_contract_names() + + """ + ////////////////////////////////////////////////////////////// + // // + // Timers // + // // + ////////////////////////////////////////////////////////////// + """ + + if config.rpc_api_usage_logging: + def log_rpc_usage(signum): + app.rpc.usage_tracker.log_usage(" For signum {}".format(signum)) + app.rpc.usage_tracker.write_failure_reasons_to_file(f"rpc-usage-failure-reasons-{signum}.txt") + + @timer(config.rpc_api_usage_logging_interval, target="worker1") + def log_rpc_usage_w1(signum): + log_rpc_usage(signum) + + @timer(config.rpc_api_usage_logging_interval, target="worker2") + def log_rpc_usage_w2(signum): + log_rpc_usage(signum) + + @timer(config.rpc_api_usage_logging_interval, target="worker3") + def log_rpc_usage_w3(signum): + log_rpc_usage(signum) + + @timer(config.rpc_api_usage_logging_interval, target="worker4") + def log_rpc_usage_w4(signum): + log_rpc_usage(signum) + + return app diff --git a/src/staking/arbitrum.py b/src/staking/arbitrum.py new file mode 100644 index 0000000..43c7035 --- /dev/null +++ b/src/staking/arbitrum.py @@ -0,0 +1,253 @@ +import logging +from dataclasses import dataclass + +import eth_utils +from web3.exceptions import BlockNotFound + +from ..web3client.client import Web3Client +from ..web3client.contracts.service_node_contribution import ServiceNodeContributionInterface +from ..web3client.contracts.service_node_contribution_factory import ServiceNodeContributionFactory +from ..web3client.contracts.service_node_rewards import ServiceNodeRewardsInterface +from ..web3client.event_scanner import ProcessedEvent + + +# TODO: we should be able to remove this once contract_id is always available via rpc.get_service_nodes +def get_service_node_rewards_contract_id_map(contract: ServiceNodeRewardsInterface): + """ + Update the map of service node contract ids to BLS public keys. This fetches the list of all service nodes from the + Service Node Rewards contract and maps them to their corresponding contract ids. + """ + [ids, bls_keys] = contract.get_all_service_node_contract_ids() + return {f"{x:064x}{y:064x}": contract_id for contract_id, (x, y) in zip(ids, bls_keys)} + + +def get_new_contribution_contracts( + web3_client: Web3Client, + logger: logging, + interface: ServiceNodeContributionFactory, + last_block: int, + end_block: int, +): + logger.perf.start("get_new_contribution_contracts") + events = interface.event_scanner.run(last_block=last_block, end_block=end_block) + + logger.perf.start("create_contribution_contract_instances") + contracts = [ + ServiceNodeContributionInterface(web3_client, event.args.contributorContract) + for event in events + if eth_utils.is_address(event.args.contributorContract) + ] + logger.perf.end("create_contribution_contract_instances") + logger.debug("Found {} new contract events".format(len(events))) + logger.debug("Found {} new contracts".format(len(contracts))) + logger.perf.end("get_new_contribution_contracts") + return contracts, events + + +@dataclass +class ContributionContractDetails: + address: str | None + fee: int | None + manual_finalize: bool | None + operator_address: str | None + pubkey_bls: str | None + service_node_pubkey: str | None + service_node_signature: str | None + status: int | None + + +def update_contribution_contract_details( + web3_client: Web3Client, + logger: logging, + contracts: list[ServiceNodeContributionInterface], + max_requests_per_batch=1000, +): + logger.perf.start("chunk_contribution_contract_instances") + assert len(contracts) > 0, "Expected at least one contract" + assert max_requests_per_batch > 0, "Expected max_requests_per_batch > 0" + + requests_per_contract = ( + ServiceNodeContributionInterface.add_details_fetch_to_batch_added_batches() + ) + + max_chunk_size = max_requests_per_batch // requests_per_contract + + logger.debug( + "contracts: {}, requests_per_contract: {}, max_chunk_size: {}".format( + len(contracts), max_requests_per_batch, max_chunk_size + ) + ) + + chunks = [contracts[i : i + max_chunk_size] for i in range(0, len(contracts), max_chunk_size)] + logger.perf.end("chunk_contribution_contract_instances") + + logger.perf.start("fetch_contribution_contract_details total") + responses = [] + for chunk in chunks: + + assert len(chunk) <= max_requests_per_batch, "Expected chunk size <= {} got {}".format( + max_requests_per_batch, len(chunk) + ) + + logger.perf.start("fetch_contribution_contract_details_chunk of size {}".format(len(chunk))) + with web3_client.web3.batch_requests() as batch: + for contract in chunk: + contract.add_details_fetch_to_batch(batch) + + res = batch.execute() + responses.extend(res) + logger.perf.end("fetch_contribution_contract_details_chunk of size {}".format(len(chunk))) + + assert ( + len(responses) == len(contracts) * requests_per_contract + ), "Expected {} responses, got {}".format(len(contracts), len(responses)) + + contract_details = [] + contributions_list = [] + + for i in range(0, len(responses), requests_per_contract): + contract_address = contracts[i // requests_per_contract].contract_address + + params = responses[i] + + operator_address = responses[i + 1] + pubkey_bls_data = responses[i + 2] + + contributions = responses[i + 3] + contributions_addresses = contributions[0] + contributions_beneficiaries = contributions[1] + contributions_amounts = contributions[2] + + reserved = responses[i + 6] + reserved_addresses = reserved[0] + reserved_amounts = reserved[1] + + contributor_slots = {} + + for j in range(len(contributions_addresses)): + address = contributions_addresses[j] + contributor_slots[address] = { + "contract_address": contract_address, + "address": address, + "amount": contributions_amounts[j], + "beneficiary_address": contributions_beneficiaries[j], + "reserved": 0, + } + + for j in range(len(reserved_addresses)): + address = reserved_addresses[j] + amount = reserved_amounts[j] + contributor_slots.setdefault(address, {"contract_address": contract_address, "address": address, "beneficiary_address":address, "amount": 0}).update({"reserved": amount}) + + status = responses[i + 4] + + manual_finalize = responses[i + 5] + + contributions_list.extend(contributor_slots.values()) + + contract_details.append( + ContributionContractDetails( + address=contract_address, + fee=params[3], + manual_finalize=manual_finalize, + operator_address=operator_address, + pubkey_bls="0x{:0128x}".format((pubkey_bls_data[0] << 256) + pubkey_bls_data[1]), + service_node_pubkey=f"{params[0]:032x}", + service_node_signature=f"{params[1]:032x}{params[2]:032x}", + status=status, + ) + ) + + logger.debug("Fetched details for {} contracts".format(len(contract_details))) + + logger.perf.end("fetch_contribution_contract_details total") + return contract_details, contributions_list + + +def get_block_timestamp(web3_client: Web3Client, block_num: int): + """Get block timestamp""" + try: + block_info = web3_client.web3.eth.get_block(block_num) + except BlockNotFound: + # Block was not mined yet, + # minor chain reorganisation? + return None + return block_info.get("timestamp") + + +def estimate_block_timestamp(ref_block: int, ref_block_timestamp: int, target_block: int): + """Estimate block timestamp + Arbitrum averages 4 blocks per second + """ + seconds_diff = (target_block - ref_block) / 4 + return ref_block_timestamp + seconds_diff + + +def batch_populate_events_with_block_timestamps( + web3_client: Web3Client, + logger: logging, + events: list[ProcessedEvent], + max_requests_per_batch=1000, +): + logger.perf.start("chunk_new_event_blocks") + if len(events) == 0: + return + assert max_requests_per_batch > 0, "Expected max_requests_per_batch > 0" + + max_chunk_size = max_requests_per_batch + + blocks = list({event.block for event in events}) + + logger.debug( + "events: {}, requests_per_contract: {}, max_chunk_size: {}".format( + len(blocks), max_requests_per_batch, max_chunk_size + ) + ) + + chunks = [blocks[i: i + max_chunk_size] for i in range(0, len(blocks), max_chunk_size)] + logger.perf.end("chunk_new_event_blocks") + + logger.perf.start("fetch_new_event_block_timestamps total") + responses = [] + for chunk in chunks: + + assert len(chunk) <= max_requests_per_batch, "Expected chunk size <= {} got {}".format( + max_requests_per_batch, len(chunk) + ) + + logger.perf.start("fetch_new_event_block_timestamps of size {}".format(len(chunk))) + with web3_client.web3.batch_requests() as batch: + for block in chunk: + batch.add(web3_client.web3.eth.get_block(block)) + + res = batch.execute() + responses.extend(res) + logger.perf.end("fetch_new_event_block_timestamps of size {}".format(len(chunk))) + + assert ( + len(responses) == len(blocks) + ), "Expected {} responses, got {}".format(len(blocks), len(responses)) + + logger.debug("Fetched timestamps of blocks for {} events".format(len(blocks))) + + logger.perf.end("fetch_new_event_block_timestamps total") + + block_timestamps = { + block_info["number"]: block_info["timestamp"] + for block_info in responses + } + + for event in events: + event.timestamp = block_timestamps.get(event.block) + if event.timestamp is None: + logger.warning("No timestamp for block {}".format(event.block)) + + return + +def populate_events_with_main_arg(events: list[ProcessedEvent]): + """ + Populates the main_arg field of the event with a main argument if one is specified, otherwise the value of the first argument. + """ + for event in events: + # NOTE: This is apparently the best way to get the 0th element of a dict + event.main_arg = event.args[next(iter(event.args))] diff --git a/src/staking/dataclasses.py b/src/staking/dataclasses.py new file mode 100644 index 0000000..e96a242 --- /dev/null +++ b/src/staking/dataclasses.py @@ -0,0 +1,233 @@ +import json +from dataclasses import dataclass +from typing import Optional + +from ..util.parse import eth_format +from ..web3client.event_scanner import ProcessedEvent + + +@dataclass +class DBNode: + active: bool + contract_id: int + decommission_count: int + earned_downtime_blocks: int + fetched_block_height: int + funded: bool + is_liquidatable: bool + is_removable: bool + last_reward_block_height: int + last_uptime_proof: int + lokinet_version: dict | None + operator_address: str + operator_fee: int + payable: bool + pubkey_bls: str + pubkey_ed25519: str + public_ip: str | None + pulse_votes: dict | None + quorumnet_port: int | None + registration_height: int + registration_hf_version: str + requested_unlock_height: int + service_node_pubkey: str + service_node_version: dict | None + staking_requirement: int + state_height: int + storage_lmq_port: int | None + storage_port: int | None + storage_server_version: dict | None + swarm: str + swarm_id: str + total_contributed: int + + # Not in staging db, optional in main db + deregistration_height: int | None + exit_type: str | None + liquidation_height: int | None + + # Not in db but added after select + contributors: list | None + events: list[ProcessedEvent] | None + + def __post_init__(self): + self.lokinet_version = json.loads(self.lokinet_version) if self.lokinet_version else None + self.service_node_version = ( + json.loads(self.service_node_version) if self.service_node_version else None + ) + self.storage_server_version = ( + json.loads(self.storage_server_version) if self.storage_server_version else None + ) + self.pulse_votes = json.loads(self.pulse_votes) if self.pulse_votes else None + + +@dataclass +class DBNodeExit: + pubkey_bls: str + deregistration_height: int | None + exit_type: str + liquidation_height: int + + +@dataclass +class DBContributionMain: + address: str + amount: int + beneficiary: str | None + contract_id: str + fetched_block_height: int + + def __post_init__(self): + # We don't need the contract_id or fetched_block_height fields when its a dict + self.__dataclass_fields__ = { + k: v + for k, v in self.__dataclass_fields__.items() + if k != "contract_id" and k != "fetched_block_height" + } + + +@dataclass +class VestingContract: + address: str + beneficiary: str + initial_amount: int + initial_beneficiary: str + revoker: str + time_end: int + time_start: int + transferable_beneficiary: bool + + +@dataclass +class DBNetworkInfo: + id: Optional[int] + active_node_count: int + block_hash: str + block_height: int + block_timestamp: float + hard_fork: int + immutable_block_hash: str + immutable_block_height: int + max_stakers: int + min_operator_contribution: int + nettype: str + node_count: int + pulse_target_timestamp: int + staking_requirement: int + total_staked: int + version: str + + def __post_init__(self): + if self.immutable_block_height is None: + self.immutable_block_height = 0 + # We don't need the id field when its a dict + self.__dataclass_fields__ = { + k: v for k, v in self.__dataclass_fields__.items() if k != "id" + } + + +@dataclass +class DBContributionContract: + address: str + fee: int + manual_finalize: bool + operator_address: str + pubkey_bls: str + service_node_pubkey: str + status: int + # Not in db but added after select + contributors: list | None + events: list | None + + +@dataclass +class DBContributionContractContribution: + address: str + amount: int + beneficiary_address: str + contract_address: str + reserved: int + + def __post_init__(self): + # We don't need the contract_address field when its a dict + self.__dataclass_fields__ = { + k: v for k, v in self.__dataclass_fields__.items() if k != "contract_address" + } + + +@dataclass +class SmartContractABI: + name: str + abi: dict + bytecode: bytes + deployed_bytecode: bytes + + def __post_init__(self): + self.abi = json.loads(self.abi) + +@dataclass +class ArbitrumEvent: + block: int + tx: str + name: str + args: str + + def __post_init__(self): + self.args = json.loads(self.args) + +@dataclass +class ArbitrumInfo: + block: int + timestamp: float + balance_reward_rate_pool: int + balance_service_node_rewards: int + +@dataclass +class RewardsInfo: + # Address of the wallet this object is for. + address: str + # Total amount of claimable tokens for the given address. This includes the earnt rewards as well as unlocked + # stakes that are available to be claimed. + amount: int + # Total amount of tokens in the lifetime of the network that have been liquidated from the stakes for this address. + lifetime_liquidated_stakes: int + # Total amount of tokens in the lifetime of the network that has been staked into nodes for this address. + lifetime_locked_stakes: int + # Total amount of tokens in the lifetime of the network that has been earnt from staking into nodes for this address. + lifetime_rewards: int + # Total amount of tokens in the lifetime of the network that has been unlocked from the nodes this address has + # staked into. + lifetime_unlocked_stakes: int + # Amount of tokens currently locked into nodes on the network. This is `lifetime locked - lifetime unlocked`. + locked_stakes: int + # Amount of tokens that have been unstaked from nodes but cannot be claimed until the time lock on those + # individual stakes have been unlocked. + timelocked_stakes: int + # Amount of tokens that have been claimed from the nodes this address has staked into. + claimed_stakes: int = 0 + # Amount of tokens that have been claimed from the rewards that have been earned from the nodes this address has + # staked into. + claimed_rewards: int = 0 + +@dataclass +class DailyRewardInfoNode: + block: int + lifetime_rewards: int + timestamp: int + +@dataclass +class Registration: + operator: bytes + pubkey_bls: bytes + pubkey_ed25519: bytes + sig_bls: bytes + sig_ed25519: bytes + timestamp: float + + def __post_init__(self): + self.operator = eth_format(self.operator) + self.pubkey_bls = self.pubkey_bls.hex() + self.pubkey_ed25519 = self.pubkey_ed25519.hex() + self.sig_bls = self.sig_bls.hex() + self.sig_ed25519 = self.sig_ed25519.hex() + diff --git a/src/staking/fetcher.py b/src/staking/fetcher.py new file mode 100644 index 0000000..1eabaf5 --- /dev/null +++ b/src/staking/fetcher.py @@ -0,0 +1,589 @@ +#!/usr/bin/env python3 +import json +import subprocess +import time + +import eth_utils + +from ..config_validate import validate_log_config, validate_contract_addresses, validate_web3_client, validate_oxen_rpc +from ..staking.arbitrum import ( + get_new_contribution_contracts, + update_contribution_contract_details, populate_events_with_main_arg, +) +from .. import config +from ..staking.dataclasses import RewardsInfo, DBNodeExit +from ..db.util import ( + assert_all_dict_values_are_within_sqlite_integer_range, + is_db_initialized, + init_db, +) +from ..staking.read import DBReaderStaking +from ..staking.write import DBWriterStaking +from ..log import Log +from ..oxen.rpc import ServiceNode, OxenRPC, NetworkInfo +from ..util import format_seconds +from ..log.time_keeper import TimeKeeper +from ..util.parse import parse_bls_pubkey, eth_format +from ..web3client.abi_manager import ABIManager +from ..web3client.client import Web3Client +from ..web3client.contracts.reward_rate_pool import RewardRatePoolInterface +from ..web3client.contracts.service_node_contribution import ( + ServiceNodeContributionInterface, +) +from ..web3client.contracts.service_node_contribution_factory import ( + ServiceNodeContributionFactory, +) +from ..web3client.contracts.service_node_rewards import ServiceNodeRewardsInterface +from ..web3client.contracts.token import TokenInterface + + +class App: + def __init__(self, name, run_once_as_script=False): + super().__init__() + log = Log(name, enable_perf=config.backend.performance_logging) + log.set_level(config.backend.log_level) + self.log = log.logger + self.run_once_as_script = run_once_as_script + + validate_log_config(config.backend) + validate_contract_addresses(config.backend) + + self.arbitrum_updates_disabled = config.backend.ws_enabled + if self.arbitrum_updates_disabled: + self.log.warning( + "Arbitrum updates are disabled because ws_enabled is set to True, the separate websocket fetcher should be used instead") + else: + self.log.info("Arbitrum updates are enabled") + validate_web3_client(config) + + validate_oxen_rpc(config.backend) + + git_rev = subprocess.run( + ["git", "rev-parse", "--short=9", "HEAD"], stdout=subprocess.PIPE, text=True + ) + self.git_rev = git_rev.stdout.strip() if git_rev.returncode == 0 else "(unknown)" + + # Creates a generic logger to pipe other packages logs into the main app logger + generic_logger = Log(None) + generic_logger.set_level( + config.backend.log_level_generic + if config.backend.log_level_generic is not None + else config.backend.log_level + ) + + if not is_db_initialized(config.backend.sqlite_db): + self.log.info( + "Initializing database {} with schema {}".format( + config.backend.sqlite_db, config.backend.sqlite_schema + ) + ) + init_db(config.backend.sqlite_db, config.backend.sqlite_schema) + + self.db_reader = DBReaderStaking( + db_path=config.backend.sqlite_db, + log_level=config.backend.log_level, + perf=config.backend.performance_logging, + ) + self.db_writer = DBWriterStaking( + db_path=config.backend.sqlite_db, + log_level=config.backend.log_level, + perf=config.backend.performance_logging, + ) + + rpc_url = config.backend.rpc_shared + rpc_cache = ( + config.backend.rpc_fetcher_cache + if config.backend.rpc_fetcher_cache + else config.backend.rpc_shared_cache + ) + + self.rpc = OxenRPC( + logger=self.log, + rpc_url=rpc_url, + cache_seconds=rpc_cache, + usage_tracking=config.backend.rpc_fetcher_usage_logging, + ) + + self.loop_sleep_refresh_rate_seconds = rpc_cache if rpc_cache > 0 else 1 + + self.arbitrum_details_next_update_time = 0 + + self.web3_client = Web3Client( + provider_urls=config.backend.web3_provider_urls, + caller_address=config.backend.web3_caller_address, + private_key=config.backend.web3_private_key, + logger=self.log, + abi_manager=ABIManager(db_writer=self.db_writer, abi_dir=config.backend.abi_dir), + ) + + self.log.info( + f"Using contract addresses:\n Token: {config.backend.addr_token}\n SN Rewards: {config.backend.addr_sn_rewards}\n Reward Rate Pool: {config.backend.addr_reward_rate_pool}\n SN Contribution Factory: {config.backend.addr_sn_contrib_factory}") + + self.token_contract = TokenInterface( + web3_client=self.web3_client, contract_address=config.backend.addr_token + ) + self.service_node_rewards = ServiceNodeRewardsInterface( + web3_client=self.web3_client, + contract_address=config.backend.addr_sn_rewards, + scanner_safety_blocks=config.backend.arbitrum_rescan_safety_blocks, + scan_start_chunk_size=config.backend.arbitrum_scan_start_chunk_size + ) + self.reward_rate_pool = RewardRatePoolInterface( + web3_client=self.web3_client, contract_address=config.backend.addr_reward_rate_pool + ) + self.service_node_contribution_factory = ServiceNodeContributionFactory( + web3_client=self.web3_client, + contract_address=config.backend.addr_sn_contrib_factory, + scanner_safety_blocks=config.backend.arbitrum_rescan_safety_blocks, + scan_start_chunk_size=config.backend.arbitrum_scan_start_chunk_size + ) + self.service_node_contribution = ServiceNodeContributionInterface( + web3_client=self.web3_client, + contract_address=config.backend.addr_sn_contrib, + ) + self.service_node_contribution_multi: dict[str, ServiceNodeContributionInterface] = {} + + self.time_keeper = TimeKeeper( + logger=Log("time_keeper").logger, + max_events=config.backend.max_time_keeper_events, + ) + + self.last_new_sn_event = 0 + self.contract_id_map = {} + + self.bootstrap() + + def bootstrap(self): + self.log.info("Bootstrapping") + self.log.perf.start("bootstrap") + + contribution_contract_addresses = self.db_reader.get_contribution_contract_addresses() + self.log.debug( + "Found {} contribution contract addresses".format(len(contribution_contract_addresses)) + ) + + contract_details = [ + {"address": interface.contract_address, "name": interface.abi_name} + for interface in [ + self.service_node_contribution, + self.reward_rate_pool, + self.service_node_rewards, + self.token_contract, + self.service_node_contribution_factory, + ] + ] + for address in contribution_contract_addresses: + self.service_node_contribution_multi[address] = ServiceNodeContributionInterface( + self.web3_client, + address, + ) + contract_details.append( + {"address": address, "name": ServiceNodeContributionInterface.abi_name} + ) + + self.db_writer.write_smart_contract_details_to_db(contract_details) + + self.log.perf.end("bootstrap") + + def run(self): + t1_event_loop_exception_count = 0 + t2_event_loop_exception_count = 0 + try: + network = self.rpc.get_network_info_from_network() + self.update_network_details_and_nodes(network) + while True: + try: + self.log.perf.start("loop") + network = self.rpc.get_network_info_from_network() + + network_last_fetched_height = ( + self.db_reader.get_last_fetched_network_block_height() + ) + network_last_commited_height = ( + self.db_reader.get_last_commited_network_block_height() + ) + self.log.debug( + "Last fetched height: {}, Immutable height: {}, Commited height {}, Current height: {}, next block timestamp: {}, ".format( + network_last_fetched_height, + network.immutable_block_height, + network_last_commited_height, + network.block_height, + network.pulse_target_timestamp, + ) + ) + + if not self.arbitrum_updates_disabled and time.time() >= self.arbitrum_details_next_update_time: + self.time_keeper.add("arb_update") + self.update_arbitrum_details() + self.time_keeper.end("arb_update") + + if network.immutable_block_height > network_last_commited_height: + self.time_keeper.add("db_migrate_and_update_exit_list") + self.db_writer.write_nodes_to_main_db(network.immutable_block_height) + self.update_exit_list() + self.time_keeper.end("db_migrate_and_update_exit_list") + + if (network.block_height - 1) > network_last_fetched_height: + self.time_keeper.add("net_update") + self.update_network_details_and_nodes(network) + self.time_keeper.end("net_update") + + self.log.perf.end("loop") + self.time_keeper.log_time_keeper() + self.rpc.usage_tracker.log_usage() + if config.backend.write_rpc_fail_reasons_to_file: + self.rpc.usage_tracker.write_failure_reasons_to_file(f"rpc-usage-failure-reasons-fetcher.txt") + + now = time.time() + + sleep_seconds = max( + self.loop_sleep_refresh_rate_seconds, + (network.pulse_target_timestamp - 5 if self.arbitrum_updates_disabled else + min(self.arbitrum_details_next_update_time, network.pulse_target_timestamp) + ) - now, + ) + + self.log.info( + "Sleeping for {}s ({}) (Target Event: {})".format( + format_seconds(sleep_seconds), + format_seconds(now + sleep_seconds, 0), + ( + "network_update" + if sleep_seconds == network.pulse_target_timestamp - 5 - now + else ( + "arb_update" + if sleep_seconds == self.arbitrum_details_next_update_time - now + else "min_refresh" + ) + ), + ) + ) + + if self.run_once_as_script: + self.log.info("run_once_as_script is True, exiting...") + return + + time.sleep(sleep_seconds) + + except Exception as e: + self.log.error("Error in event loop task") + self.log.exception(e) + + t1_event_loop_exception_count += 1 + + if t2_event_loop_exception_count > 3: + self.log.warning( + "Too many t2 event loop exceptions, sleeping for 2 minutes before continuing" + ) + t2_event_loop_exception_count = 0 + time.sleep(120) + elif t1_event_loop_exception_count > 10: + self.log.warning( + "Too many t1 event loop exceptions, sleeping for 30 seconds before continuing" + ) + t2_event_loop_exception_count += 1 + t1_event_loop_exception_count = 0 + time.sleep(30) + else: + self.log.error("Sleeping for 1 second before continuing") + time.sleep(1) + + except KeyboardInterrupt: + self.log.info("Application exiting...") + + def update_network_details_and_nodes( + self, + network: NetworkInfo, + ): + self.log.info("Update service node list task start") + parsed_nodes, contributor_stake_map, current_height, node_count, total_staked, active_node_count = self.fetch_service_node_list() + + self.db_writer.write_nodes_to_staging_db( + current_height, parsed_nodes, contributor_stake_map + ) + + self.db_writer.write_network_info_to_db(network=network, node_count=node_count, total_staked=total_staked, + active_node_count=active_node_count) + + rewards_info = self.get_rewards_info() + self.db_writer.write_rewards_info_to_db(rewards_info) + now = time.time() + self.db_writer.update_daily_rolling_rewards(rewards_info, current_height, now) + + self.log.info("Scheduled task finish") + + def fetch_service_node_list(self): + self.log.perf.start("fetch_service_node_list") + current_height = None + parsed_nodes = [] + contributions = [] + active_node_count = 0 + total_staked = 0 + + try: + res = self.rpc.get_service_nodes().get() + current_height = res.get("height") + self.log.debug("Fetched service node list at height {}".format(current_height)) + + nodes: list[ServiceNode] = res.get("service_node_states") + self.log.debug("Fetched {} service nodes".format(len(nodes))) + + new_seed_events = self.db_reader.get_arbitrum_events_by_name("NewSeededServiceNode", + from_block=self.last_new_sn_event + 1) + new_sn_events = self.db_reader.get_arbitrum_events_by_name("NewServiceNodeV2", + from_block=self.last_new_sn_event + 1) + + if len(new_seed_events)== 0 and len(new_sn_events) == 0: + self.log.warning("No new service node events found, waiting for new events") + return parsed_nodes, contributions, current_height, len(parsed_nodes), total_staked, active_node_count + + for event in new_seed_events: + self.contract_id_map[parse_bls_pubkey(event.args["blsPubkey"])] = event.args["serviceNodeID"] + if event.block > self.last_new_sn_event: + self.last_new_sn_event = event.block + + for event in new_sn_events: + self.contract_id_map[parse_bls_pubkey(event.args["pubkey"])] = event.args["serviceNodeID"] + if event.block > self.last_new_sn_event: + self.last_new_sn_event = event.block + + for node in nodes: + pubkey_bls = None + try: + if node.get("active"): + active_node_count += 1 + + pubkey_bls = node.get("pubkey_bls") + contract_id = self.contract_id_map.get(pubkey_bls) + if contract_id is None: + continue + node["contract_id"] = contract_id + + if node["contract_id"] is None: + self.log.warning( + "Contract ID not found for node with BLS pubkey: {}".format(pubkey_bls) + ) + #assert node["contract_id"] is not None + + # Remove some fields that might appear if field:all is passed to the rpc + if "portions_for_operator" in node: + del node["portions_for_operator"] + + # Convert some ints to strings to avoid overflowing the sqlite integer type + node["swarm_id"] = str(node["swarm_id"]) + + lokinet_version = node.get("lokinet_version", None) + node["lokinet_version"] = ( + json.dumps(lokinet_version) if lokinet_version is not None else None + ) + + pulse_votes = node.get("pulse_votes", None) + node["pulse_votes"] = ( + json.dumps(pulse_votes) if pulse_votes is not None else None + ) + + service_node_version = node.get("service_node_version", None) + node["service_node_version"] = ( + json.dumps(service_node_version) + if service_node_version is not None + else None + ) + + storage_server_version = node.get("storage_server_version", None) + node["storage_server_version"] = ( + json.dumps(storage_server_version) + if storage_server_version is not None + else None + ) + + node_staked = node.get("total_contributed", 0) + total_staked += node_staked + + assert_all_dict_values_are_within_sqlite_integer_range(node) + + for contributor in node.get("contributors", []): + contributor_address = None + try: + amount = contributor.get("amount") + assert amount is not None + + contributor_address = contributor.get("address") + assert contributor_address is not None + + contributions.append( + { + "address": contributor_address, + "beneficiary": contributor.get("beneficiary"), + "contract_id": contract_id, + "amount": amount, + } + ) + + except Exception as e: + self.log.error(f"Error processing contributor {contributor_address} for node {pubkey_bls}") + self.log.exception(e) + continue + + parsed_nodes.append(node) + + except Exception as e: + self.log.error("Error processing node {}".format(pubkey_bls)) + self.log.exception(e) + continue + + except Exception as e: + self.log.error("Error fetching and parsing service node list") + self.log.exception(e) + finally: + self.log.perf.end("update_service_node_list") + return parsed_nodes, contributions, current_height, len(parsed_nodes), total_staked, active_node_count + + def update_exit_list(self): + self.log.perf.start("update_exit_list") + self.log.info("Update exit list task start") + exit_liquidation_list = self.rpc.bls_exit_liquidation_list().get() + + if exit_liquidation_list is None: + self.log.warning("bls_exit_liquidation_list is None, fetching exit list failed") + return + + exit_events = [] + for entry in exit_liquidation_list: + + pubkey_bls = entry.get("info").get("bls_public_key") + if pubkey_bls is None: + pubkey_bls = entry.get("info").get("pubkey_bls") + if pubkey_bls is None: + self.log.warning(f"info.bls_public_key is None for bls_exit_liquidation_list entry: {entry}") + continue + + exit_type = entry.get("type") + exit_events.append( + DBNodeExit( + pubkey_bls=pubkey_bls, + deregistration_height=entry.get("height") if exit_type == "deregister" else None, + exit_type=exit_type, + liquidation_height=entry.get("liquidation_height"), + ) + ) + + self.log.debug("Processed {} exit events".format(len(exit_events))) + self.db_writer.write_exit_list_to_db(exit_events) + self.log.info("Update exit list task finish") + self.log.perf.end("update_exit_list") + + def get_rewards_info(self): + self.log.perf.start("update_rewards_details") + self.log.debug("Update rewards details task start") + rewards_info = [] + try: + accrued_rewards_json = self.rpc.get_accrued_rewards().get() + + assert accrued_rewards_json is not None, "Accrued rewards request failed" + assert accrued_rewards_json["status"] == "OK", "Accrued rewards request failed {}".format( + accrued_rewards_json) + + assert "balances" in accrued_rewards_json, "Accrued rewards request failed, 'balances' key was missing: {}".format( + accrued_rewards_json) + + # Populate (Binary ETH wallet address -> accrued_rewards) table + for balance in accrued_rewards_json.get("balances", []): + # Ignore non-ethereum addresses (e.g. left oxen rewards, not relevant) + address = balance.get("address") + if address is None or not eth_utils.is_address(address): + self.log.warning("Invalid address {}".format(address)) + continue + + address = eth_format(address) + + amount = balance.get("amount") + lifetime_liquidated_stakes = balance.get("lifetime_liquidated_stakes") + lifetime_locked_stakes = balance.get("lifetime_locked_stakes") + lifetime_rewards = balance.get("lifetime_rewards") + lifetime_unlocked_stakes = balance.get("lifetime_unlocked_stakes") + locked_stakes = balance.get("locked_stakes") + timelocked_stakes = balance.get("timelocked_stakes") + + rewards_info.append(RewardsInfo( + address=address, + amount=amount, + lifetime_liquidated_stakes=lifetime_liquidated_stakes, + lifetime_locked_stakes=lifetime_locked_stakes, + lifetime_rewards=lifetime_rewards, + lifetime_unlocked_stakes=lifetime_unlocked_stakes, + locked_stakes=locked_stakes, + timelocked_stakes=timelocked_stakes, + )) + + except Exception as e: + self.log.error("Error fetching and parsing rewards details") + self.log.exception(e) + finally: + self.log.perf.end("update_rewards_details") + return rewards_info + + def update_arbitrum_details(self): + try: + self.log.perf.start("update_arbitrum_details") + self.log.info("Update arbitrum details task start") + + last_event_block_height = self.db_reader.get_last_fetched_arbitrum_event_block_height() + current_block = self.web3_client.web3.eth.block_number + end_block = current_block - 1 + + service_node_rewards_balance = self.token_contract.balance_of(self.service_node_rewards.contract_address) + reward_rate_pool_balance = self.token_contract.balance_of(self.reward_rate_pool.contract_address) + self.log.debug("Arbitrum info: service node rewards balance {}, reward rate pool balance {}".format( + service_node_rewards_balance, reward_rate_pool_balance)) + self.db_writer.write_arbitrum_info_to_db(current_block, service_node_rewards_balance, + reward_rate_pool_balance) + + new_contribution_contracts, new_contribution_events = get_new_contribution_contracts( + self.web3_client, + self.log, + self.service_node_contribution_factory, + last_event_block_height, + end_block, + ) + + # Writing contract details to db + + new_contracts = [] + for contract in new_contribution_contracts: + self.service_node_contribution_multi[contract.contract_address] = contract + new_contracts.append( + {"address": contract.contract_address, "name": contract.abi_name} + ) + + self.db_writer.write_smart_contract_details_to_db(new_contracts) + + # NOTE: Writes events to db BEFORE writing contribution contracts to db so the events are available for the "recent_add_node_events_since_last_update" function + + events = self.service_node_rewards.event_scanner.run( + last_block=last_event_block_height, + end_block=end_block, + ) + + events.extend(new_contribution_events) + populate_events_with_main_arg(events) + + self.db_writer.write_arbitrum_events_to_db(events) + + # Writing contribution contract details to db (if there are any) + + contrib_contract_list = list(self.service_node_contribution_multi.values()) + if len(contrib_contract_list) > 0: + contract_details_list, contributions_list = update_contribution_contract_details( + self.web3_client, self.log, contrib_contract_list + ) + + self.db_writer.write_contribution_contracts_to_db(contract_details_list, contributions_list) + else: + self.log.info("No contribution contracts to write to db") + + self.arbitrum_details_next_update_time = time.time() + config.backend.refresh_rate_seconds_arbitrum + self.log.perf.end("update_arbitrum_details") + + except Exception as e: + self.log.error("Error fetching and parsing arbitrum details") + self.log.exception(e) diff --git a/src/staking/read.py b/src/staking/read.py new file mode 100644 index 0000000..2c11aaa --- /dev/null +++ b/src/staking/read.py @@ -0,0 +1,510 @@ +import sqlite3 +from contextlib import closing + +from ..db.read import DBReader +from .dataclasses import DBNode, DBContributionMain, DBNetworkInfo, DBContributionContract, \ + DBContributionContractContribution, SmartContractABI, ArbitrumInfo, VestingContract, RewardsInfo, \ + DailyRewardInfoNode +from ..db.util import sql_connect_in_read_mode +from ..log import Log +from ..util.parse import eth_format +from ..web3client.event_scanner import ProcessedEvent + + +class DBReaderStaking: + def __init__(self, db_path: str, log_level: int, perf: bool = False): + self.log = Log("db_reader", log_level, enable_perf=perf).logger + self.db_path = db_path + + def get_last_fetched_network_block_height(self) -> int: + self.log.perf.start("get_last_fetched_network_block_height") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("SELECT MAX(fetched_block_height) FROM service_nodes_staging") + (fetched_block_height,) = cursor.fetchone() + self.log.debug( + "get_last_fetched_network_block_height result: {}".format(fetched_block_height) + ) + self.log.perf.end("get_last_fetched_network_block_height") + return fetched_block_height if fetched_block_height is not None else 0 + + def get_last_commited_network_block_height(self) -> int: + self.log.perf.start("get_last_commited_network_block_height") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("SELECT MAX(fetched_block_height) FROM service_nodes_main") + (commited_block_height,) = cursor.fetchone() + self.log.debug( + "get_last_commited_network_block_height result: {}".format( + commited_block_height + ) + ) + self.log.perf.end("get_last_commited_network_block_height") + return commited_block_height if commited_block_height is not None else 0 + + def get_network_info(self): + self.log.perf.start("get_network_info") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("SELECT * FROM network_info LIMIT 1") + network_info = DBNetworkInfo(*cursor.fetchone()) + + self.log.debug("Network Info: {}".format(network_info)) + self.log.perf.end("get_network_info") + return network_info + + def get_last_fetched_arbitrum_event_block_height(self) -> int: + self.log.perf.start("get_last_fetched_arbitrum_event_block_height") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("SELECT MAX(block) FROM arbitrum_events") + (fetched_block_height,) = cursor.fetchone() + self.log.debug( + "get_last_fetched_arbitrum_event_block_height result: {}".format( + fetched_block_height + ) + ) + self.log.perf.end("get_last_fetched_arbitrum_event_block_height") + return fetched_block_height if fetched_block_height is not None else 0 + + def get_contribution_contract_contributors(self, address:str): + self.log.perf.start("get_contribution_contract_contributors") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("""SELECT * FROM contribution_contracts_contributions WHERE contract_address = ?""", (address,)) + contributors = [DBContributionContractContribution(*contribution) for contribution in cursor.fetchall()] + self.log.debug("Contributors: {}".format(len(contributors))) + self.log.perf.end("get_contribution_contract_contributors") + return contributors + + def get_contribution_contracts(self): + self.log.perf.start("get_contribution_contracts") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("""SELECT * FROM contribution_contracts""") + contracts = cursor.fetchall() + + parsed_contracts = {} + for contract in contracts: + contract_dict = DBContributionContract(*contract, contributors=[], events=[]) + parsed_contracts[contract_dict.address] = contract_dict + + cursor.execute( + """ + SELECT * FROM contribution_contracts_contributions + """ + ) + contributions = cursor.fetchall() + for contribution in contributions: + contribution_dict = DBContributionContractContribution(*contribution) + parsed_contracts[contribution_dict.contract_address].contributors.append( + contribution_dict + ) + + self.log.debug("Parsed contribution contracts: {}".format(len(parsed_contracts))) + self.log.perf.end("get_contribution_contracts") + return parsed_contracts + + def get_contribution_contracts_non_finalized(self): + self.log.perf.start("get_contribution_contracts") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("""SELECT * FROM contribution_contracts""") + contracts = cursor.fetchall() + + parsed_contracts = {} + for contract in contracts: + contract_dict = DBContributionContract(*contract, contributors=[], events=[]) + parsed_contracts[contract_dict.address] = contract_dict + + cursor.execute( + """ + SELECT * FROM contribution_contracts_contributions where status < 3 + """ + ) + contributions = cursor.fetchall() + for contribution in contributions: + contribution_dict = DBContributionContractContribution(*contribution) + parsed_contracts[contribution_dict.contract_address].contributors.append( + contribution_dict + ) + + self.log.debug("Parsed contribution contracts: {}".format(len(parsed_contracts))) + self.log.perf.end("get_contribution_contracts") + return parsed_contracts + + + def get_contribution_contract_addresses(self): + self.log.perf.start("get_contribution_contracts") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT address FROM contribution_contracts + """ + ) + addresses = cursor.fetchall() + self.log.debug("Contract addresses: {}".format(len(addresses))) + self.log.perf.end("get_contribution_contracts") + return [address[0] for address in addresses] + + def get_nodes(self): + self.log.perf.start("get_nodes") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + parsed_nodes = {} + + # TODO: investigate using a join or something less messy than two select * queries + cursor.execute("""SELECT * FROM service_nodes_main""") + + for node in cursor.fetchall(): + node_dict = DBNode(*node, contributors=[], events=[]) + parsed_nodes[node_dict.contract_id] = node_dict + + # We want to sort by fetched_block_height in ascending order so later updates overwrite earlier ones + cursor.execute( + """SELECT * FROM service_nodes_staging ORDER BY fetched_block_height ASC""" + ) + + for node in cursor.fetchall(): + node_dict = DBNode(*node, exit_type=None, deregistration_height=None, liquidation_height=None, contributors=[], events=[]) + existing_node = parsed_nodes.get(node_dict.contract_id) + if existing_node is not None: + existing_node.exit_type = node_dict.exit_type + existing_node.deregistration_height = node_dict.deregistration_height + existing_node.liquidation_height = node_dict.liquidation_height + parsed_nodes[node_dict.contract_id] = node_dict + + + cursor.execute("""SELECT * from service_nodes_contributions_main""") + + db_contributions_main = [DBContributionMain(*contribution) for contribution in cursor.fetchall()] + + # We want to sort by fetched_block_height in ascending order so later updates overwrite earlier ones + cursor.execute( + """SELECT * from service_nodes_contributions_staging ORDER BY fetched_block_height ASC""" + ) + + db_contributions_staging = [DBContributionMain(*contribution) for contribution in cursor.fetchall()] + + parsed_contributions = {} + for contribution_dict in db_contributions_main + db_contributions_staging: + # TODO: there has to be a better way to override the old data with new data + key = f"{contribution_dict.contract_id}{contribution_dict.address}" + parsed_contributions[key] = contribution_dict + + for contribution_dict in parsed_contributions.values(): + parsed_nodes[contribution_dict.contract_id].contributors.append( + contribution_dict + ) + + contract_ids = list(parsed_nodes.keys()) + + placeholder= '?' # For SQLite. See DBAPI paramstyle. + placeholders= ', '.join(placeholder for unused in contract_ids) + query= 'SELECT * FROM arbitrum_events WHERE main_arg IN (%s) ORDER BY block DESC, log_index DESC' % placeholders + cursor.execute(query, contract_ids) + + for event in cursor.fetchall(): + processed_event = ProcessedEvent(*event) + try: + contract_id = int(processed_event.main_arg) + parsed_nodes[contract_id].events.append(processed_event) + except Exception as e: + self.log.error("Error processing event: {}".format(e)) + continue + + nodes_list = list(parsed_nodes.values()) + + self.log.debug("Parsed nodes: {}".format(len(nodes_list))) + self.log.perf.end("get_nodes") + return list(parsed_nodes.values()) + + def get_contribution_addresses(self): + self.log.perf.start("get_contribution_addresses") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + addresses = set() + cursor.execute("""SELECT address, beneficiary from service_nodes_contributions_main""") + cursor.execute("""SELECT address, beneficiary from service_nodes_contributions_staging ORDER BY fetched_block_height ASC""") + + for address, beneficiary in cursor.fetchall(): + addresses.add(address) + if beneficiary is not None: + addresses.add(beneficiary) + + self.log.debug("Contribution addresses: {}".format(len(addresses))) + self.log.perf.end("get_contribution_addresses") + return addresses + + def get_rewards_info(self): + self.log.perf.start("get_rewards_info") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("SELECT * FROM rewards_info") + rewards = [RewardsInfo(*info) for info in cursor.fetchall()] + rewards_info = {info.address: info for info in rewards} + self.log.debug("Rewards info: {}".format(len(rewards_info))) + self.log.perf.end("get_rewards_info") + return rewards_info + + def get_rewards_info_for_address(self, address: str): + self.log.perf.start("get_rewards_info_for_address") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("SELECT * FROM rewards_info WHERE address = ?", (address,)) + if cursor.rowcount == 0: + return None + info = RewardsInfo(*cursor.fetchone()) + self.log.debug("Rewards info: {}".format(info)) + self.log.perf.end("get_rewards_info_for_address") + return info + + def get_daily_rewards_info_for_address(self, address: str, from_block: int = 0): + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("SELECT block, lifetime_rewards, timestamp FROM daily_rewards_info WHERE address = ? AND block >= ?", (address, from_block)) + info = [DailyRewardInfoNode(*node) for node in cursor.fetchall()] + return info + + def get_smart_contract_abis(self): + self.log.perf.start("get_smart_contract_abis") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM smart_contract_abis + """ + ) + abis = [SmartContractABI(*abi) for abi in cursor.fetchall()] + self.log.debug("Smart contract abis: {}".format(len(abis))) + self.log.perf.end("get_smart_contract_abis") + return abis + + def get_smart_contract_abi(self, name: str): + self.log.perf.start("get_smart_contract_abi") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM smart_contract_abis WHERE name = ? + """, + (name,), + ) + abi = SmartContractABI(*cursor.fetchone()) + self.log.debug("Smart contract abi: {}".format(abi)) + self.log.perf.end("get_smart_contract_abi") + return abi + + def get_smart_contract_names(self) -> list[str]: + self.log.perf.start("get_smart_contract_names") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT name FROM smart_contract_abis + """ + ) + names = [name[0] for name in cursor.fetchall()] + self.log.debug("Smart contract names: {}".format(len(names))) + self.log.perf.end("get_smart_contract_names") + return names + + def get_smart_contract_addresses(self): + self.log.perf.start("get_smart_contract_addresses") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT address, name FROM smart_contracts + """ + ) + addresses = [ + {"address": address, "name": name} for address, name in cursor.fetchall() + ] + self.log.debug("Smart contract addresses: {}".format(len(addresses))) + self.log.perf.end("get_smart_contract_addresses") + return addresses + + def get_smart_contract_addresses_core(self): + self.log.perf.start("get_smart_contract_addresses_core") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT address, name FROM smart_contracts WHERE name IN ('ServiceNodeRewards', 'ServiceNodeContributionFactory', 'ServiceNodeRewards') + """ + ) + addresses = [ + {"address": address, "name": name} for address, name in cursor.fetchall() + ] + self.log.debug("Smart contract addresses: {}".format(len(addresses))) + self.log.perf.end("get_smart_contract_addresses_core") + return addresses + + def get_smart_contract_address(self, name: str): + self.log.perf.start("get_smart_contract_address") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT address FROM smart_contracts WHERE name = ? + """, + (name,), + ) + address = cursor.fetchone() + self.log.debug("Smart contract address: {}".format(address)) + self.log.perf.end("get_smart_contract_address") + return address[0] + + def get_arbitrum_events(self, from_block = 0, names: list = None): + self.log.perf.start("get_arbitrum_events") + assert from_block >= 0, "from_block must be >= 0" + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + self.log.debug(f"Getting events from block {from_block} with names {names}") + if names is None: + cursor.execute( + """ + SELECT * FROM arbitrum_events WHERE block >= ? + """, + (from_block,), + ) + else: + placeholder= '?' # For SQLite. See DBAPI paramstyle. + placeholders= ', '.join(placeholder for unused in names) + query= 'SELECT * FROM arbitrum_events WHERE block >= ? AND name IN ({})'.format(placeholders) + cursor.execute(query, (from_block, *names)) + + events = [ProcessedEvent(*event) for event in cursor.fetchall()] + self.log.debug("Arbitrum events: {}".format(len(events))) + self.log.perf.end("get_arbitrum_events") + return events + + def get_arbitrum_events_by_name(self, name: str, from_block = 0): + self.log.perf.start("get_arbitrum_events_by_name") + assert from_block >= 0, "from_block must be >= 0" + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM arbitrum_events WHERE name = ? + """, + (name,), + ) + events = [ProcessedEvent(*event) for event in cursor.fetchall()] + self.log.debug("Arbitrum events: {}".format(len(events))) + self.log.perf.end("get_arbitrum_events_by_name") + return events + + def get_arbitrum_events_by_main_args_desc(self, main_args: list[str]): + self.log.perf.start("get_arbitrum_events_by_main_arg") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM arbitrum_events WHERE main_arg IN ({}) ORDER BY block DESC, log_index DESC + """.format(",".join(["?"] * len(main_args))), + (tuple(main_args)), + ) + events = [ProcessedEvent(*event) for event in cursor.fetchall()] + self.log.debug("Arbitrum events: {}".format(len(events))) + self.log.perf.end("get_arbitrum_events_by_main_arg") + return events + + + def get_arbitrum_event_main_args_by_name(self, name: str, from_block = 0): + self.log.perf.start("get_arbitrum_event_main_args_by_name") + assert from_block >= 0, "from_block must be >= 0" + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT main_arg FROM arbitrum_events WHERE name = ? AND block >= ? + """, + (name, from_block), + ) + events = cursor.fetchall() + addresses = [event[0] for event in events] + self.log.debug("Arbitrum Event Args: {}".format(len(addresses))) + self.log.perf.end("get_arbitrum_event_main_args_by_name") + return addresses + + + def get_arbitrum_events_page(self, args=None): + if args is None: + args = [1000, 0] + self.log.perf.start("get_arbitrum_events") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + limit = args[0] if len(args) > 0 else 1000 + skip = args[1] if len(args) > 1 else 0 + + cursor.execute( + """ + SELECT * FROM arbitrum_events ORDER BY block DESC LIMIT ? OFFSET ? + """, + (limit, skip), + ) + events = [ProcessedEvent(*event) for event in cursor.fetchall()] + self.log.debug("Arbitrum events: {}".format(len(events))) + self.log.perf.end("get_arbitrum_events") + + cursor.execute("SELECT COUNT(*) FROM arbitrum_events") + total = cursor.fetchone()[0] + + return events, limit, skip, total + + def get_arbitrum_info(self): + self.log.perf.start("get_arbitrum_info") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute("SELECT * FROM arbitrum_info ORDER BY block DESC LIMIT 1") + info = ArbitrumInfo(*cursor.fetchone()) + + self.log.debug("Arbitrum info: {}".format(info)) + self.log.perf.end("get_arbitrum_info") + return info + + def get_arbitrum_events_for_stake_contrat_id(self, contract_id: int): + self.log.perf.start("get_events_for_stake_contrat_id") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM arbitrum_events WHERE main_arg = ? ORDER BY block DESC + """, + (contract_id,), + ) + events = [ProcessedEvent(*event) for event in cursor.fetchall()] + self.log.debug("Arbitrum events: {}".format(len(events))) + self.log.perf.end("get_events_for_stake_contrat_id") + return events + + def get_vesting_contracts(self) -> list[VestingContract]: + self.log.perf.start("get_vesting_contracts") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT * FROM vesting_contracts + """ + ) + contracts = [VestingContract(*contract) for contract in cursor.fetchall()] + self.log.debug("Vesting contracts: {}".format(len(contracts))) + self.log.perf.end("get_vesting_contracts") + return contracts + + def has_vesting_contracts(self) -> bool: + self.log.perf.start("has_vesting_contracts") + with closing(sql_connect_in_read_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + SELECT COUNT(*) FROM vesting_contracts + """ + ) + count = cursor.fetchone()[0] + self.log.debug("Vesting contracts: {}".format(count)) + self.log.perf.end("has_vesting_contracts") + return count > 0 diff --git a/src/staking/schema.sql b/src/staking/schema.sql new file mode 100644 index 0000000..9f12f6a --- /dev/null +++ b/src/staking/schema.sql @@ -0,0 +1,253 @@ + +PRAGMA journal_mode=WAL; + +CREATE TABLE service_nodes_staging ( + active BOOLEAN NOT NULL, + contract_id INTEGER NOT NULL, + decommission_count INTEGER NOT NULL, + earned_downtime_blocks INTEGER NOT NULL, + fetched_block_height INTEGER NOT NULL, + funded BOOLEAN NOT NULL, + is_liquidatable BOOLEAN NOT NULL, + is_removable BOOLEAN NOT NULL, + last_reward_block_height INTEGER NOT NULL, +-- last_reward_transaction_index INTEGER NOT NULL, + last_uptime_proof INTEGER NOT NULL, + lokinet_version TEXT, + operator_address BLOB NOT NULL, + operator_fee INTEGER NOT NULL, + payable BOOLEAN NOT NULL, +-- portions_for_operator TEXT NOT NULL, -- too large to be an int + pubkey_bls BLOB NOT NULL, + pubkey_ed25519 BLOB NOT NULL, +-- pubkey_x25519 BLOB NOT NULL, + public_ip TEXT, + pulse_votes TEXT, -- JSON encoded dict + quorumnet_port INTEGER, + registration_height INTEGER NOT NULL, + registration_hf_version INTEGER NOT NULL, + requested_unlock_height INTEGER NOT NULL, + service_node_pubkey BLOB NOT NULL, + service_node_version TEXT, -- JSON encoded list of integers + staking_requirement INTEGER NOT NULL, + state_height INTEGER NOT NULL, + storage_lmq_port INTEGER, + storage_port INTEGER, + storage_server_version TEXT, -- JSON encoded list of integers + swarm TEXT NOT NULL, + swarm_id TEXT NOT NULL, -- too large to be an int + total_contributed INTEGER NOT NULL, + + PRIMARY KEY(contract_id, fetched_block_height) +); + + +CREATE INDEX service_nodes_staging_contract_id_idx ON service_nodes_staging(contract_id); +CREATE INDEX service_nodes_staging_fetched_block_height_desc_idx + ON service_nodes_staging(fetched_block_height DESC); + +CREATE TABLE service_nodes_contributions_staging ( + address BLOB NOT NULL, + amount INTEGER NOT NULL, + beneficiary BLOB, + contract_id INTEGER NOT NULL, + fetched_block_height INTEGER NOT NULL, + + FOREIGN KEY (contract_id, fetched_block_height) REFERENCES service_nodes_staging(contract_id, fetched_block_height), + PRIMARY KEY (contract_id, address, fetched_block_height) +); + +CREATE INDEX service_nodes_contributions_staging_contract_id_idx ON service_nodes_contributions_staging(contract_id); +CREATE INDEX service_nodes_contributions_staging_fetched_block_height_desc_idx ON service_nodes_contributions_staging(fetched_block_height DESC); +CREATE INDEX service_nodes_contributions_staging_fetched_block_height_asc_idx ON service_nodes_contributions_staging(fetched_block_height ASC); + + +CREATE TABLE service_nodes_main ( + active BOOLEAN NOT NULL, + contract_id INTEGER NOT NULL, + decommission_count INTEGER NOT NULL, + earned_downtime_blocks INTEGER NOT NULL, + fetched_block_height INTEGER NOT NULL, + funded BOOLEAN NOT NULL, + is_liquidatable BOOLEAN NOT NULL, + is_removable BOOLEAN NOT NULL, + last_reward_block_height INTEGER NOT NULL, +-- last_reward_transaction_index INTEGER NOT NULL, + last_uptime_proof INTEGER NOT NULL, + lokinet_version TEXT, + operator_address BLOB NOT NULL, + operator_fee INTEGER NOT NULL, + payable BOOLEAN NOT NULL, +-- portions_for_operator TEXT NOT NULL, -- too large to be an int + pubkey_bls BLOB NOT NULL, + pubkey_ed25519 BLOB NOT NULL, +-- pubkey_x25519 BLOB NOT NULL, + public_ip TEXT, + pulse_votes TEXT, -- JSON encoded dict + quorumnet_port INTEGER, + registration_height INTEGER NOT NULL, + registration_hf_version INTEGER NOT NULL, + requested_unlock_height INTEGER NOT NULL, + service_node_pubkey BLOB NOT NULL, + service_node_version TEXT, -- JSON encoded list of integers + staking_requirement INTEGER NOT NULL, + state_height INTEGER NOT NULL, + storage_lmq_port INTEGER, + storage_port INTEGER, + storage_server_version TEXT,-- JSON encoded list of integers + swarm TEXT NOT NULL, + swarm_id TEXT NOT NULL, -- too large to be an int + total_contributed INTEGER NOT NULL, + /** NOTE: The exit details below are unique to the main db */ + deregistration_height INTEGER, + exit_type TEXT, + liquidation_height INTEGER, + + PRIMARY KEY(contract_id) +); + + +CREATE INDEX service_nodes_main_contract_id_idx ON service_nodes_main(contract_id); +CREATE INDEX service_nodes_main_fetched_block_height_desc_idx + ON service_nodes_main(fetched_block_height DESC); + +CREATE TABLE service_nodes_contributions_main ( + address BLOB NOT NULL, + amount INTEGER NOT NULL, + beneficiary BLOB, + contract_id INTEGER NOT NULL, + fetched_block_height INTEGER NOT NULL, + + FOREIGN KEY (contract_id) REFERENCES service_nodes_main(contract_id), + PRIMARY KEY (contract_id, address) +); + +CREATE INDEX service_nodes_contributions_main_contract_id_idx ON service_nodes_contributions_main(contract_id); +CREATE INDEX service_nodes_contributions_main_fetched_block_height_desc_idx ON service_nodes_contributions_main(fetched_block_height DESC); + +CREATE TABLE network_info ( + id INTEGER PRIMARY KEY NOT NULL, + active_node_count INTEGER NOT NULL, + block_hash TEXT NOT NULL, + block_height INTEGER NOT NULL, + block_timestamp FLOAT NOT NULL, + hard_fork INTEGER NOT NULL, + immutable_block_hash TEXT NOT NULL, + immutable_block_height INTEGER NOT NULL, + max_stakers INTEGER NOT NULL, + min_operator_contribution INTEGER NOT NULL, + nettype TEXT NOT NULL, + node_count INTEGER NOT NULL, + pulse_target_timestamp INTEGER NOT NULL, + staking_requirement INTEGER NOT NULL, + total_staked INTEGER NOT NULL, + version TEXT NOT NULL +); + +CREATE INDEX network_info_block_height_idx ON network_info(block_height DESC); + +CREATE TABLE rewards_info ( + address BLOB NOT NULL PRIMARY KEY, + amount INTEGER NOT NULL, + lifetime_liquidated_stakes INTEGER NOT NULL, + lifetime_locked_stakes INTEGER NOT NULL, + lifetime_rewards INTEGER NOT NULL, + lifetime_unlocked_stakes INTEGER NOT NULL, + locked_stakes INTEGER NOT NULL, + timelocked_stakes INTEGER NOT NULL, + claimed_stakes INTEGER NOT NULL DEFAULT 0, + claimed_rewards INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE daily_rewards_info ( + address BLOB NOT NULL, + block INTEGER NOT NULL, + lifetime_rewards INTEGER NOT NULL, + timestamp FLOAT NOT NULL, + + PRIMARY KEY (address, block) +); + +CREATE INDEX daily_rewards_info_timestamp_asc ON daily_rewards_info(timestamp ASC); +CREATE INDEX daily_rewards_info_timestamp_desc ON daily_rewards_info(timestamp DESC); + +CREATE TABLE arbitrum_info ( + block INTEGER PRIMARY KEY NOT NULL, + timestamp FLOAT NOT NULL DEFAULT ((julianday('now') - 2440587.5)*86400.0), /* unix epoch */ + balance_reward_rate_pool INTEGER NOT NULL, + balance_service_node_rewards INTEGER NOT NULL +); + +CREATE INDEX arbitrum_info_block_idx ON arbitrum_info(block DESC); + +CREATE TABLE arbitrum_events ( + args TEXT NOT NULL, + block INTEGER NOT NULL, + log_index INTEGER NOT NULL, + main_arg TEXT, + name TEXT NOT NULL, + tx TEXT NOT NULL, + PRIMARY KEY (log_index, tx) +); + +CREATE INDEX arbitrum_events_block_idx ON arbitrum_events(block DESC); +CREATE INDEX arbitrum_events_main_arg_idx ON arbitrum_events(main_arg, block DESC); + +CREATE TABLE contribution_contracts ( + address TEXT NOT NULL, + fee INTEGER, + manual_finalize BOOLEAN, + operator_address TEXT, + pubkey_bls BLOB, + service_node_pubkey BLOB, + status INTEGER NOT NULL DEFAULT 0, + + PRIMARY KEY (address) +); + +CREATE INDEX contribution_contracts_address_idx ON contribution_contracts(address); + +CREATE TABLE contribution_contracts_contributions ( + address BLOB NOT NULL, + amount INTEGER NOT NULL DEFAULT 0, + beneficiary_address BLOB, + contract_address BLOB NOT NULL, + reserved INTEGER, + + FOREIGN KEY (contract_address) REFERENCES contribution_contracts (address), + PRIMARY KEY (contract_address, address) +); + +CREATE INDEX contribution_contracts_contributions_contract_address_address ON contribution_contracts_contributions(contract_address, address); +CREATE INDEX contribution_contracts_contributions_contract_address_address_amount ON contribution_contracts_contributions(contract_address, address, amount); + +CREATE TABLE smart_contract_abis ( + name TEXT NOT NULL, + abi TEXT NOT NULL, + bytecode BLOB NOT NULL, + deployed_bytecode BLOB NOT NULL, + PRIMARY KEY (name) +); + +CREATE TABLE smart_contracts ( + address TEXT NOT NULL, + name TEXT NOT NULL, + PRIMARY KEY (address), + + foreign key (name) references smart_contract_abis(name) +); + +CREATE TABLE vesting_contracts ( + address TEXT NOT NULL, + beneficiary TEXT NOT NULL, + initial_amount INTEGER NOT NULL, + initial_beneficiary TEXT NOT NULL, + revoker TEXT NOT NULL, + time_end INTEGER NOT NULL, + time_start INTEGER NOT NULL, + transferable_beneficiary BOOLEAN NOT NULL, + + PRIMARY KEY (address) +); + +CREATE INDEX vesting_contracts_beneficiary_idx ON vesting_contracts(beneficiary); diff --git a/src/staking/snapshot.py b/src/staking/snapshot.py new file mode 100644 index 0000000..c41bb04 --- /dev/null +++ b/src/staking/snapshot.py @@ -0,0 +1,103 @@ +import os +import sqlite3 + +from ..log import Log + + +class DBSnapshot: + def __init__(self, source_db_path: str, snapshot_db_path: str, excluded_tables: set[str] | None, log_level: int, + perf: bool = False): + assert source_db_path is not None and len(source_db_path) > 0 + assert snapshot_db_path is not None and len(snapshot_db_path) > 0 + + self.path_source = source_db_path + self.path_snapshot = snapshot_db_path + self.path_snapshot_tmp = snapshot_db_path + ".tmp" + self.excluded_tables = excluded_tables if excluded_tables is not None else set() + self.log = Log("db_snapshot", log_level, enable_perf=perf).logger + + os.makedirs(os.path.dirname(self.path_snapshot), exist_ok=True) + + self.log.info("Initializing snapshot task for DB. Source DB: {}, Snapshot DB: {}, Excluded tables: {}".format( + self.path_source, self.path_snapshot, self.excluded_tables)) + + def cleanup(self) -> None: + """ + Removes the temporary snapshot DB if it exists. + """ + self.log.perf.start("cleanup") + self.log.info("Cleaning up tmp snapshot database {}".format(self.path_snapshot_tmp)) + if os.path.exists(self.path_snapshot_tmp): + os.remove(self.path_snapshot_tmp) + self.log.perf.end("cleanup") + + def promote_tmp_db(self) -> None: + """ + Swaps the temporary snapshot DB with the final snapshot DB. + """ + self.log.perf.start("promote_tmp_db") + self.log.info("Swapping tmp snapshot database {} with final snapshot database {}".format(self.path_snapshot_tmp, + self.path_snapshot)) + if os.path.exists(self.path_snapshot): + os.remove(self.path_snapshot) + os.rename(self.path_snapshot_tmp, self.path_snapshot) + self.log.perf.end("promote_tmp_db") + + def snapshot(self) -> None: + """ + Creates all tables from source_db_path into backup_db_path, + but does NOT copy the row data for any tables listed in self.excluded_tables. + """ + + # Open (or create) the new backup DB + self.log.perf.start("backup_db") + self.log.info("Backing up database {} to {}".format(self.path_source, self.path_snapshot)) + + self.cleanup() + + with sqlite3.connect(self.path_snapshot_tmp) as backup_conn: + self.log.perf.start("backup_db_create_tables") + # Attach the source database so we can refer to it as 'old_db' + backup_conn.execute(f"ATTACH DATABASE '{self.path_source}' AS old_db;") + # Gather tables and their CREATE TABLE statements from the source + schema_query = """ + SELECT name, sql + FROM old_db.sqlite_master + WHERE type='table' + AND name NOT LIKE 'sqlite_%' -- skip internal or system tables + """ + tables = backup_conn.execute(schema_query).fetchall() + + # 1. Create all tables in the new DB + for table_name, create_sql in tables: + # Make sure there's a valid CREATE statement + if create_sql: + backup_conn.execute(create_sql) + + self.log.perf.end("backup_db_create_tables") + self.log.perf.start("backup_db_copy_rows") + + # 2. For each table, copy rows unless it's in tables_without_rows + for table_name, _ in tables: + if table_name not in self.excluded_tables: + # Insert rows from the source's table + insert_sql = f""" + INSERT INTO {table_name} + SELECT * FROM old_db.{table_name} + """ + backup_conn.execute(insert_sql) + else: + # We create the table above, but do NOT copy any rows for this table + print(f"Skipping rows for table: {table_name}") + + self.log.perf.end("backup_db_copy_rows") + + backup_conn.commit() + + # Detach the source database + backup_conn.execute("DETACH DATABASE old_db;") + + self.promote_tmp_db() + + self.log.info("Backed up database {} to {}".format(self.path_source, self.path_snapshot)) + self.log.perf.end("backup_db") diff --git a/src/staking/write.py b/src/staking/write.py new file mode 100644 index 0000000..1821899 --- /dev/null +++ b/src/staking/write.py @@ -0,0 +1,1143 @@ +import json +import time +from contextlib import closing + +from web3 import Web3 + +from ..db.util import sql_connect_in_write_mode +from ..log import Log +from ..staking.arbitrum import ContributionContractDetails +from ..staking.dataclasses import RewardsInfo, DBNodeExit, VestingContract +from ..oxen.rpc import ServiceNode, NetworkInfo +from ..web3client.abi_manager import ABIData +from ..web3client.event_scanner import ProcessedEvent + + +class DBWriterStaking: + def __init__(self, db_path: str, log_level: int, perf: bool = False): + self.log = Log("db_writer", log_level, enable_perf=perf).logger + self.db_path = db_path + self.defer_writing_arbitrum_events = False + self.deferred_arbitrum_events = [] + + def write_nodes_to_staging_db( + self, + height: int, + parsed_nodes: list[ServiceNode], + # TODO: type the contributor_stake_map properly + contributions: list[dict[str, int]], + ): + self.log.perf.start("write_to_db") + + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Inserting {} service nodes".format(len(parsed_nodes))) + self.log.perf.start("write_nodes_to_staging_db -> insert nodes") + + cursor.executemany( + """ + INSERT INTO service_nodes_staging ( + active, + contract_id, + decommission_count, + earned_downtime_blocks, + fetched_block_height, + funded, + is_liquidatable, + is_removable, + last_reward_block_height, + last_uptime_proof, + lokinet_version, + operator_address, + operator_fee, + payable, + pubkey_bls, + pubkey_ed25519, + public_ip, + pulse_votes, + quorumnet_port, + registration_height, + registration_hf_version, + requested_unlock_height, + service_node_pubkey, + service_node_version, + staking_requirement, + state_height, + storage_lmq_port, + storage_port, + storage_server_version, + swarm, + swarm_id, + total_contributed + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + ( + node.get("active"), + node.get("contract_id"), + node.get("decommission_count"), + node.get("earned_downtime_blocks"), + height, + node.get("funded"), + node.get("is_liquidatable"), + node.get("is_removable"), + node.get("last_reward_block_height"), + node.get("last_uptime_proof"), + node.get("lokinet_version"), + node.get("operator_address"), + node.get("operator_fee"), + node.get("payable"), + node.get("pubkey_bls"), + node.get("pubkey_ed25519"), + node.get("public_ip"), + node.get("pulse_votes"), + node.get("quorumnet_port"), + node.get("registration_height"), + node.get("registration_hf_version"), + node.get("requested_unlock_height"), + node.get("service_node_pubkey"), + node.get("service_node_version"), + node.get("staking_requirement"), + node.get("state_height"), + node.get("storage_lmq_port"), + node.get("storage_port"), + node.get("storage_server_version"), + node.get("swarm"), + node.get("swarm_id"), + node.get("total_contributed"), + ) + for node in parsed_nodes + ), + ) + + inserted_nodes_rows = cursor.rowcount + + self.log.perf.end("write_nodes_to_staging_db -> insert nodes") + self.log.debug( + "Inserted {} rows into service_nodes_staging".format(inserted_nodes_rows) + ) + self.log.debug("Inserting {} service node contributions".format(len(contributions))) + self.log.perf.start("write_nodes_to_staging_db -> insert contributions") + + cursor.executemany( + """ + INSERT OR REPLACE INTO service_nodes_contributions_staging ( + address, + amount, + beneficiary, + contract_id, + fetched_block_height + ) + VALUES (?, ?, ?, ?, ?) + """, + ( + ( + contribution["address"], + contribution["amount"], + contribution["beneficiary"], + contribution["contract_id"], + height, + ) + for contribution in contributions + ), + ) + + inserted_contributions_rows = cursor.rowcount + + self.log.perf.end("write_nodes_to_staging_db -> insert contributions") + self.log.debug( + "Inserted {} rows into service_nodes_contributions_staging".format( + inserted_contributions_rows + ) + ) + + connection.commit() + self.log.perf.end("write_to_db") + + def write_nodes_to_main_db(self, immutable_height: int): + """ + Gets all nodes from the staging db at or below the immutable_height and writes them to the main db then remove + those nodes from the staging db. + """ + self.log.perf.start("write_nodes_to_main_db") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.perf.start("write_nodes_to_main_db -> select nodes") + + cursor.execute( + """ + SELECT * FROM service_nodes_staging WHERE fetched_block_height = ? + """, + (immutable_height,), + ) + nodes = cursor.fetchall() + selected_nodes_count = len(nodes) + + self.log.perf.end("write_nodes_to_main_db -> select nodes") + self.log.info("Found {} nodes to write to main db".format(selected_nodes_count)) + + # We only want to continue here if there are any nodes ready to commit. + if selected_nodes_count == 0: + self.log.debug("No nodes ready to commit") + return + + self.log.perf.start("write_nodes_to_main_db -> insert nodes") + + cursor.executemany( + """ + INSERT OR REPLACE INTO service_nodes_main ( + active, + contract_id, + decommission_count, + earned_downtime_blocks, + fetched_block_height, + funded, + is_liquidatable, + is_removable, + last_reward_block_height, + last_uptime_proof, + lokinet_version, + operator_address, + operator_fee, + payable, + pubkey_bls, + pubkey_ed25519, + public_ip, + pulse_votes, + quorumnet_port, + registration_height, + registration_hf_version, + requested_unlock_height, + service_node_pubkey, + service_node_version, + staking_requirement, + state_height, + storage_lmq_port, + storage_port, + storage_server_version, + swarm, + swarm_id, + total_contributed + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + nodes, + ) + + inserted_or_updated_rows = cursor.rowcount + + self.log.perf.end("write_nodes_to_main_db -> insert nodes") + self.log.info("Wrote {} rows to main db".format(inserted_or_updated_rows)) + + if inserted_or_updated_rows != len(nodes): + self.log.error( + "Inserted or updated {} rows, but expected {}".format( + inserted_or_updated_rows, len(nodes) + ) + ) + connection.rollback() + self.log.perf.end("write_nodes_to_main_db") + return + + self.log.perf.start("write_nodes_to_main_db -> select contributions") + + cursor.execute( + """ + SELECT * FROM service_nodes_contributions_staging WHERE fetched_block_height = ? + """, + (immutable_height,), + ) + contributions = cursor.fetchall() + selected_contributions_count = len(contributions) + + self.log.perf.end("write_nodes_to_main_db -> select contributions") + self.log.debug( + "Found {} contributions to write to main db".format( + selected_contributions_count + ) + ) + self.log.perf.start("write_nodes_to_main_db -> insert contributions") + + cursor.executemany( + """ + INSERT OR REPLACE INTO service_nodes_contributions_main (address, amount, beneficiary, contract_id, fetched_block_height) + VALUES (?, ?, ?, ?, ?) + """, + contributions, + ) + + inserted_contributions_rows = cursor.rowcount + + self.log.perf.end("write_nodes_to_main_db -> insert contributions") + self.log.info("Wrote {} rows to main db".format(inserted_contributions_rows)) + + if inserted_contributions_rows != len(contributions): + self.log.error( + "Inserted {} rows, but expected {}".format( + inserted_contributions_rows, len(contributions) + ) + ) + connection.rollback() + return + + self.log.perf.start("write_nodes_to_main_db -> delete nodes") + + cursor.execute( + """ + DELETE FROM service_nodes_staging WHERE fetched_block_height <= ? + """, + (immutable_height,), + ) + + deleted_rows = cursor.rowcount + + self.log.perf.end("write_nodes_to_main_db -> delete nodes") + self.log.info("Deleted {} rows from staging db".format(deleted_rows)) + self.log.perf.start("write_nodes_to_main_db -> delete contributions") + + cursor.execute( + """ + DELETE FROM service_nodes_contributions_staging WHERE fetched_block_height <= ? + """, + (immutable_height,), + ) + + deleted_contributions_rows = cursor.rowcount + + self.log.perf.end("write_nodes_to_main_db -> delete contributions") + self.log.info( + "Deleted {} rows from staging contributions db".format( + deleted_contributions_rows + ) + ) + + connection.commit() + self.log.info("Transaction committed successfully") + + self.log.perf.end("write_nodes_to_main_db") + + def write_exit_list_to_db(self, exit_list: list[DBNodeExit]): + self.log.perf.start("write_exit_list_to_db") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Updating nodes in main with {} exit events".format(len(exit_list))) + self.log.perf.start("write_exit_list_to_db -> insert exit events") + cursor.executemany( + """ + UPDATE service_nodes_main SET + deregistration_height = ?, + exit_type = ?, + liquidation_height = ? + WHERE pubkey_bls = ? + """, + ( + ( + e.deregistration_height, + e.exit_type, + e.liquidation_height, + e.pubkey_bls, + ) + for e in exit_list + ) + ) + inserted_exit_rows = cursor.rowcount + + self.log.perf.end("write_exit_list_to_db -> insert exit events") + self.log.debug( + "Inserted {} rows into exit events".format(inserted_exit_rows) + ) + + connection.commit() + self.log.perf.end("write_exit_list_to_db") + + def write_network_info_to_db( + self, + network: NetworkInfo, + node_count: int, + total_staked: int, + active_node_count: int, + ): + self.log.perf.start("write_network_info_to_db") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + with closing(connection.cursor()) as cursor: + cursor.execute( + """ + INSERT OR REPLACE INTO network_info ( + id, + active_node_count, + block_hash, + block_height, + block_timestamp, + hard_fork, + immutable_block_hash, + immutable_block_height, + max_stakers, + min_operator_contribution, + node_count, + nettype, + pulse_target_timestamp, + staking_requirement, + total_staked, + version + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + 1, + active_node_count, + network.block_hash, + network.block_height, + time.time().__floor__(), + network.hard_fork, + network.immutable_block_hash, + network.immutable_block_height, + network.max_stakers, + network.min_operator_contribution, + node_count, + network.nettype, + network.pulse_target_timestamp, + network.staking_requirement, + total_staked, + network.version, + ), + ) + connection.commit() + self.log.perf.end("write_network_info_to_db") + + def write_rewards_info_to_db(self, rewards_info: list[RewardsInfo]): + self.log.perf.start("write_rewards_info_to_db") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Inserting {} rewards info".format(len(rewards_info))) + self.log.perf.start("write_rewards_info_to_db -> insert rewards info") + + cursor.executemany( + """ + INSERT INTO rewards_info ( + address, + amount, + lifetime_liquidated_stakes, + lifetime_locked_stakes, + lifetime_rewards, + lifetime_unlocked_stakes, + locked_stakes, + timelocked_stakes + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(address) DO UPDATE SET + amount = excluded.amount, + lifetime_liquidated_stakes = excluded.lifetime_liquidated_stakes, + lifetime_locked_stakes = excluded.lifetime_locked_stakes, + lifetime_rewards = excluded.lifetime_rewards, + lifetime_unlocked_stakes = excluded.lifetime_unlocked_stakes, + locked_stakes = excluded.locked_stakes, + timelocked_stakes = excluded.timelocked_stakes; + """, + ( + ( + info.address, + info.amount, + info.lifetime_liquidated_stakes, + info.lifetime_locked_stakes, + info.lifetime_rewards, + info.lifetime_unlocked_stakes, + info.locked_stakes, + info.timelocked_stakes + ) + for info in rewards_info + ), + ) + + inserted_rewards_rows = cursor.rowcount + + self.log.perf.end("write_rewards_info_to_db -> insert rewards info") + self.log.debug( + "Inserted {} rows into rewards_info".format(inserted_rewards_rows) + ) + + connection.commit() + self.log.perf.end("write_rewards_info_to_db") + + def update_daily_rolling_rewards(self, rewards_info: list[RewardsInfo], block: int, timestamp: int): + self.log.perf.start("update_daily_rolling_rewards") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Deleting expired daily rolling rewards") + + # Keep 26 hours of rewards for a safe buffer, we only care about the last 24 hours + delete_before_timestamp = timestamp - 26 * 60 * 60 + + cursor.execute( + """ + DELETE FROM daily_rewards_info WHERE timestamp < ? + """ + , (delete_before_timestamp,)) + + self.log.debug("Updating daily rolling rewards") + cursor.executemany( + """ + INSERT INTO daily_rewards_info ( + address, + block, + lifetime_rewards, + timestamp + ) + VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING; + """, + ( + ( + info.address, + block, + info.lifetime_rewards, + timestamp + ) + for info in rewards_info + ), + ) + + inserted_rewards_rows = cursor.rowcount + + self.log.debug( + "Inserted {} rows into daily_rewards_info".format(inserted_rewards_rows) + ) + + connection.commit() + self.log.perf.end("update_daily_rolling_rewards") + + + def write_update_rewards_claim_amounts(self, address: str, claimed_stakes: int, claimed_rewards: int): + self.log.perf.start("write_update_rewards_claim_amounts") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug(f"Updating rewards claim amounts for {address}") + self.log.perf.start("write_update_rewards_claim_amounts -> update rewards claim amounts") + cursor.execute( + """ + UPDATE rewards_info SET claimed_stakes = ?, claimed_rewards = ? WHERE address = ? + """, + (claimed_stakes, claimed_rewards, address), + ) + updated_rows = cursor.rowcount + self.log.perf.end("write_update_rewards_claim_amounts -> update rewards claim amounts") + self.log.debug( + "Updated {} rows in rewards_info".format(updated_rows) + ) + connection.commit() + self.log.perf.end("write_update_rewards_claim_amounts") + + def write_reset_all_rewards_claim_amounts(self): + self.log.perf.start("write_reset_all_rewards_claim_amounts") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Updating rewards claim amounts for all addresses") + self.log.perf.start("write_reset_all_rewards_claim_amounts -> update rewards claim amounts") + cursor.execute( + """ + UPDATE rewards_info SET claimed_stakes = 0, claimed_rewards = 0 + """ + ) + updated_rows = cursor.rowcount + self.log.perf.end("write_reset_all_rewards_claim_amounts -> update rewards claim amounts") + self.log.debug( + "Updated {} rows in rewards_info".format(updated_rows) + ) + connection.commit() + self.log.perf.end("write_reset_all_rewards_claim_amounts") + + def write_arbitrum_event_to_db(self, event: ProcessedEvent): + if self.defer_writing_arbitrum_events: + self.log.debug(f"Deferring arbitrum event write: {event}") + self.deferred_arbitrum_events.append(event) + return + self.log.perf.start("write_arbitrum_event_to_db") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Inserting event into arbitrum_events") + self.log.debug(event) + self.log.perf.start("write_arbitrum_event_to_db -> insert event") + cursor.execute( + """ + INSERT INTO arbitrum_events ( + args, + block, + log_index, + main_arg, + name, + tx + ) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + Web3.to_json(dict(event.args)), + event.block, + event.log_index, + event.main_arg, + event.name, + event.tx, + ), + ) + inserted_event_rows = cursor.rowcount + self.log.perf.end("write_arbitrum_event_to_db -> insert event") + self.log.debug( + "Inserted {} rows into arbitrum_events".format(inserted_event_rows) + ) + connection.commit() + self.log.perf.end("write_arbitrum_event_to_db") + + def write_deferred_arbitrum_events_to_db(self): + if len(self.deferred_arbitrum_events) == 0: + self.log.warning("No deferred arbitrum events to write") + return + + events, self.deferred_arbitrum_events = self.deferred_arbitrum_events, [] + events.sort(key=lambda x: (x.block, x.log_index)) + + try: + self.log.info(f"Writing {len(events)} deferred arbitrum events to db") + self.write_arbitrum_events_to_db(events) + except Exception as e: + self.log.error("Error writing deferred arbitrum events") + self.log.error(e) + self.deferred_arbitrum_events = events + self.deferred_arbitrum_events + + def write_arbitrum_events_to_db(self, events: list[ProcessedEvent]): + self.log.perf.start("write_arbitrum_events_to_db") + + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Inserting {} events into arbitrum_events".format(len(events))) + self.log.perf.start("write_arbitrum_events_to_db -> insert events") + + cursor.executemany( + """ + INSERT OR REPLACE INTO arbitrum_events ( + args, + block, + log_index, + main_arg, + name, + tx + ) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + ( + Web3.to_json(dict(event.args)), + event.block, + event.log_index, + event.main_arg, + event.name, + event.tx, + ) + for event in events + ), + ) + + inserted_or_updated_rows_count = cursor.rowcount + + self.log.perf.end("write_arbitrum_events_to_db -> insert events") + self.log.debug( + "Inserted or updated {} rows into arbitrum_events".format( + inserted_or_updated_rows_count + ) + ) + + connection.commit() + self.log.perf.end("write_arbitrum_events_to_db") + + def write_new_contribution_contract(self, address: str, operator_address: str, service_node_pubkey: str): + self.log.perf.start("write_new_contribution_contract") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug(f"Inserting new contribution contract") + cursor.execute(""" + INSERT OR REPLACE INTO contribution_contracts ( + address, + operator_address, + service_node_pubkey + ) + VALUES (?, ?, ?) + """, (address, operator_address, service_node_pubkey)) + + connection.commit() + self.log.perf.end("write_new_contribution_contract") + + def write_update_contribution_contract_status(self, address: str, status: int): + self.log.perf.start("write_update_contribution_contract_status") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug(f"Updating contribution contract status to {status}") + cursor.execute( + """ + UPDATE contribution_contracts SET status = ? WHERE address = ? + """, + (status, address), + ) + + connection.commit() + self.log.perf.end("write_update_contribution_contract_status") + + def write_update_contribution_contract_manual_finalize(self, address: str, manual_finalize: bool): + self.log.perf.start("write_update_contribution_contract_manual_finalize") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug(f"Updating contribution contract manual_finalize to {manual_finalize}") + cursor.execute( + """ + UPDATE contribution_contracts SET manual_finalize = ? WHERE address = ? + """, + (manual_finalize, address), + ) + + connection.commit() + self.log.perf.end("write_update_contribution_contract_manual_finalize") + + def write_update_contribution_contract_fee(self, address: str, fee: int): + self.log.perf.start("write_update_contribution_contract_fee") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug(f"Updating contribution contract fee to {fee}") + cursor.execute( + """ + UPDATE contribution_contracts SET fee = ? WHERE address = ? + """, + (fee, address), + ) + + connection.commit() + self.log.perf.end("write_update_contribution_contract_fee") + + def write_update_contribution_contract_pubkeys(self, address: str, pubkey_bls: str, service_node_pubkey: str): + self.log.perf.start("write_update_contribution_contract_pubkeys") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug(f"Updating contribution contract pubkeys") + cursor.execute( + """ + INSERT INTO contribution_contracts (address, pubkey_bls, service_node_pubkey) + VALUES (?, ?, ?) + ON CONFLICT(address) DO UPDATE SET + pubkey_bls = excluded.pubkey_bls, + service_node_pubkey = excluded.service_node_pubkey; + """, + (address, pubkey_bls, service_node_pubkey), + ) + + connection.commit() + self.log.perf.end("write_update_contribution_contract_pubkeys") + + def write_update_contribution_contract_contributor(self, contract_address: str, contributor): + self.log.perf.start("write_update_contribution_contract_contributor") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug(f"Updating contribution contract contributor") + cursor.execute( + """ + INSERT OR REPLACE INTO contribution_contracts_contributions ( + address, + amount, + beneficiary_address, + contract_address, + reserved + ) + VALUES (?, ?, ?, ?, ?) + """, + ( + contributor.address, + contributor.amount, + contributor.beneficiary, + contract_address, + contributor.reserved + ), + ) + + connection.commit() + + def write_delete_contribution_contract_contributor(self, contract_address: str, contributor_address: str): + self.log.perf.start("write_delete_contribution_contract_contributor") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug(f"Deleting contribution contract contributor") + cursor.execute( + """ + DELETE FROM contribution_contracts_contributions WHERE address = ? AND contract_address = ? + """, + (contributor_address, contract_address), + ) + + connection.commit() + self.log.perf.end("write_delete_contribution_contract_contributor") + + def write_delete_all_contribution_contract_contributors(self, contract_address: str): + self.log.perf.start("write_delete_all_contribution_contract_contributors") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug(f"Deleting all contribution contract contributors") + cursor.execute( + """ + DELETE FROM contribution_contracts_contributions WHERE contract_address = ? + """, + (contract_address,), + ) + connection.commit() + self.log.perf.end("write_delete_all_contribution_contract_contributors") + + def write_contribution_contracts_to_db( + self, contracts: list[ContributionContractDetails], contributions_list: list + ): + self.log.perf.start("write_contribution_contracts_to_db") + + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Inserting {} contribution contracts".format(len(contracts))) + self.log.perf.start("write_contribution_contracts_to_db -> insert contracts") + + cursor.executemany( + """ + INSERT OR REPLACE INTO contribution_contracts ( + address, + fee, + manual_finalize, + operator_address, + pubkey_bls, + service_node_pubkey, + status + ) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + ( + contract.address, + contract.fee, + contract.manual_finalize, + contract.operator_address, + contract.pubkey_bls, + contract.service_node_pubkey, + contract.status, + ) + for contract in contracts + ), + ) + + inserted_contract_rows = cursor.rowcount + + self.log.perf.end("write_contribution_contracts_to_db -> insert contracts") + self.log.debug( + "Inserted or Updated {} rows into contribution_contracts".format( + inserted_contract_rows + ) + ) + self.log.perf.start("write_contribution_contracts_to_db -> delete contributions") + + # The contributors for a contact need to be deleted before the contract can be inserted again to account + # for contract resets, or contributors leaving the contract. We could read from the db and only delete + # the missing ones but this should be more performant. + # TODO: investigate a better solution + cursor.executemany( + """DELETE FROM contribution_contracts_contributions WHERE contract_address = ?""", + (( + contract.address, + ) + for contract in contracts) + ) + + deleted_contributions_rows = cursor.rowcount + + self.log.perf.end("write_contribution_contracts_to_db -> delete contributions") + self.log.debug( + "Deleted {} rows from contribution_contracts_contributions".format( + deleted_contributions_rows + ) + ) + self.log.debug( + "Inserting {} contract contributions".format(len(contributions_list)) + ) + self.log.perf.start( + "write_contribution_contracts_to_db -> insert contribution contracts contributions" + ) + + cursor.executemany( + """ + INSERT INTO contribution_contracts_contributions ( + address, + amount, + beneficiary_address, + contract_address, + reserved + ) + VALUES (?, ?, ?, ?, ?) + """, + ( + ( + contribution["address"], + contribution["amount"], + contribution["beneficiary_address"], + contribution["contract_address"], + contribution["reserved"], + ) + for contribution in contributions_list + ), + ) + + inserted_contributions_rows = cursor.rowcount + + self.log.perf.end( + "write_contribution_contracts_to_db -> insert contribution contracts contributions" + ) + self.log.debug( + "Inserted {} rows into contribution_contracts_contributions".format( + inserted_contributions_rows + ) + ) + + connection.commit() + self.log.perf.end("write_contribution_contracts_to_db") + + def write_smart_contract_abis_to_db(self, abis: list[ABIData]): + self.log.perf.start("write_smart_contract_abis_to_db") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Inserting {} smart contract abis".format(len(abis))) + self.log.perf.start("write_smart_contract_abis_to_db -> insert abis") + + cursor.executemany( + """ + INSERT OR REPLACE INTO smart_contract_abis ( + name, + abi, + bytecode, + deployed_bytecode + ) + VALUES (?, ?, ?, ?) + """, + ( + ( + abi.name, + json.dumps(abi.abi), + abi.bytecode, + abi.deployed_bytecode, + ) + for abi in abis + ), + ) + + inserted_abi_rows = cursor.rowcount + + self.log.perf.end("write_smart_contract_abis_to_db -> insert abis") + self.log.debug( + "Inserted {} rows into smart_contract_abis".format(inserted_abi_rows) + ) + + connection.commit() + self.log.perf.end("write_smart_contract_abis_to_db") + + def write_smart_contract_details_to_db( + self, + contracts, + ): + self.log.perf.start("write_smart_contract_details_to_db") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Inserting {} smart contract details".format(len(contracts))) + self.log.perf.start("write_smart_contract_details_to_db -> insert contracts") + + cursor.executemany( + """ + INSERT OR REPLACE INTO smart_contracts ( + address, + name + ) + VALUES (?, ?) + """, + ( + ( + contract.get("address"), + contract.get("name"), + ) + for contract in contracts + ), + ) + + inserted_details_rows = cursor.rowcount + + self.log.perf.end("write_smart_contract_details_to_db -> insert details") + self.log.debug( + "Inserted {} rows into smart_contract_details".format(inserted_details_rows) + ) + + connection.commit() + self.log.perf.end("write_smart_contract_details_to_db") + + def write_arbitrum_info_to_db(self, current_block, service_node_rewards_balance, reward_rate_pool_balance): + self.log.perf.start("write_arbitrum_info_to_db") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug( + "Inserting arbitrum info: current block {}, service node rewards balance {}, reward rate pool balance {}".format( + current_block, service_node_rewards_balance, reward_rate_pool_balance)) + self.log.perf.start("write_arbitrum_info_to_db -> insert info") + + cursor.execute( + "INSERT OR REPLACE INTO arbitrum_info (block, balance_service_node_rewards, balance_reward_rate_pool) VALUES (?, ?, ?)", + (current_block, service_node_rewards_balance, reward_rate_pool_balance)) + + inserted_info_rows = cursor.rowcount + + self.log.perf.end("write_arbitrum_info_to_db -> insert info") + self.log.debug( + "Inserted {} rows into arbitrum_info".format(inserted_info_rows) + ) + + connection.commit() + self.log.perf.end("write_arbitrum_info_to_db") + + def write_vesting_contracts(self, vesting_contracts: list[VestingContract]): + self.log.perf.start("write_vesting_contracts") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Inserting {} vesting contracts".format(len(vesting_contracts))) + self.log.perf.start("write_vesting_contracts -> insert contracts") + + # assert the table is empty + cursor.execute("SELECT COUNT(*) FROM vesting_contracts") + assert cursor.fetchone()[0] == 0, "Vesting contract table is not empty" + + cursor.executemany( + """ + INSERT OR REPLACE INTO vesting_contracts ( + address, + beneficiary, + initial_amount, + initial_beneficiary, + revoker, + time_end, + time_start, + transferable_beneficiary + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + ( + contract.address, + contract.beneficiary, + contract.initial_amount, + contract.initial_beneficiary, + contract.revoker, + contract.time_end, + contract.time_start, + contract.transferable_beneficiary, + ) + for contract in vesting_contracts + ), + ) + + inserted_contract_rows = cursor.rowcount + + self.log.perf.end("write_vesting_contracts -> insert contracts") + self.log.debug( + "Inserted {} rows into vesting_contracts".format(inserted_contract_rows) + ) + + connection.commit() + self.log.perf.end("write_vesting_contracts") + + def delete_all_vesting_contracts(self): + self.log.perf.start("delete_all_vesting_contracts") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + cursor.execute("DELETE FROM vesting_contracts") + deleted_rows = cursor.rowcount + self.log.debug( + "Cleared {} rows from vesting_contracts".format(deleted_rows) + ) + connection.commit() + self.log.perf.end("delete_all_vesting_contracts") + + def write_update_vesting_contract_beneficiary(self, address: str, beneficiary: str): + self.log.perf.start("write_update_vesting_contract_beneficiary") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Updating vesting contract {} beneficiary to {}".format(address, beneficiary)) + self.log.perf.start("write_update_vesting_contract_beneficiary -> update beneficiary") + + cursor.execute( + """ + UPDATE vesting_contracts SET beneficiary = ? WHERE address = ? + """, + (beneficiary, address), + ) + + updated_rows = cursor.rowcount + + self.log.perf.end("write_update_vesting_contract_beneficiary -> update beneficiary") + self.log.debug( + "Updated {} rows in vesting_contracts".format(updated_rows) + ) + + connection.commit() + self.log.perf.end("write_update_vesting_contract_beneficiary") + + def delete_all_events(self): + self.log.perf.start("delete_all_events") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + self.log.debug("Deleting all events from the db") + + cursor.execute("""Delete from arbitrum_events""") + + deleted_rows = cursor.rowcount + + self.log.debug( + "Cleared {} rows from vesting_contracts".format(deleted_rows) + ) + + connection.commit() + self.log.perf.end("delete_all_events") + + def delete_all_contrib_contracts_and_contributors(self): + self.log.perf.start("delete_all_contrib_contracts_and_contributors") + with closing(sql_connect_in_write_mode(self.db_path)) as connection: + connection.execute("BEGIN") + with closing(connection.cursor()) as cursor: + cursor.execute("""Delete from contribution_contracts_contributions""") + deleted_contributions_rows = cursor.rowcount + + self.log.debug( + "Cleared {} rows from contribution_contracts_contributions".format(deleted_contributions_rows) + ) + + cursor.execute("""Delete from contribution_contracts""") + deleted_contract_rows = cursor.rowcount + + self.log.debug( + "Cleared {} rows from contribution_contracts".format(deleted_contract_rows) + ) + + connection.commit() + self.log.perf.end("delete_all_contrib_contracts_and_contributors") diff --git a/src/util/__init__.py b/src/util/__init__.py new file mode 100644 index 0000000..f4d7ed4 --- /dev/null +++ b/src/util/__init__.py @@ -0,0 +1,18 @@ +from eth_utils import is_address + + +def is_not_empty_string(value) -> bool: + return value is not None and len(value) > 0 + +def format_seconds(seconds: int | float, precision: int = 3) -> str: + assert precision >= 0 + if precision == 0: + return seconds.__round__().__str__() + return seconds.__round__(precision).__str__() + + +def format_ms(ms: int | float, precision: int = 3) -> str: + assert precision >= 0 + if precision == 0: + return ms.__round__().__str__() + return ms.__round__(precision).__str__() diff --git a/src/util/cache.py b/src/util/cache.py new file mode 100644 index 0000000..de149cf --- /dev/null +++ b/src/util/cache.py @@ -0,0 +1,62 @@ +import logging +import time +from copy import copy +from typing import Optional, Callable + +from ..log import Log + + +class Cache: + def __init__(self, stale_time_seconds: int = 0, log_level: int = logging.INFO): + self.log = Log("data_manager", log_level).logger + self.default_stale_time_seconds = stale_time_seconds + self.store = {} + self.cache_expiry = {} + + def get(self, key, getter=Optional[Callable], getter_args=None, ttl=None, invalidate_timestamp=None): + if ttl is None or ttl < 0: + ttl = self.default_stale_time_seconds + now = time.time() + + if key in self.store and self.cache_expiry.get(key, 0) > now: + return self.store.get(key) + + self.clear_stale(now) + + data = getter(getter_args) if getter_args is not None else getter() + self.set_cache_value(key, data, ttl, invalidate_timestamp) + return data + + def get_cached_only(self, key: str): + now = time.time() + if key in self.store and self.cache_expiry.get(key, 0) > now: + return self.store.get(key) + return None + + def set_cache_value(self, key: str, data=None, ttl=None, invalidate_timestamp=None): + now = time.time() + if invalidate_timestamp is None: + if ttl is None or ttl < 0: + ttl = self.default_stale_time_seconds + expire = now + ttl + else: + expire = invalidate_timestamp + + self.store[key] = data + self.set_expiry_timestamp(key, expire) + + def set_expiry_ttl(self, key: str, ttl: int): + self.cache_expiry[key] = time.time() + ttl + + def set_expiry_timestamp(self, key: str, timestamp: int): + self.cache_expiry[key] = timestamp + + def get_stale_timestamp(self, key: str): + return self.cache_expiry.get(key, 0) + + def clear_stale(self, now): + # NOTE: must be a copy as the dictionary is modified during iteration + for key, expiry in copy(self.cache_expiry).items(): + if expiry < now: + del self.store[key] + del self.cache_expiry[key] diff --git a/src/util/cache_tests.py b/src/util/cache_tests.py new file mode 100644 index 0000000..bca7c08 --- /dev/null +++ b/src/util/cache_tests.py @@ -0,0 +1,56 @@ +import time + +import pytest + +default_stale_time = 2 + + +@pytest.fixture() +def cache(): + from .cache import Cache + return Cache(stale_time_seconds=default_stale_time) + + +def test_cache_init(cache): + assert cache.default_stale_time_seconds == default_stale_time + assert cache.store == {} + assert cache.cache_expiry == {} + + +def test_cache_set(cache): + cache.set_cache_value("test", "potato") + assert cache.store == {"test": "potato"} + + cache_expiry = cache.cache_expiry + assert int(cache_expiry.get("test")) == int(time.time() + default_stale_time) + + +def test_cache_clear_stale(cache): + cache.set_cache_value("test", "potato") + cache.clear_stale(time.time() + 2) + assert cache.store == {} + assert cache.cache_expiry == {} + +def test_cache_normal(cache): + def getter(): + return "potato" + + val = cache.get("test", getter) + assert val == "potato" + +def test_cache_stale_timestamp_expired(cache): + cache.get("test", getter=lambda: "potato") + assert int(cache.cache_expiry.get("test")) == int(time.time() + default_stale_time) + assert int(cache.get_stale_timestamp("test")) == int(time.time() + default_stale_time) + +def test_cache_large_invalidate(cache): + def getter(): + return "potato" + + invalidate_timestamp = 99999999999999999999 + + cache.set_cache_value("test", "potato", invalidate_timestamp=invalidate_timestamp) + val = cache.get("test", getter) + assert val == "potato" + + assert cache.cache_expiry.get("test") == invalidate_timestamp diff --git a/src/util/config_import.py b/src/util/config_import.py new file mode 100644 index 0000000..5936eeb --- /dev/null +++ b/src/util/config_import.py @@ -0,0 +1,9 @@ +import os +import sys + +def import_config(): + sys.path.append(os.path.join(os.path.dirname(__file__), "..")) + from src import config + # reset system path to original state + sys.path.pop() + return config \ No newline at end of file diff --git a/src/util/flask_utils.py b/src/util/flask_utils.py new file mode 100644 index 0000000..be87177 --- /dev/null +++ b/src/util/flask_utils.py @@ -0,0 +1,105 @@ +import subprocess +import time +from dataclasses import dataclass +import flask +from werkzeug.middleware.proxy_fix import ProxyFix + +from ..util.cache import Cache +from ..log import Log +from ..util.parse import hexify + + +def json_response(vals=None, vals_no_hexify=None): + """ + Takes a dict, adds some general info fields to it, and jsonifies it for a flask route function + return value. The dict gets passed through `hexify` first to convert any bytes values to hex. + + Note: because network_info is cached, it can be called earlier in the route and both network_info + dict will be the same in both places, and basically guaranteed cached at this stage. + """ + if vals is None: + vals = {} + else: + hexify(vals) + + if vals_no_hexify is None: + vals_no_hexify = {} + + return flask.jsonify({**vals, **vals_no_hexify, "t": int(time.time())}) + + +@dataclass +class FlaskAppConfig: + name: str + log_level: int + log_level_generic: str | None = None + enable_perf: bool | None = False + cache_stale_time_seconds: int = 0 + api_rate_limit: int | None = None + api_rate_limit_period: int | None = None + + +class FlaskReqLimiter: + """ + Flask request limiter + + This is a simple request limiter that can be used to rate limit requests to a Flask app. + It uses a simple in-memory store to keep track of the number of requests per IP address. + + Usage: + ``` + from util.flask import FlaskReqLimiter + + app.req_limiter = FlaskReqLimiter(max_reqs_per_sec=100) + + @app.before_request + def rate_limit(): + return app.req_limiter.rate_limit() + ``` + """ + + def __init__(self, max_reqs_per_sec: int = 100, rate_limit_period: int = 60): + self.store = {} + self.max_reqs_per_sec = max_reqs_per_sec + self.rate_limit_period = rate_limit_period + + def rate_limit(self): + now = time.time() + ip_address = flask.request.remote_addr + requests, expire = self.store.get(ip_address, (0, now + self.rate_limit_period)) + + if now > expire: + self.store[ip_address] = (1, now + self.rate_limit_period) + return + + if requests >= self.max_reqs_per_sec: + return flask.abort(429) + + self.store[ip_address] = (requests + 1, expire) + + +class FlaskApp(flask.Flask): + def __init__(self, config: FlaskAppConfig): + name = config.name if config.name else __name__ + super().__init__(name) + log = Log(name, enable_perf=config.enable_perf) + log.set_level(config.log_level) + self.log = log.logger + + git_rev = subprocess.run( + ["git", "rev-parse", "--short=9", "HEAD"], stdout=subprocess.PIPE, text=True + ) + self.git_rev = git_rev.stdout.strip() if git_rev.returncode == 0 else "(unknown)" + + # Creates a generic logger to pipe other packages logs into the main app logger + if config.log_level_generic is not None: + generic_logger = Log(None) + generic_logger.set_level(config.log_level_generic) + + self.cache = Cache(stale_time_seconds=config.cache_stale_time_seconds) + + if config.api_rate_limit is not None and config.api_rate_limit_period is not None: + self.log.info(f"IP Rate limit: {config.api_rate_limit} per {config.api_rate_limit_period} seconds") + self.wsgi_app = ProxyFix(self.wsgi_app) + self.req_limiter = FlaskReqLimiter(max_reqs_per_sec=config.api_rate_limit, + rate_limit_period=config.api_rate_limit_period) diff --git a/src/util/parse.py b/src/util/parse.py new file mode 100644 index 0000000..4dc907a --- /dev/null +++ b/src/util/parse.py @@ -0,0 +1,196 @@ +import re +from typing import Callable, Any, Union +from functools import partial + +import string + +import eth_utils +import flask +import oxenc +from eth_typing import ChecksumAddress +from werkzeug.routing import BaseConverter + +eth_regex = "0x[0-9a-fA-F]{40}" + +def parse_bls_pubkey(bls_pubkey: dict): + x, y = bls_pubkey["X"], bls_pubkey["Y"] + return f"{x:064x}{y:064x}" + +def parse_ed25519_pubkey(ed25519_pubkey: int): + return f"{ed25519_pubkey:064x}" + +def raw_eth_addr(k, v): + if re.fullmatch(eth_regex, v): + if not eth_utils.is_address(v): + raise ParseError(k, "ETH address checksum failed") + return bytes.fromhex(v[2:]) + raise ParseError(k, "not an ETH address") + +def get_relative_time_from_ms(ms: int, short: bool = False, include_suffix = False): + if include_suffix: + prefix, suffix = ("in ", "") if ms > 0 else ("", " ago") + else: + prefix, suffix = "", "" + + if ms < 1000: + time = "{} {}".format(ms, ("ms" if short else "milliseconds")) + elif ms < 1000 * 60: + time = "{} {}".format(ms // 1000, ("s" if short else "seconds")) + elif ms < 1000 * 60 * 60: + time = "{} {}".format(ms // (1000 * 60), ("m" if short else "minutes")) + elif ms < 1000 * 60 * 60 * 24: + time = "{} {}".format(ms // (1000 * 60 * 60), ("h" if short else "hours")) + else: + time = "{} {}".format(ms // (1000 * 60 * 60 * 24), ("d" if short else "days")) + + return f"{prefix}{time}{suffix}" + +def hexify(container): + """ + Takes a dict or list and mutates it to change any `bytes` values in it to str hex representation + of the bytes, recursively. + """ + if isinstance(container, dict): + it = container.items() + elif isinstance(container, list): + it = enumerate(container) + else: + return + + for i, v in it: + if isinstance(v, bytes): + container[i] = v.hex() + else: + hexify(v) + +def eth_format(addr: Union[bytes, str]) -> ChecksumAddress: + try: + return eth_utils.to_checksum_address(addr) + except ValueError: + raise ParseError(addr, "Invalid ETH address") + +class EthConverter(BaseConverter): + def __init__(self, url_map): + super().__init__(url_map) + self.regex = eth_regex + + +# Validates that input is 64 hex bytes and converts it to 32 bytes. +class Hex64Converter(BaseConverter): + def __init__(self, url_map): + super().__init__(url_map) + self.regex = "[0-9a-fA-F]{64}" + + def to_python(self, value): + return bytes.fromhex(value) + + def to_url(self, value): + return value.hex() + + +class ParseError(ValueError): + def __init__(self, field, reason): + self.field = field + super().__init__(f"{field}: {reason}") + + +class ParseMissingError(ParseError): + def __init__(self, field): + super().__init__(field, f"required parameter is missing") + + +class ParseUnknownError(ParseError): + def __init__(self, field): + super().__init__(field, f"unknown parameter") + + +class ParseMultipleError(ParseError): + def __init__(self, field): + super().__init__(field, f"cannot be specified multiple times") + + +def parse_query_params(params: dict[str, Callable[[str, str], Any]]): + """ + Takes a dict of fields and callables such as: + + { + "field": ("out", callable), + ... + } + + where: + - `"field"` is the expected query string name + - `callable` will be invoked as `callable("field", value)` to determined the returned value. + + On error, throws a ParseError with `.field` set to the "field" name that triggered the error. + + Notes: + - callable should throw a ParseError for an unaccept input value. + - if "-field" starts with "-" then the field is optional; otherwise it is an error if not + provided. The "-" is not included in the returned key. + - if "field" ends with "[]" then the value will be an array of values returned by the callable, + and the parameter can be specified multiple times. Otherwise a value can be specified only + once. The "[]" is not included in the returned key. + - you can do both of the above: "-field[]" will allow the value to be provided zero or more + times; the value will be omitted if not present in the input, and an array (under the "field") + key if provided at least once. + """ + + parsed = {} + + param_map = { + k.removeprefix("-").removesuffix("[]"): ( + k.startswith("-"), + k.endswith("[]"), + cb, + ) + for k, cb in params.items() + } + + for k, v in flask.request.values.items(multi=True): + found = param_map.get(k) + if found is None: + raise ParseUnknownError(k) + + _, multi, callback = found + + if multi: + parsed.setdefault(k, []).append(callback(k, v) if callback else v) + elif k not in parsed: + parsed[k] = callback(k, v) if callback else v + else: + raise ParseMultipleError(k) + + for k, p in param_map.items(): + optional = p[0] + if not optional and k not in flask.request.values: + raise ParseMissingError(k) + + return parsed + + +# Decodes `x` into a bytes of length `length`. `x` should be hex or base64 encoded, without +# whitespace. Both regular and "URL-safe" base64 are accepted. Padding is optional for base64 +# values. Throws ParseError if the input is invalid or of the wrong size. `length` must be at +# least 5 (smaller byte values are harder or even ambiguous to distinguish between hex and base64). +def decode_bytes(k, x, length): + assert length >= 5 + + hex_len = length * 2 + b64_unpadded = (length * 4 + 2) // 3 + b64_padded = (length + 2) // 3 * 4 + + if len(x) == hex_len and all(c in string.hexdigits for c in x): + return bytes.fromhex(x) + if len(x) in (b64_unpadded, b64_padded): + if oxenc.is_base64(x): + return oxenc.from_base64(x) + if "-" in x or "_" in x: # Looks like (maybe) url-safe b64 + x = x.replace("/", "_").replace("+", "-") + if oxenc.is_base64(x): + return oxenc.from_base64(x) + raise ParseError(k, f"expected {hex_len} hex or {b64_unpadded} base64 characters") + + +def byte_decoder(length: int): + return partial(decode_bytes, length=length) diff --git a/src/web3client/__init__.py b/src/web3client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/web3client/abi_manager.py b/src/web3client/abi_manager.py new file mode 100644 index 0000000..a173ded --- /dev/null +++ b/src/web3client/abi_manager.py @@ -0,0 +1,84 @@ +import json +import os +from dataclasses import dataclass + + +@dataclass +class ABIData: + name: str + abi: dict + bytecode: bytes + deployed_bytecode: bytes + + +class ABIManager: + + cache: dict[str, ABIData] = {} + + def __init__(self, db_writer=None, abi_dir="src/web3client/abis"): + """ + Initializes the ABIManager with the directory containing ABI JSON files. + + :param db_writer: The db writer to use for interacting with the db. + :param abi_dir: The directory where ABI files are stored. Default is 'abis'. + """ + self.abi_dir = abi_dir + abis = self.load_all_abis() + if db_writer is not None: + db_writer.write_smart_contract_abis_to_db(abis) + + def get_abi(self, contract_name): + """ + Gets the ABI for a contract. + """ + if contract_name in self.cache: + return self.cache[contract_name].abi + + return self.load_abi(contract_name) + + def load_abi(self, file_name): + """ + Loads the ABI from a specified artifact JSON file. + + :param file_name: The name of the artifact file (without .json extension). + :return: The ABI extracted from the specified artifact JSON file. + :raises FileNotFoundError: If the specified file does not exist. + :raises KeyError: If the 'abi' key is not found in the JSON data. + """ + if file_name in self.cache: + return self.cache[file_name] + + file_path = os.path.join(self.abi_dir, "{}.json".format(file_name)) + if not os.path.exists(file_path): + raise FileNotFoundError("No such file: {}".format(file_path)) + + with open(file_path, "r") as file: + data = json.load(file) + if "abi" not in data: + raise KeyError("Missing 'abi' key in the JSON file.") + abi = data["abi"] + name = data["contractName"] + bytecode_bytes = data["bytecode"] + deployed_bytecode_bytes = data["deployedBytecode"] + data = ABIData(name, abi, bytecode_bytes, deployed_bytecode_bytes) + self.cache[file_name] = data + return data + + def load_all_abis(self): + """ + Loads all ABIs from the specified directory. + + :return: A dictionary where the keys are the artifact names and the values are the ABIs. + """ + abis = [] + for file in os.listdir(self.abi_dir): + if file.endswith(".json"): + name = file[:-5] + abis.append(self.load_abi(name)) + return abis + + +# Example usage: +# manager = ABIManager() +# abi = manager.load_abi('MyContract') +# This `abi` can now be used with Web3 library to interact with a smart contract. diff --git a/abis/RewardRatePool.json b/src/web3client/abis/RewardRatePool.json similarity index 95% rename from abis/RewardRatePool.json rename to src/web3client/abis/RewardRatePool.json index 48ebf9a..aeac884 100644 --- a/abis/RewardRatePool.json +++ b/src/web3client/abis/RewardRatePool.json @@ -178,7 +178,7 @@ }, { "inputs": [], - "name": "SENT", + "name": "SESH", "outputs": [ { "internalType": "contract IERC20", @@ -268,7 +268,7 @@ }, { "internalType": "address", - "name": "_sent", + "name": "_sesh", "type": "address" } ], @@ -383,8 +383,8 @@ "type": "function" } ], - "bytecode": "0x6080604052348015600f57600080fd5b50610ba98061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100e65760003560e01c80631357e1dc146100eb5780631c31f7101461010757806332f7c3841461011c57806338af3eed14610124578063485cc9551461014457806360e52ecb14610157578063715018a61461016a57806379ba5097146101725780637b0a47ee1461017a5780638da5cb5b14610182578063ab1a4a2f1461018a578063ab5fcdc91461019f578063ca598087146101a8578063d66fcb69146101b0578063e1f1c4a7146101c3578063e30c3978146101cc578063f2fde38b146101d4578063f85bb0f0146101e7575b600080fd5b6100f460025481565b6040519081526020015b60405180910390f35b61011a6101153660046109fc565b6101ef565b005b6100f461024d565b600154610137906001600160a01b031681565b6040516100fe9190610a17565b61011a610152366004610a2b565b6102ef565b600054610137906001600160a01b031681565b61011a610422565b61011a610436565b6100f461047e565b6101376104b2565b610192609781565b6040516100fe9190610a5e565b6100f460035481565b6100f46104cd565b6100f46101be366004610a72565b610554565b6101926103e881565b610137610598565b61011a6101e23660046109fc565b6105a3565b61011a610614565b6101f7610690565b600180546001600160a01b0319166001600160a01b0383161790556040517feee59a71c694e68368a1cb0d135c448051bbfb12289e6c2223b0ceb100c2321d90610242908390610a17565b60405180910390a150565b6000806003544261025e9190610aaa565b6000546040516370a0823160e01b81529192506102dc916001600160a01b03909116906370a0823190610295903090600401610a17565b602060405180830381865afa1580156102b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102d69190610abd565b82610554565b6002546102e99190610ad6565b91505090565b60006102f96106c2565b805490915060ff600160401b82041615906001600160401b03166000811580156103205750825b90506000826001600160401b0316600114801561033c5750303b155b90508115801561034a575080155b156103685760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b0319166001178555831561039157845460ff60401b1916600160401b1785555b600180546001600160a01b03808a166001600160a01b0319928316179092554260035560008054928916929091169190911790556103ce336106e6565b831561041957845460ff60401b191685556040517fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29061041090600190610a5e565b60405180910390a15b50505050505050565b61042a610690565b61043460006106f7565b565b3380610440610598565b6001600160a01b031614610472578060405163118cdaa760e01b81526004016104699190610a17565b60405180910390fd5b61047b816106f7565b50565b60008061048961024d565b905060006104956104cd565b90506104ab6104a48383610aaa565b6078610554565b9250505090565b6000806104bd61071a565b546001600160a01b031692915050565b600254600080546040516370a0823160e01b81529192916001600160a01b03909116906370a0823190610504903090600401610a17565b602060405180830381865afa158015610521573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105459190610abd565b61054f9190610ad6565b905090565b60006105666103e86301e13380610ae9565b6001600160401b03168261057b609786610b12565b6105859190610b12565b61058f9190610b29565b90505b92915050565b6000806104bd61073e565b6105ab610690565b60006105b561073e565b80546001600160a01b0319166001600160a01b03841690811782559091506105db6104b2565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b600061061e61024d565b90506000600254826106309190610aaa565b6002839055426003556040518181529091507f952b264c8e0a06cddb4bbaa6d6af1d565145329fd95bbe72cb2b53942b2dc9669060200160405180910390a160015460005461068c916001600160a01b03918216911683610762565b5050565b336106996104b2565b6001600160a01b031614610434573360405163118cdaa760e01b81526004016104699190610a17565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6106ee6107b9565b61047b816107de565b600061070161073e565b80546001600160a01b0319168155905061068c82610810565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526107b490849061086c565b505050565b6107c16108c6565b61043457604051631afcd79f60e31b815260040160405180910390fd5b6107e66107b9565b6001600160a01b038116610472576000604051631e4fbdf760e01b81526004016104699190610a17565b600061081a61071a565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b60006108816001600160a01b038416836108e0565b905080516000141580156108a65750808060200190518101906108a49190610b4b565b155b156107b45782604051635274afe760e01b81526004016104699190610a17565b60006108d06106c2565b54600160401b900460ff16919050565b606061058f8383600084600080856001600160a01b031684866040516109069190610b6d565b60006040518083038185875af1925050503d8060008114610943576040519150601f19603f3d011682016040523d82523d6000602084013e610948565b606091505b5091509150610958868383610964565b925050505b9392505050565b60608261097957610974826109b7565b61095d565b815115801561099057506001600160a01b0384163b155b156109b05783604051639996b31560e01b81526004016104699190610a17565b508061095d565b8051156109c75780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80356001600160a01b03811681146109f757600080fd5b919050565b600060208284031215610a0e57600080fd5b61058f826109e0565b6001600160a01b0391909116815260200190565b60008060408385031215610a3e57600080fd5b610a47836109e0565b9150610a55602084016109e0565b90509250929050565b6001600160401b0391909116815260200190565b60008060408385031215610a8557600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b8181038181111561059257610592610a94565b600060208284031215610acf57600080fd5b5051919050565b8082018082111561059257610592610a94565b6001600160401b038181168382160290811690818114610b0b57610b0b610a94565b5092915050565b808202811582820484141761059257610592610a94565b600082610b4657634e487b7160e01b600052601260045260246000fd5b500490565b600060208284031215610b5d57600080fd5b8151801515811461095d57600080fd5b6000825160005b81811015610b8e5760208186018101518583015201610b74565b50600092019182525091905056fea164736f6c634300081a000a", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100e65760003560e01c80631357e1dc146100eb5780631c31f7101461010757806332f7c3841461011c57806338af3eed14610124578063485cc9551461014457806360e52ecb14610157578063715018a61461016a57806379ba5097146101725780637b0a47ee1461017a5780638da5cb5b14610182578063ab1a4a2f1461018a578063ab5fcdc91461019f578063ca598087146101a8578063d66fcb69146101b0578063e1f1c4a7146101c3578063e30c3978146101cc578063f2fde38b146101d4578063f85bb0f0146101e7575b600080fd5b6100f460025481565b6040519081526020015b60405180910390f35b61011a6101153660046109fc565b6101ef565b005b6100f461024d565b600154610137906001600160a01b031681565b6040516100fe9190610a17565b61011a610152366004610a2b565b6102ef565b600054610137906001600160a01b031681565b61011a610422565b61011a610436565b6100f461047e565b6101376104b2565b610192609781565b6040516100fe9190610a5e565b6100f460035481565b6100f46104cd565b6100f46101be366004610a72565b610554565b6101926103e881565b610137610598565b61011a6101e23660046109fc565b6105a3565b61011a610614565b6101f7610690565b600180546001600160a01b0319166001600160a01b0383161790556040517feee59a71c694e68368a1cb0d135c448051bbfb12289e6c2223b0ceb100c2321d90610242908390610a17565b60405180910390a150565b6000806003544261025e9190610aaa565b6000546040516370a0823160e01b81529192506102dc916001600160a01b03909116906370a0823190610295903090600401610a17565b602060405180830381865afa1580156102b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102d69190610abd565b82610554565b6002546102e99190610ad6565b91505090565b60006102f96106c2565b805490915060ff600160401b82041615906001600160401b03166000811580156103205750825b90506000826001600160401b0316600114801561033c5750303b155b90508115801561034a575080155b156103685760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b0319166001178555831561039157845460ff60401b1916600160401b1785555b600180546001600160a01b03808a166001600160a01b0319928316179092554260035560008054928916929091169190911790556103ce336106e6565b831561041957845460ff60401b191685556040517fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29061041090600190610a5e565b60405180910390a15b50505050505050565b61042a610690565b61043460006106f7565b565b3380610440610598565b6001600160a01b031614610472578060405163118cdaa760e01b81526004016104699190610a17565b60405180910390fd5b61047b816106f7565b50565b60008061048961024d565b905060006104956104cd565b90506104ab6104a48383610aaa565b6078610554565b9250505090565b6000806104bd61071a565b546001600160a01b031692915050565b600254600080546040516370a0823160e01b81529192916001600160a01b03909116906370a0823190610504903090600401610a17565b602060405180830381865afa158015610521573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105459190610abd565b61054f9190610ad6565b905090565b60006105666103e86301e13380610ae9565b6001600160401b03168261057b609786610b12565b6105859190610b12565b61058f9190610b29565b90505b92915050565b6000806104bd61073e565b6105ab610690565b60006105b561073e565b80546001600160a01b0319166001600160a01b03841690811782559091506105db6104b2565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b600061061e61024d565b90506000600254826106309190610aaa565b6002839055426003556040518181529091507f952b264c8e0a06cddb4bbaa6d6af1d565145329fd95bbe72cb2b53942b2dc9669060200160405180910390a160015460005461068c916001600160a01b03918216911683610762565b5050565b336106996104b2565b6001600160a01b031614610434573360405163118cdaa760e01b81526004016104699190610a17565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6106ee6107b9565b61047b816107de565b600061070161073e565b80546001600160a01b0319168155905061068c82610810565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526107b490849061086c565b505050565b6107c16108c6565b61043457604051631afcd79f60e31b815260040160405180910390fd5b6107e66107b9565b6001600160a01b038116610472576000604051631e4fbdf760e01b81526004016104699190610a17565b600061081a61071a565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b60006108816001600160a01b038416836108e0565b905080516000141580156108a65750808060200190518101906108a49190610b4b565b155b156107b45782604051635274afe760e01b81526004016104699190610a17565b60006108d06106c2565b54600160401b900460ff16919050565b606061058f8383600084600080856001600160a01b031684866040516109069190610b6d565b60006040518083038185875af1925050503d8060008114610943576040519150601f19603f3d011682016040523d82523d6000602084013e610948565b606091505b5091509150610958868383610964565b925050505b9392505050565b60608261097957610974826109b7565b61095d565b815115801561099057506001600160a01b0384163b155b156109b05783604051639996b31560e01b81526004016104699190610a17565b508061095d565b8051156109c75780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80356001600160a01b03811681146109f757600080fd5b919050565b600060208284031215610a0e57600080fd5b61058f826109e0565b6001600160a01b0391909116815260200190565b60008060408385031215610a3e57600080fd5b610a47836109e0565b9150610a55602084016109e0565b90509250929050565b6001600160401b0391909116815260200190565b60008060408385031215610a8557600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b8181038181111561059257610592610a94565b600060208284031215610acf57600080fd5b5051919050565b8082018082111561059257610592610a94565b6001600160401b038181168382160290811690818114610b0b57610b0b610a94565b5092915050565b808202811582820484141761059257610592610a94565b600082610b4657634e487b7160e01b600052601260045260246000fd5b500490565b600060208284031215610b5d57600080fd5b8151801515811461095d57600080fd5b6000825160005b81811015610b8e5760208186018101518583015201610b74565b50600092019182525091905056fea164736f6c634300081a000a", + "bytecode": "0x6080604052348015600f57600080fd5b50610ba98061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100e65760003560e01c80631357e1dc146100eb5780631c31f7101461010757806332f7c3841461011c57806338af3eed14610124578063485cc95514610144578063715018a61461015757806379ba50971461015f5780637b0a47ee146101675780638da5cb5b1461016f578063ab1a4a2f14610177578063ab5fcdc91461018c578063c3aca55814610195578063ca598087146101a8578063d66fcb69146101b0578063e1f1c4a7146101c3578063e30c3978146101cc578063f2fde38b146101d4578063f85bb0f0146101e7575b600080fd5b6100f460025481565b6040519081526020015b60405180910390f35b61011a6101153660046109fc565b6101ef565b005b6100f461024d565b600154610137906001600160a01b031681565b6040516100fe9190610a17565b61011a610152366004610a2b565b6102ef565b61011a610422565b61011a610436565b6100f461047e565b6101376104b2565b61017f609781565b6040516100fe9190610a5e565b6100f460035481565b600054610137906001600160a01b031681565b6100f46104cd565b6100f46101be366004610a72565b610554565b61017f6103e881565b610137610598565b61011a6101e23660046109fc565b6105a3565b61011a610614565b6101f7610690565b600180546001600160a01b0319166001600160a01b0383161790556040517feee59a71c694e68368a1cb0d135c448051bbfb12289e6c2223b0ceb100c2321d90610242908390610a17565b60405180910390a150565b6000806003544261025e9190610aaa565b6000546040516370a0823160e01b81529192506102dc916001600160a01b03909116906370a0823190610295903090600401610a17565b602060405180830381865afa1580156102b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102d69190610abd565b82610554565b6002546102e99190610ad6565b91505090565b60006102f96106c2565b805490915060ff600160401b82041615906001600160401b03166000811580156103205750825b90506000826001600160401b0316600114801561033c5750303b155b90508115801561034a575080155b156103685760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b0319166001178555831561039157845460ff60401b1916600160401b1785555b600180546001600160a01b03808a166001600160a01b0319928316179092554260035560008054928916929091169190911790556103ce336106e6565b831561041957845460ff60401b191685556040517fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29061041090600190610a5e565b60405180910390a15b50505050505050565b61042a610690565b61043460006106f7565b565b3380610440610598565b6001600160a01b031614610472578060405163118cdaa760e01b81526004016104699190610a17565b60405180910390fd5b61047b816106f7565b50565b60008061048961024d565b905060006104956104cd565b90506104ab6104a48383610aaa565b6078610554565b9250505090565b6000806104bd61071a565b546001600160a01b031692915050565b600254600080546040516370a0823160e01b81529192916001600160a01b03909116906370a0823190610504903090600401610a17565b602060405180830381865afa158015610521573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105459190610abd565b61054f9190610ad6565b905090565b60006105666103e86301e13380610ae9565b6001600160401b03168261057b609786610b12565b6105859190610b12565b61058f9190610b29565b90505b92915050565b6000806104bd61073e565b6105ab610690565b60006105b561073e565b80546001600160a01b0319166001600160a01b03841690811782559091506105db6104b2565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b600061061e61024d565b90506000600254826106309190610aaa565b6002839055426003556040518181529091507f952b264c8e0a06cddb4bbaa6d6af1d565145329fd95bbe72cb2b53942b2dc9669060200160405180910390a160015460005461068c916001600160a01b03918216911683610762565b5050565b336106996104b2565b6001600160a01b031614610434573360405163118cdaa760e01b81526004016104699190610a17565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6106ee6107b9565b61047b816107de565b600061070161073e565b80546001600160a01b0319168155905061068c82610810565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526107b490849061086c565b505050565b6107c16108c6565b61043457604051631afcd79f60e31b815260040160405180910390fd5b6107e66107b9565b6001600160a01b038116610472576000604051631e4fbdf760e01b81526004016104699190610a17565b600061081a61071a565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b60006108816001600160a01b038416836108e0565b905080516000141580156108a65750808060200190518101906108a49190610b4b565b155b156107b45782604051635274afe760e01b81526004016104699190610a17565b60006108d06106c2565b54600160401b900460ff16919050565b606061058f8383600084600080856001600160a01b031684866040516109069190610b6d565b60006040518083038185875af1925050503d8060008114610943576040519150601f19603f3d011682016040523d82523d6000602084013e610948565b606091505b5091509150610958868383610964565b925050505b9392505050565b60608261097957610974826109b7565b61095d565b815115801561099057506001600160a01b0384163b155b156109b05783604051639996b31560e01b81526004016104699190610a17565b508061095d565b8051156109c75780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80356001600160a01b03811681146109f757600080fd5b919050565b600060208284031215610a0e57600080fd5b61058f826109e0565b6001600160a01b0391909116815260200190565b60008060408385031215610a3e57600080fd5b610a47836109e0565b9150610a55602084016109e0565b90509250929050565b6001600160401b0391909116815260200190565b60008060408385031215610a8557600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b8181038181111561059257610592610a94565b600060208284031215610acf57600080fd5b5051919050565b8082018082111561059257610592610a94565b6001600160401b038181168382160290811690818114610b0b57610b0b610a94565b5092915050565b808202811582820484141761059257610592610a94565b600082610b4657634e487b7160e01b600052601260045260246000fd5b500490565b600060208284031215610b5d57600080fd5b8151801515811461095d57600080fd5b6000825160005b81811015610b8e5760208186018101518583015201610b74565b50600092019182525091905056fea164736f6c634300081a000a", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100e65760003560e01c80631357e1dc146100eb5780631c31f7101461010757806332f7c3841461011c57806338af3eed14610124578063485cc95514610144578063715018a61461015757806379ba50971461015f5780637b0a47ee146101675780638da5cb5b1461016f578063ab1a4a2f14610177578063ab5fcdc91461018c578063c3aca55814610195578063ca598087146101a8578063d66fcb69146101b0578063e1f1c4a7146101c3578063e30c3978146101cc578063f2fde38b146101d4578063f85bb0f0146101e7575b600080fd5b6100f460025481565b6040519081526020015b60405180910390f35b61011a6101153660046109fc565b6101ef565b005b6100f461024d565b600154610137906001600160a01b031681565b6040516100fe9190610a17565b61011a610152366004610a2b565b6102ef565b61011a610422565b61011a610436565b6100f461047e565b6101376104b2565b61017f609781565b6040516100fe9190610a5e565b6100f460035481565b600054610137906001600160a01b031681565b6100f46104cd565b6100f46101be366004610a72565b610554565b61017f6103e881565b610137610598565b61011a6101e23660046109fc565b6105a3565b61011a610614565b6101f7610690565b600180546001600160a01b0319166001600160a01b0383161790556040517feee59a71c694e68368a1cb0d135c448051bbfb12289e6c2223b0ceb100c2321d90610242908390610a17565b60405180910390a150565b6000806003544261025e9190610aaa565b6000546040516370a0823160e01b81529192506102dc916001600160a01b03909116906370a0823190610295903090600401610a17565b602060405180830381865afa1580156102b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102d69190610abd565b82610554565b6002546102e99190610ad6565b91505090565b60006102f96106c2565b805490915060ff600160401b82041615906001600160401b03166000811580156103205750825b90506000826001600160401b0316600114801561033c5750303b155b90508115801561034a575080155b156103685760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b0319166001178555831561039157845460ff60401b1916600160401b1785555b600180546001600160a01b03808a166001600160a01b0319928316179092554260035560008054928916929091169190911790556103ce336106e6565b831561041957845460ff60401b191685556040517fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29061041090600190610a5e565b60405180910390a15b50505050505050565b61042a610690565b61043460006106f7565b565b3380610440610598565b6001600160a01b031614610472578060405163118cdaa760e01b81526004016104699190610a17565b60405180910390fd5b61047b816106f7565b50565b60008061048961024d565b905060006104956104cd565b90506104ab6104a48383610aaa565b6078610554565b9250505090565b6000806104bd61071a565b546001600160a01b031692915050565b600254600080546040516370a0823160e01b81529192916001600160a01b03909116906370a0823190610504903090600401610a17565b602060405180830381865afa158015610521573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105459190610abd565b61054f9190610ad6565b905090565b60006105666103e86301e13380610ae9565b6001600160401b03168261057b609786610b12565b6105859190610b12565b61058f9190610b29565b90505b92915050565b6000806104bd61073e565b6105ab610690565b60006105b561073e565b80546001600160a01b0319166001600160a01b03841690811782559091506105db6104b2565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b600061061e61024d565b90506000600254826106309190610aaa565b6002839055426003556040518181529091507f952b264c8e0a06cddb4bbaa6d6af1d565145329fd95bbe72cb2b53942b2dc9669060200160405180910390a160015460005461068c916001600160a01b03918216911683610762565b5050565b336106996104b2565b6001600160a01b031614610434573360405163118cdaa760e01b81526004016104699190610a17565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6106ee6107b9565b61047b816107de565b600061070161073e565b80546001600160a01b0319168155905061068c82610810565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526107b490849061086c565b505050565b6107c16108c6565b61043457604051631afcd79f60e31b815260040160405180910390fd5b6107e66107b9565b6001600160a01b038116610472576000604051631e4fbdf760e01b81526004016104699190610a17565b600061081a61071a565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b60006108816001600160a01b038416836108e0565b905080516000141580156108a65750808060200190518101906108a49190610b4b565b155b156107b45782604051635274afe760e01b81526004016104699190610a17565b60006108d06106c2565b54600160401b900460ff16919050565b606061058f8383600084600080856001600160a01b031684866040516109069190610b6d565b60006040518083038185875af1925050503d8060008114610943576040519150601f19603f3d011682016040523d82523d6000602084013e610948565b606091505b5091509150610958868383610964565b925050505b9392505050565b60608261097957610974826109b7565b61095d565b815115801561099057506001600160a01b0384163b155b156109b05783604051639996b31560e01b81526004016104699190610a17565b508061095d565b8051156109c75780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80356001600160a01b03811681146109f757600080fd5b919050565b600060208284031215610a0e57600080fd5b61058f826109e0565b6001600160a01b0391909116815260200190565b60008060408385031215610a3e57600080fd5b610a47836109e0565b9150610a55602084016109e0565b90509250929050565b6001600160401b0391909116815260200190565b60008060408385031215610a8557600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b8181038181111561059257610592610a94565b600060208284031215610acf57600080fd5b5051919050565b8082018082111561059257610592610a94565b6001600160401b038181168382160290811690818114610b0b57610b0b610a94565b5092915050565b808202811582820484141761059257610592610a94565b600082610b4657634e487b7160e01b600052601260045260246000fd5b500490565b600060208284031215610b5d57600080fd5b8151801515811461095d57600080fd5b6000825160005b81811015610b8e5760208186018101518583015201610b74565b50600092019182525091905056fea164736f6c634300081a000a", "linkReferences": {}, "deployedLinkReferences": {} } diff --git a/abis/ServiceNodeContribution.json b/src/web3client/abis/ServiceNodeContribution.json similarity index 98% rename from abis/ServiceNodeContribution.json rename to src/web3client/abis/ServiceNodeContribution.json index b2ff513..9abc20c 100644 --- a/abis/ServiceNodeContribution.json +++ b/src/web3client/abis/ServiceNodeContribution.json @@ -661,7 +661,7 @@ }, { "inputs": [], - "name": "SENT", + "name": "SESH", "outputs": [ { "internalType": "contract IERC20", @@ -1542,8 +1542,8 @@ "type": "function" } ], - "bytecode": "0x610120604052600f805460ff1916905534801561001b57600080fd5b5060405161543d38038061543d83398101604081905261003a9161196c565b866001600160a01b0381166100a45760405162461bcd60e51b815260206004820152602560248201527f5368617265643a205a65726f2d61646472657373206973206e6f74207065726d6044820152641a5d1d195960da1b60648201526084015b60405180910390fd5b86806000036100f55760405162461bcd60e51b815260206004820152601b60248201527f5368617265643a2075696e7420696e70757420697320656d7074790000000000604482015260640161009b565b6001600160a01b03891660a081905260408051630ada733d60e31b815290516356d399e8916004808201926020929091908290030181865afa15801561013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101639190611a30565b60c0818152505060a0516001600160a01b0316637c89d2f06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ce9190611a49565b6001600160a01b03166080526101008890523260e05260006101f588888888888680610204565b50505050505050505050611c7f565b61020c61026c565b845160208601516040870151610225928a928a92610398565b6060850151610233906104ad565b61023c84610537565b600f805461ff0019166101008515150217905580156102635760e051610263908383610859565b50505050505050565b600c5460005b81811015610334576000600c828154811061028f5761028f611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff1660038111156102d5576102d5611751565b141580156102e35750600081115b156102ff576080516102ff906001600160a01b03168383610e71565b61032a826001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b5050600101610272565b50610341600c60006116cd565b600f805460ff191690556040805160008082526020820190925281610388565b60408051808201909152600080825260208201528152602001906001900390816103615790505b50905061039481610537565b5050565b6000600f5460ff1660038111156103b1576103b1611751565b146103d657600f54604051639a0293fd60e01b815261009b9160ff1690600401611a7c565b60a05160e0516040805160016226579360e01b03198152885160048201526020808a01516024830152885160448301528801516064820152908701516084820152606087015160a48201526001600160a01b0391821660c482015260e4810186905291169063ffd9a86d9061010401600060405180830381600087803b15801561045f57600080fd5b505af1158015610473573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6000600f5460ff1660038111156104c6576104c6611751565b146104eb57600f5460405163ad88fc8f60e01b815261009b9160ff1690600401611a7c565b61271061ffff8216111561051f57604051624ae8fd60e41b815261ffff82166004820152612710602482015260440161009b565b6005805461ffff191661ffff92909216919091179055565b6000600f5460ff16600381111561055057610550611751565b1461057557600f546040516312ba63f960e11b815261009b9160ff1690600401611a7c565b600e5460005b818110156105d457600d6000600e838154811061059a5761059a611a66565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff191690550161057b565b506105e1600e60006116ee565b5060c051610100518251111561061957815161010051604051630962235760e31b81526004810192909252602482015260440161009b565b815160005b8181101561085357806000036106955760e0516001600160a01b031684828151811061064c5761064c611a66565b6020026020010151600001516001600160a01b0316146106955760e05160405163ef77b46760e01b8152600481018390526001600160a01b03909116602482015260440161009b565b60006001600160a01b03168482815181106106b2576106b2611a66565b6020026020010151600001516001600160a01b0316036106e85760405163621caef560e11b81526004810182905260240161009b565b6000600d600086848151811061070057610700611a66565b6020026020010151600001516001600160a01b03166001600160a01b0316815260200190815260200160002090508060000154600014610756576040516308308c2360e21b81526004810183905260240161009b565b600061076c858461010051610ece60201b60201c565b9050600086848151811061078257610782611a66565b6020026020010151602001519050818110156107b75783818360405163c4aa8aa360e01b815260040161009b93929190611aa4565b808610156107de57838187604051636d3ae3a760e01b815260040161009b93929190611aa4565b6107e88187611ad0565b9550600e8785815181106107fe576107fe611a66565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161061e565b50505050565b6000600f5460ff16600381111561087257610872611751565b1415801561089757506001600f5460ff16600381111561089457610894611751565b14155b156108bc57600f54604051630295f95f60e51b815261009b9160ff1690600401611a7c565b60a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108fc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109209190611a30565b61010051146109b3576101005160a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109919190611a30565b604051631248081b60e31b81526004810192909252602482015260440161009b565b60a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a179190611a30565b60c05114610a625760c05160a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b6000600f5460ff166003811115610a7b57610a7b611751565b03610b145760e0516001600160a01b0316836001600160a01b031614610ab95760e0516040516316a4ebc160e21b815261009b918591600401611ae3565b600f805460ff1916600117905560e05160025460055460405161ffff90911681526001600160a01b03909216917fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d60205260409020805415801590610b415750600181015460ff16155b15610b8a578054821015610b7557805460405163454cd8e160e01b815261009b918491600401918252602082015260400190565b6001818101805460ff19169091179055610be6565b6001600160a01b0384166000908152600a6020526040902054158015610bb65750610bb3610f65565b82105b15610be65781610bc4610f65565b6040516310e45ff160e21b81526004810192909252602482015260440161009b565b6001600160a01b0384166000908152600a60205260408120549003610cb7576000610c11858561102b565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8909101805491909316911617905550610cc1565b610cc18484611049565b6001600160a01b0384166000908152600a602052604081208054849290610ce9908490611afd565b90915550506001600160a01b0384166000908152600b60205260408120429055610d116111ca565b90506000610d1d611236565b60c051909150610d2d8284611afd565b1115610d5457818160c05160405163513b428b60e11b815260040161009b93929190611aa4565b61010051600c541115610d8157610100516040516315b5c3d560e21b815260040161009b91815260200190565b60c0518203610dd35760e0516002546040516001600160a01b03909216917f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa285604051610e0e91815260200190565b60405180910390a2608051610e2e906001600160a01b03168730876112a8565b6002600f5460ff166003811115610e4757610e47611751565b148015610e5c5750600f54610100900460ff16155b15610e6957610e696112e1565b505050505050565b610ec983846001600160a01b031663a9059cbb8585604051602401610e97929190611b10565b60408051808303601f1901815291905260208101805160e09390931b6001600160e01b03938416179052915061154216565b505050565b6000828211610efa576040516376675ed960e11b8152600481018490526024810183905260440161009b565b82600003610f2b576004610f0f600186611ad0565b610f199190611b29565b610f24906001611afd565b9050610f5e565b6000610f378484611ad0565b905080610f45600187611ad0565b610f4f9190611b29565b610f5a906001611afd565b9150505b9392505050565b600e5460009081908190815b81811015610fe8576000600e8281548110610f8e57610f8e611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610fde578054610fce9087611afd565b9550610fdb600186611afd565b94505b5050600101610f71565b5061102383610ff56111ca565b60c0516110029190611ad0565b61100c9190611ad0565b600c5461101a908590611afd565b61010051610ece565b935050505090565b60006001600160a01b038216156110425781610f5e565b5090919050565b6001600f5460ff16600381111561106257611062611751565b1415801561108757506002600f5460ff16600381111561108457611084611751565b14155b156110ac576002546040516310004b0160e11b8152600481019190915260240161009b565b60006110b8838361102b565b600c549091506000908190815b8181101561115e576000600c82815481106110e2576110e2611a66565b6000918252602090912060029091020180549091506001600160a01b03808a169116036111555760018101546001600160a01b0380881691160361112a575050505050505050565b600190810180546001600160a01b038881166001600160a01b0319831617909255169450925061115e565b506001016110c5565b508161117f5785604051632fd768d360e11b815260040161009b9190611b4b565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe7284866040516111ba929190611ae3565b60405180910390a2505050505050565b600c54600090815b81811015611231576000600c82815481106111ef576111ef611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506112269085611afd565b9350506001016111d2565b505090565b600e54600090815b81811015611231576000600e828154811061125b5761125b611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff1661129e57805461129b9086611afd565b94505b505060010161123e565b6040516001600160a01b0384811660248301528381166044830152606482018390526108539186918216906323b872dd90608401610e97565b6002600f5460ff1660038111156112fa576112fa611751565b1461131f57600f546040516383696f6f60e01b815261009b9160ff1690600401611a7c565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b038111156113755761137561177c565b6040519080825280602002602001820160405280156113c957816020015b60408051608081018252600091810182815260608201839052815260208101919091528152602001906001900390816113935790505b50905060005b82811015611464576000600c82815481106113ec576113ec611a66565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061145057611450611a66565b6020908102919091010152506001016113cf565b506080516001600160a01b031663095ea7b360a05160c0516040518363ffffffff1660e01b8152600401611499929190611b10565b6020604051808303816000875af11580156114b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114dc9190611b5f565b5060a0516001600160a01b031663bc7efecc600060066002856040518563ffffffff1660e01b81526004016115149493929190611bd9565b600060405180830381600087803b15801561152e57600080fd5b505af1158015610e69573d6000803e3d6000fd5b60006115576001600160a01b0384168361159c565b9050805160001415801561157c57508080602001905181019061157a9190611b5f565b155b15610ec95782604051635274afe760e01b815260040161009b9190611b4b565b6060610f5e838360006115b0565b92915050565b6060814710156115d5573060405163cd78605960e01b815260040161009b9190611b4b565b600080856001600160a01b031684866040516115f19190611c50565b60006040518083038185875af1925050503d806000811461162e576040519150601f19603f3d011682016040523d82523d6000602084013e611633565b606091505b50909250905061164486838361164e565b9695505050505050565b6060826116635761165e826116a1565b610f5e565b815115801561167a57506001600160a01b0384163b155b1561169a5783604051639996b31560e01b815260040161009b9190611b4b565b5080610f5e565b8051156116b15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b50805460008255600202906000526020600020908101906116ca919061170c565b50805460008255906000526020600020908101906116ca919061173c565b5b808211156117385780546001600160a01b03199081168255600182018054909116905560020161170d565b5090565b5b80821115611738576000815560010161173d565b634e487b7160e01b600052602160045260246000fd5b6001600160a01b03811681146116ca57600080fd5b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b03811182821017156117b4576117b461177c565b60405290565b604080519081016001600160401b03811182821017156117b4576117b461177c565b604051601f8201601f191681016001600160401b03811182821017156118045761180461177c565b604052919050565b60006080828403121561181e57600080fd5b611826611792565b8251815260208084015190820152604080840151908201526060928301519281019290925250919050565b60006080828403121561186357600080fd5b61186b611792565b825181526020808401519082015260408084015190820152606083015190915061ffff8116811461189b57600080fd5b606082015292915050565b600082601f8301126118b757600080fd5b81516001600160401b038111156118d0576118d061177c565b6118df60208260051b016117dc565b8082825260208201915060208360061b86010192508583111561190157600080fd5b602085015b8381101561194d576040818803121561191e57600080fd5b6119266117ba565b815161193181611767565b8152602082810151818301529084529290920191604001611906565b5095945050505050565b8051801515811461196757600080fd5b919050565b60008060008060008060008789036101c081121561198957600080fd5b885161199481611767565b60208a015190985096506040603f19820112156119b057600080fd5b506119b96117ba565b604089015181526060890151602082015294506119d98960808a0161180c565b93506119e9896101008a01611851565b6101808901519093506001600160401b03811115611a0657600080fd5b611a128a828b016118a6565b925050611a226101a08901611957565b905092959891949750929550565b600060208284031215611a4257600080fd5b5051919050565b600060208284031215611a5b57600080fd5b8151610f5e81611767565b634e487b7160e01b600052603260045260246000fd5b6020810160048310611a9e57634e487b7160e01b600052602160045260246000fd5b91905290565b9283526020830191909152604082015260600190565b634e487b7160e01b600052601160045260246000fd5b818103818111156115aa576115aa611aba565b6001600160a01b0392831681529116602082015260400190565b808201808211156115aa576115aa611aba565b6001600160a01b03929092168252602082015260400190565b600082611b4657634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b0391909116815260200190565b600060208284031215611b7157600080fd5b610f5e82611957565b600081518084526020840193506020830160005b82811015611bcf578151805180516001600160a01b039081168952602091820151168189015290810151604088015260609096019590910190600101611b8e565b5093949350505050565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000611644610160830184611b7a565b6000825160005b81811015611c715760208186018101518583015201611c57565b506000920191825250919050565b60805160a05160c05160e051610100516135f2611e4b6000396000818161052c01528181610f36015281816115b4015281816115dd0152818161179701528181611e3001528181611e56015281816123b501526123e90152600081816104660152818161064b01528181610673015281816106d601528181610a2801528181610a5001528181610afe01528181610b2601528181610bcb01528181610bf301528181610c5b01528181610c9301528181610cbb01528181610d0d01528181610d350152818161104b01528181611073015281816111b5015281816112140152818161123c01528181611469015281816114910152818161162e015281816116a00152818161205901528181612093015281816120f50152818161244c015281816129480152612a7d01526000818161043f01528181610ef80152818161159201528181611f9601528181611fbc01528181612342015281816123750152818161241701526127b601526000818161034a01528181611dae01528181611e7701528181611f1401528181611fdd015281816127940152818161283901526129170152600081816104a001528181611ab201528181611cef015281816124f2015261276701526135f26000f3fe608060405234801561001057600080fd5b50600436106101e25760003560e01c8062e9a407146101e75780630aaffd2a146101fc5780630d616d201461020f5780630d9639ba146102175780630dcf4b8f146102375780630ebb172a1461024d57806312fa329b1461026f578063200d2ed2146102905780632816ee73146102aa5780632c6cda93146102bd5780632c7baaf3146102d0578063313f336b1461030f5780633362379414610345578063356c299d1461037957806342e94c90146103b75780634564168b146103d75780634a387cdf146103f35780634bb278f3146103fb5780634e7320ce14610403578063565a9d481461041857806356d399e81461043a578063570ca7351461046157806358b810d31461048857806360e52ecb1461049b5780636786452c146104c25780636cf72aa4146104d55780637c0b4bfb146104e857806386fa5063146105275780638c7e7a311461054e5780638dd0855714610561578063937e09b11461058157806396a608c0146105895780639ca002a614610591578063a4b6aa83146105a4578063bc063e1a146105ac578063ccec3716146105c8578063d26146a5146105db578063d826f88f146105ee578063dda0a81a146105f6578063e020e4c41461060d578063e50d50d814610620575b600080fd5b6101fa6101f5366004612e8f565b610640565b005b6101fa61020a366004612ecb565b6106c2565b6101fa6106cc565b61021f6107b7565b60405161022e93929190612f5d565b60405180910390f35b61023f61096d565b60405190815260200161022e565b6102576201518081565b6040516001600160401b03909116815260200161022e565b61028261027d366004612fca565b6109d9565b60405161022e929190612fe3565b600f5461029d9060ff1681565b60405161022e9190613013565b6101fa6102b836600461303b565b610a12565b6101fa6102cb366004613079565b610a1d565b6102d8610a93565b60405161022e919081518152602080830151908201526040808301519082015260609182015161ffff169181019190915260800190565b6006546007546008546009546103259392919084565b60408051948552602085019390935291830152606082015260800161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b60405161022e9190613094565b6002546003546004546005546103939392919061ffff1684565b6040805194855260208501939093529183015261ffff16606082015260800161022e565b61023f6103c5366004612ecb565b600a6020526000908152604090205481565b6000546001546103e5919082565b60405161022e9291906130a8565b600c5461023f565b6101fa610af3565b61040b610b68565b60405161022e91906130dc565b600f5461042a90610100900460ff1681565b604051901515815260200161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa610496366004613103565b610bc0565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa6104d03660046131f1565b610c88565b6101fa6104e3366004613242565b610d02565b6105126104f6366004612ecb565b600d602052600090815260409020805460019091015460ff1682565b6040805192835290151560208301520161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61023f61055c36600461325f565b610d78565b61057461056f366004612fca565b610e09565b60405161022e91906132a6565b61023f610e62565b61023f610f62565b61023f61059f366004612fca565b610fb9565b61023f610fce565b6105b561271081565b60405161ffff909116815260200161022e565b6101fa6105d6366004612ecb565b611040565b61036c6105e9366004612fca565b6111df565b6101fa611209565b6105fe61127e565b60405161022e939291906132b4565b6101fa61061b3660046132ed565b61145e565b61023f61062e366004612ecb565b600b6020526000908152604090205481565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106b657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b60405180910390fd5b6106bf816114e3565b50565b6106bf33826118a8565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016330361070657610704611a29565b565b336000908152600b602052604081205461072090426133e4565b90506201518081101561076a57336000908152600b602052604090819020549051630429b06960e31b815260048101919091524260248201526201518060448201526064016106ad565b600061077533611b4c565b905080156107b35760405181815233907f249a82fbe5056a1940b4e996665ba2a82c340ae9fa1e069fd1ababf5508f396e9060200160405180910390a25b5050565b600e5460609081908190806001600160401b038111156107d9576107d9612d34565b604051908082528060200260200182016040528015610802578160200160208202803683370190505b509350806001600160401b0381111561081d5761081d612d34565b604051908082528060200260200182016040528015610846578160200160208202803683370190505b509250806001600160401b0381111561086157610861612d34565b60405190808252806020026020018201604052801561088a578160200160208202803683370190505b50915060005b81811015610966576000600e82815481106108ad576108ad6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912087519192509082908890859081106108eb576108eb6133f7565b60200260200101906001600160a01b031690816001600160a01b0316815250508060000154868481518110610922576109226133f7565b60209081029190910101526001810154855160ff9091169086908590811061094c5761094c6133f7565b911515602092830291909101909101525050600101610890565b5050909192565b600c54600090815b818110156109d4576000600c8281548110610992576109926133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506109c9908561340d565b935050600101610975565b505090565b600c81815481106109e957600080fd5b6000918252602090912060029091020180546001909101546001600160a01b0391821692501682565b6107b3338284611d49565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a8a57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf8161255d565b610ac26040518060800160405280600081526020016000815260200160008152602001600061ffff1681525090565b5060408051608081018252600254815260035460208201526004549181019190915260055461ffff16606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b6057337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6107046125e7565b610b936040518060800160405280600081526020016000815260200160008152602001600081525090565b50604080516080810182526006548152600754602082015260085491810191909152600954606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610c2d57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c35611a29565b610c3e8561255d565b610c47846114e3565b610c50836128a5565b8015610c8157610c817f00000000000000000000000000000000000000000000000000000000000000008383611d49565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cf557337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c8185858585856128bf565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610d6f57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf816128a5565b6000828211610d9e5782826040516376675ed960e11b81526004016106ad9291906130a8565b82600003610dcf576004610db36001866133e4565b610dbd9190613420565b610dc890600161340d565b9050610e02565b6000610ddb84846133e4565b905080610de96001876133e4565b610df39190613420565b610dfe90600161340d565b9150505b9392505050565b610e11612c79565b600c8281548110610e2457610e246133f7565b60009182526020918290206040805180820190915260029092020180546001600160a01b039081168352600190910154169181019190915292915050565b600e5460009081908190815b81811015610ee5576000600e8281548110610e8b57610e8b6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610edb578054610ecb908761340d565b9550610ed860018661340d565b94505b5050600101610e6e565b50610f5a83610ef261096d565b610f1c907f00000000000000000000000000000000000000000000000000000000000000006133e4565b610f2691906133e4565b600c54610f3490859061340d565b7f0000000000000000000000000000000000000000000000000000000000000000610d78565b935050505090565b600c54600090610f725750600090565b600a6000600c600081548110610f8a57610f8a6133f7565b600091825260208083206002909202909101546001600160a01b03168352820192909252604001902054905090565b6000610fc88260006001610d78565b92915050565b600e54600090815b818110156109d4576000600e8281548110610ff357610ff36133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16611036578054611033908661340d565b94505b5050600101610fd6565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110ad57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6003600f5460ff1660038111156110c6576110c6612ffd565b141580156110ea57506000600f5460ff1660038111156110e8576110e8612ffd565b145b1561110f57600f54604051632f3e69dd60e01b81526106ad9160ff1690600401613013565b6040516370a0823160e01b815281906000906001600160a01b038316906370a0823190611140903090600401613094565b602060405180830381865afa15801561115d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111819190613442565b9050600081116111a6578260405163e932c57360e01b81526004016106ad9190613094565b6111da6001600160a01b0383167f0000000000000000000000000000000000000000000000000000000000000000836129da565b505050565b600e81815481106111ef57600080fd5b6000918252602090912001546001600160a01b0316905081565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461127657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610704611a29565b600c5460609081908190806001600160401b038111156112a0576112a0612d34565b6040519080825280602002602001820160405280156112c9578160200160208202803683370190505b509350806001600160401b038111156112e4576112e4612d34565b60405190808252806020026020018201604052801561130d578160200160208202803683370190505b509250806001600160401b0381111561132857611328612d34565b604051908082528060200260200182016040528015611351578160200160208202803683370190505b50915060005b81811015610966576000600c8281548110611374576113746133f7565b60009182526020909120600290910201805487519192506001600160a01b0316908790849081106113a7576113a76133f7565b6001600160a01b039283166020918202929092010152600182015486519116908690849081106113d9576113d96133f7565b60200260200101906001600160a01b031690816001600160a01b031681525050600a600087848151811061140f5761140f6133f7565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020016000205484838151811061144a5761144a6133f7565b602090810291909101015250600101611357565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114cb57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6114da87878787878787612a32565b50505050505050565b6000600f5460ff1660038111156114fc576114fc612ffd565b1461152157600f546040516312ba63f960e11b81526106ad9160ff1690600401613013565b600e5460005b8181101561158057600d6000600e8381548110611546576115466133f7565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff1916905501611527565b5061158d600e6000612c90565b5080517f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000010156116175781517f0000000000000000000000000000000000000000000000000000000000000000604051630962235760e31b81526004016106ad9291906130a8565b815160005b818110156118a257806000036116cd577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316848281518110611668576116686133f7565b6020026020010151600001516001600160a01b0316146116cd5760405163ef77b46760e01b8152600481018290526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660248201526044016106ad565b60006001600160a01b03168482815181106116ea576116ea6133f7565b6020026020010151600001516001600160a01b0316036117205760405163621caef560e11b8152600481018290526024016106ad565b6000600d6000868481518110611738576117386133f7565b6020026020010151600001516001600160a01b03166001600160a01b031681526020019081526020016000209050806000015460001461178e576040516308308c2360e21b8152600481018390526024016106ad565b60006117bb85847f0000000000000000000000000000000000000000000000000000000000000000610d78565b905060008684815181106117d1576117d16133f7565b6020026020010151602001519050818110156118065783818360405163c4aa8aa360e01b81526004016106ad9392919061345b565b8086101561182d57838187604051636d3ae3a760e01b81526004016106ad9392919061345b565b61183781876133e4565b9550600e87858151811061184d5761184d6133f7565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161161c565b50505050565b6001600f5460ff1660038111156118c1576118c1612ffd565b141580156118e657506002600f5460ff1660038111156118e3576118e3612ffd565b14155b1561190b576002546040516310004b0160e11b815260048101919091526024016106ad565b60006119178383612aa3565b600c549091506000908190815b818110156119bd576000600c8281548110611941576119416133f7565b6000918252602090912060029091020180549091506001600160a01b03808a169116036119b45760018101546001600160a01b03808816911603611989575050505050505050565b600190810180546001600160a01b038881166001600160a01b031983161790925516945092506119bd565b50600101611924565b50816119de5785604051632fd768d360e11b81526004016106ad9190613094565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe728486604051611a19929190612fe3565b60405180910390a2505050505050565b600c5460005b81811015611aec576000600c8281548110611a4c57611a4c6133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff166003811115611a9257611a92612ffd565b14158015611aa05750600081115b15611ad957611ad96001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001683836129da565b611ae282612ac1565b5050600101611a2f565b50611af9600c6000612cae565b600f805460ff191690556040805160008082526020820190925281611b40565b6040805180820190915260008082526020820152815260200190600190039081611b195790505b5090506107b3816114e3565b6001600160a01b0381166000908152600a602052604081205490819003611b7257919050565b611b7b82612ac1565b600c5460005b81811015611c9657600c8181548110611b9c57611b9c6133f7565b60009182526020909120600290910201546001600160a01b0390811690851603611c8e57600c611bcd6001846133e4565b81548110611bdd57611bdd6133f7565b9060005260206000209060020201600c8281548110611bfe57611bfe6133f7565b60009182526020909120825460029092020180546001600160a01b039283166001600160a01b0319918216178255600193840154939091018054939092169216919091179055600c805480611c5557611c55613471565b60008281526020902060026000199092019182020180546001600160a01b03199081168255600191909101805490911690559055611c96565b600101611b81565b506001600160a01b0383166000908152600d6020526040902060018101805460ff191690556003600f5460ff166003811115611cd457611cd4612ffd565b03611ce25760009250611d42565b611d166001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001685856129da565b6002600f5460ff166003811115611d2f57611d2f612ffd565b03611d4257600f805460ff191660011790555b5050919050565b6000600f5460ff166003811115611d6257611d62612ffd565b14158015611d8757506001600f5460ff166003811115611d8457611d84612ffd565b14155b15611dac57600f54604051630295f95f60e51b81526106ad9160ff1690600401613013565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e2e9190613442565b7f000000000000000000000000000000000000000000000000000000000000000014611f12577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ef79190613442565b604051631248081b60e31b81526004016106ad9291906130a8565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f949190613442565b7f000000000000000000000000000000000000000000000000000000000000000014612039577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b6000600f5460ff16600381111561205257612052612ffd565b03612145577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316146120cd57827f00000000000000000000000000000000000000000000000000000000000000006040516316a4ebc160e21b81526004016106ad929190612fe3565b600f805460ff1916600117905560025460055460405161ffff90911681526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d602052604090208054158015906121725750600181015460ff16155b156121b257805482101561219d57805460405163454cd8e160e01b81526106ad9184916004016130a8565b6001818101805460ff19169091179055612207565b6001600160a01b0384166000908152600a60205260409020541580156121de57506121db610e62565b82105b1561220757816121ec610e62565b6040516310e45ff160e21b81526004016106ad9291906130a8565b6001600160a01b0384166000908152600a602052604081205490036122d85760006122328585612aa3565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c89091018054919093169116179055506122e2565b6122e284846118a8565b6001600160a01b0384166000908152600a60205260408120805484929061230a90849061340d565b90915550506001600160a01b0384166000908152600b6020526040812042905561233261096d565b9050600061233e610fce565b90507f000000000000000000000000000000000000000000000000000000000000000061236b828461340d565b11156123b05781817f000000000000000000000000000000000000000000000000000000000000000060405163513b428b60e11b81526004016106ad9392919061345b565b600c547f00000000000000000000000000000000000000000000000000000000000000001015612415576040516315b5c3d560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201526024016106ad565b7f000000000000000000000000000000000000000000000000000000000000000082036124a2576002546040516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa2856040516124dd91815260200190565b60405180910390a261251a6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016873087612ae8565b6002600f5460ff16600381111561253357612533612ffd565b1480156125485750600f54610100900460ff16155b15612555576125556125e7565b505050505050565b6000600f5460ff16600381111561257657612576612ffd565b1461259b57600f5460405163ad88fc8f60e01b81526106ad9160ff1690600401613013565b61271061ffff821611156125cf57604051624ae8fd60e41b815261ffff8216600482015261271060248201526044016106ad565b6005805461ffff191661ffff92909216919091179055565b6002600f5460ff16600381111561260057612600612ffd565b1461262557600f546040516383696f6f60e01b81526106ad9160ff1690600401613013565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b0381111561267b5761267b612d34565b6040519080825280602002602001820160405280156126b457816020015b6126a1612ccf565b8152602001906001900390816126995790505b50905060005b8281101561274f576000600c82815481106126d7576126d76133f7565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061273b5761273b6133f7565b6020908102919091010152506001016126ba565b5060405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906127de907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090600401613487565b6020604051808303816000875af11580156127fd573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061282191906134a0565b50604051632f1fbfb360e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bc7efecc90612877906000906006906002908790600401613504565b600060405180830381600087803b15801561289157600080fd5b505af1158015612555573d6000803e3d6000fd5b600f80549115156101000261ff0019909216919091179055565b6000600f5460ff1660038111156128d8576128d8612ffd565b146128fd57600f54604051639a0293fd60e01b81526106ad9160ff1690600401613013565b60405160016226579360e01b031981526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063ffd9a86d9061297290889088907f000000000000000000000000000000000000000000000000000000000000000090899060040161357b565b600060405180830381600087803b15801561298c57600080fd5b505af11580156129a0573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6111da83846001600160a01b031663a9059cbb8585604051602401612a00929190613487565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050612b21565b612a3a611a29565b612a5387878760000151886020015189604001516128bf565b612a60856060015161255d565b612a69846114e3565b612a72836128a5565b80156114da576114da7f00000000000000000000000000000000000000000000000000000000000000008383611d49565b60006001600160a01b03821615612aba5781610e02565b5090919050565b6001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b6040516001600160a01b0384811660248301528381166044830152606482018390526118a29186918216906323b872dd90608401612a00565b6000612b366001600160a01b03841683612b7b565b90508051600014158015612b5b575080806020019051810190612b5991906134a0565b155b156111da5782604051635274afe760e01b81526004016106ad9190613094565b6060610e028383600084600080856001600160a01b03168486604051612ba191906135b6565b60006040518083038185875af1925050503d8060008114612bde576040519150601f19603f3d011682016040523d82523d6000602084013e612be3565b606091505b5091509150612bf3868383612bfd565b9695505050505050565b606082612c1257612c0d82612c50565b610e02565b8151158015612c2957506001600160a01b0384163b155b15612c495783604051639996b31560e01b81526004016106ad9190613094565b5080610e02565b805115612c605780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b604080518082019091526000808252602082015290565b50805460008255906000526020600020908101906106bf9190612cef565b50805460008255600202906000526020600020908101906106bf9190612d08565b6040518060400160405280612ce2612c79565b8152602001600081525090565b5b80821115612d045760008155600101612cf0565b5090565b5b80821115612d045780546001600160a01b031990811682556001820180549091169055600201612d09565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715612d6c57612d6c612d34565b60405290565b604051608081016001600160401b0381118282101715612d6c57612d6c612d34565b604051601f8201601f191681016001600160401b0381118282101715612dbc57612dbc612d34565b604052919050565b80356001600160a01b0381168114612ddb57600080fd5b919050565b600082601f830112612df157600080fd5b81356001600160401b03811115612e0a57612e0a612d34565b612e1960208260051b01612d94565b8082825260208201915060208360061b860101925085831115612e3b57600080fd5b602085015b83811015612e855760408188031215612e5857600080fd5b612e60612d4a565b612e6982612dc4565b8152602082810135818301529084529290920191604001612e40565b5095945050505050565b600060208284031215612ea157600080fd5b81356001600160401b03811115612eb757600080fd5b612ec384828501612de0565b949350505050565b600060208284031215612edd57600080fd5b610e0282612dc4565b600081518084526020840193506020830160005b82811015612f215781516001600160a01b0316865260209586019590910190600101612efa565b5093949350505050565b600081518084526020840193506020830160005b82811015612f21578151865260209586019590910190600101612f3f565b606081526000612f706060830186612ee6565b8281036020840152612f828186612f2b565b83810360408501528451808252602080870193509091019060005b81811015612fbd5783511515835260209384019390920191600101612f9d565b5090979650505050505050565b600060208284031215612fdc57600080fd5b5035919050565b6001600160a01b0392831681529116602082015260400190565b634e487b7160e01b600052602160045260246000fd5b602081016004831061303557634e487b7160e01b600052602160045260246000fd5b91905290565b6000806040838503121561304e57600080fd5b8235915061305e60208401612dc4565b90509250929050565b803561ffff81168114612ddb57600080fd5b60006020828403121561308b57600080fd5b610e0282613067565b6001600160a01b0391909116815260200190565b918252602082015260400190565b805182526020810151602083015260408101516040830152606081015160608301525050565b60808101610fc882846130b6565b80151581146106bf57600080fd5b8035612ddb816130ea565b600080600080600060a0868803121561311b57600080fd5b61312486613067565b945060208601356001600160401b0381111561313f57600080fd5b61314b88828901612de0565b945050604086013561315c816130ea565b925061316a60608701612dc4565b949793965091946080013592915050565b60006040828403121561318d57600080fd5b613195612d4a565b823581526020928301359281019290925250919050565b6000608082840312156131be57600080fd5b6131c6612d72565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b6000806000806000610120868803121561320a57600080fd5b613214878761317b565b945061322387604088016131ac565b949794965050505060c08301359260e081013592610100909101359150565b60006020828403121561325457600080fd5b8135610e02816130ea565b60008060006060848603121561327457600080fd5b505081359360208301359350604090920135919050565b80516001600160a01b03908116835260209182015116910152565b60408101610fc8828461328b565b6060815260006132c76060830186612ee6565b82810360208401526132d98186612ee6565b90508281036040840152612bf38185612f2b565b60008060008060008060008789036101c081121561330a57600080fd5b6133148a8a61317b565b97506133238a60408b016131ac565b9650608060bf198201121561333757600080fd5b50613340612d72565b60c0890135815260e0890135602082015261010089013560408201526133696101208a01613067565b606082015294506101408801356001600160401b0381111561338a57600080fd5b6133968a828b01612de0565b9450506133a661016089016130f8565b92506133b56101808901612dc4565b9699959850939692959194919350506101a09091013590565b634e487b7160e01b600052601160045260246000fd5b81810381811115610fc857610fc86133ce565b634e487b7160e01b600052603260045260246000fd5b80820180821115610fc857610fc86133ce565b60008261343d57634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561345457600080fd5b5051919050565b9283526020830191909152604082015260600190565b634e487b7160e01b600052603160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000602082840312156134b257600080fd5b8151610e02816130ea565b600081518084526020840193506020830160005b82811015612f215781516134e687825161328b565b602090810151604088015260609096019591909101906001016134d1565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000612bf36101608301846134bd565b8451815260208086015190820152610100810161359b60408301866130b6565b6001600160a01b039390931660c082015260e0015292915050565b6000825160005b818110156135d757602081860181015185830152016135bd565b50600092019182525091905056fea164736f6c634300081a000a", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101e25760003560e01c8062e9a407146101e75780630aaffd2a146101fc5780630d616d201461020f5780630d9639ba146102175780630dcf4b8f146102375780630ebb172a1461024d57806312fa329b1461026f578063200d2ed2146102905780632816ee73146102aa5780632c6cda93146102bd5780632c7baaf3146102d0578063313f336b1461030f5780633362379414610345578063356c299d1461037957806342e94c90146103b75780634564168b146103d75780634a387cdf146103f35780634bb278f3146103fb5780634e7320ce14610403578063565a9d481461041857806356d399e81461043a578063570ca7351461046157806358b810d31461048857806360e52ecb1461049b5780636786452c146104c25780636cf72aa4146104d55780637c0b4bfb146104e857806386fa5063146105275780638c7e7a311461054e5780638dd0855714610561578063937e09b11461058157806396a608c0146105895780639ca002a614610591578063a4b6aa83146105a4578063bc063e1a146105ac578063ccec3716146105c8578063d26146a5146105db578063d826f88f146105ee578063dda0a81a146105f6578063e020e4c41461060d578063e50d50d814610620575b600080fd5b6101fa6101f5366004612e8f565b610640565b005b6101fa61020a366004612ecb565b6106c2565b6101fa6106cc565b61021f6107b7565b60405161022e93929190612f5d565b60405180910390f35b61023f61096d565b60405190815260200161022e565b6102576201518081565b6040516001600160401b03909116815260200161022e565b61028261027d366004612fca565b6109d9565b60405161022e929190612fe3565b600f5461029d9060ff1681565b60405161022e9190613013565b6101fa6102b836600461303b565b610a12565b6101fa6102cb366004613079565b610a1d565b6102d8610a93565b60405161022e919081518152602080830151908201526040808301519082015260609182015161ffff169181019190915260800190565b6006546007546008546009546103259392919084565b60408051948552602085019390935291830152606082015260800161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b60405161022e9190613094565b6002546003546004546005546103939392919061ffff1684565b6040805194855260208501939093529183015261ffff16606082015260800161022e565b61023f6103c5366004612ecb565b600a6020526000908152604090205481565b6000546001546103e5919082565b60405161022e9291906130a8565b600c5461023f565b6101fa610af3565b61040b610b68565b60405161022e91906130dc565b600f5461042a90610100900460ff1681565b604051901515815260200161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa610496366004613103565b610bc0565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa6104d03660046131f1565b610c88565b6101fa6104e3366004613242565b610d02565b6105126104f6366004612ecb565b600d602052600090815260409020805460019091015460ff1682565b6040805192835290151560208301520161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61023f61055c36600461325f565b610d78565b61057461056f366004612fca565b610e09565b60405161022e91906132a6565b61023f610e62565b61023f610f62565b61023f61059f366004612fca565b610fb9565b61023f610fce565b6105b561271081565b60405161ffff909116815260200161022e565b6101fa6105d6366004612ecb565b611040565b61036c6105e9366004612fca565b6111df565b6101fa611209565b6105fe61127e565b60405161022e939291906132b4565b6101fa61061b3660046132ed565b61145e565b61023f61062e366004612ecb565b600b6020526000908152604090205481565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106b657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b60405180910390fd5b6106bf816114e3565b50565b6106bf33826118a8565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016330361070657610704611a29565b565b336000908152600b602052604081205461072090426133e4565b90506201518081101561076a57336000908152600b602052604090819020549051630429b06960e31b815260048101919091524260248201526201518060448201526064016106ad565b600061077533611b4c565b905080156107b35760405181815233907f249a82fbe5056a1940b4e996665ba2a82c340ae9fa1e069fd1ababf5508f396e9060200160405180910390a25b5050565b600e5460609081908190806001600160401b038111156107d9576107d9612d34565b604051908082528060200260200182016040528015610802578160200160208202803683370190505b509350806001600160401b0381111561081d5761081d612d34565b604051908082528060200260200182016040528015610846578160200160208202803683370190505b509250806001600160401b0381111561086157610861612d34565b60405190808252806020026020018201604052801561088a578160200160208202803683370190505b50915060005b81811015610966576000600e82815481106108ad576108ad6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912087519192509082908890859081106108eb576108eb6133f7565b60200260200101906001600160a01b031690816001600160a01b0316815250508060000154868481518110610922576109226133f7565b60209081029190910101526001810154855160ff9091169086908590811061094c5761094c6133f7565b911515602092830291909101909101525050600101610890565b5050909192565b600c54600090815b818110156109d4576000600c8281548110610992576109926133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506109c9908561340d565b935050600101610975565b505090565b600c81815481106109e957600080fd5b6000918252602090912060029091020180546001909101546001600160a01b0391821692501682565b6107b3338284611d49565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a8a57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf8161255d565b610ac26040518060800160405280600081526020016000815260200160008152602001600061ffff1681525090565b5060408051608081018252600254815260035460208201526004549181019190915260055461ffff16606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b6057337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6107046125e7565b610b936040518060800160405280600081526020016000815260200160008152602001600081525090565b50604080516080810182526006548152600754602082015260085491810191909152600954606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610c2d57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c35611a29565b610c3e8561255d565b610c47846114e3565b610c50836128a5565b8015610c8157610c817f00000000000000000000000000000000000000000000000000000000000000008383611d49565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cf557337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c8185858585856128bf565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610d6f57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf816128a5565b6000828211610d9e5782826040516376675ed960e11b81526004016106ad9291906130a8565b82600003610dcf576004610db36001866133e4565b610dbd9190613420565b610dc890600161340d565b9050610e02565b6000610ddb84846133e4565b905080610de96001876133e4565b610df39190613420565b610dfe90600161340d565b9150505b9392505050565b610e11612c79565b600c8281548110610e2457610e246133f7565b60009182526020918290206040805180820190915260029092020180546001600160a01b039081168352600190910154169181019190915292915050565b600e5460009081908190815b81811015610ee5576000600e8281548110610e8b57610e8b6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610edb578054610ecb908761340d565b9550610ed860018661340d565b94505b5050600101610e6e565b50610f5a83610ef261096d565b610f1c907f00000000000000000000000000000000000000000000000000000000000000006133e4565b610f2691906133e4565b600c54610f3490859061340d565b7f0000000000000000000000000000000000000000000000000000000000000000610d78565b935050505090565b600c54600090610f725750600090565b600a6000600c600081548110610f8a57610f8a6133f7565b600091825260208083206002909202909101546001600160a01b03168352820192909252604001902054905090565b6000610fc88260006001610d78565b92915050565b600e54600090815b818110156109d4576000600e8281548110610ff357610ff36133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16611036578054611033908661340d565b94505b5050600101610fd6565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110ad57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6003600f5460ff1660038111156110c6576110c6612ffd565b141580156110ea57506000600f5460ff1660038111156110e8576110e8612ffd565b145b1561110f57600f54604051632f3e69dd60e01b81526106ad9160ff1690600401613013565b6040516370a0823160e01b815281906000906001600160a01b038316906370a0823190611140903090600401613094565b602060405180830381865afa15801561115d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111819190613442565b9050600081116111a6578260405163e932c57360e01b81526004016106ad9190613094565b6111da6001600160a01b0383167f0000000000000000000000000000000000000000000000000000000000000000836129da565b505050565b600e81815481106111ef57600080fd5b6000918252602090912001546001600160a01b0316905081565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461127657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610704611a29565b600c5460609081908190806001600160401b038111156112a0576112a0612d34565b6040519080825280602002602001820160405280156112c9578160200160208202803683370190505b509350806001600160401b038111156112e4576112e4612d34565b60405190808252806020026020018201604052801561130d578160200160208202803683370190505b509250806001600160401b0381111561132857611328612d34565b604051908082528060200260200182016040528015611351578160200160208202803683370190505b50915060005b81811015610966576000600c8281548110611374576113746133f7565b60009182526020909120600290910201805487519192506001600160a01b0316908790849081106113a7576113a76133f7565b6001600160a01b039283166020918202929092010152600182015486519116908690849081106113d9576113d96133f7565b60200260200101906001600160a01b031690816001600160a01b031681525050600a600087848151811061140f5761140f6133f7565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020016000205484838151811061144a5761144a6133f7565b602090810291909101015250600101611357565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114cb57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6114da87878787878787612a32565b50505050505050565b6000600f5460ff1660038111156114fc576114fc612ffd565b1461152157600f546040516312ba63f960e11b81526106ad9160ff1690600401613013565b600e5460005b8181101561158057600d6000600e8381548110611546576115466133f7565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff1916905501611527565b5061158d600e6000612c90565b5080517f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000010156116175781517f0000000000000000000000000000000000000000000000000000000000000000604051630962235760e31b81526004016106ad9291906130a8565b815160005b818110156118a257806000036116cd577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316848281518110611668576116686133f7565b6020026020010151600001516001600160a01b0316146116cd5760405163ef77b46760e01b8152600481018290526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660248201526044016106ad565b60006001600160a01b03168482815181106116ea576116ea6133f7565b6020026020010151600001516001600160a01b0316036117205760405163621caef560e11b8152600481018290526024016106ad565b6000600d6000868481518110611738576117386133f7565b6020026020010151600001516001600160a01b03166001600160a01b031681526020019081526020016000209050806000015460001461178e576040516308308c2360e21b8152600481018390526024016106ad565b60006117bb85847f0000000000000000000000000000000000000000000000000000000000000000610d78565b905060008684815181106117d1576117d16133f7565b6020026020010151602001519050818110156118065783818360405163c4aa8aa360e01b81526004016106ad9392919061345b565b8086101561182d57838187604051636d3ae3a760e01b81526004016106ad9392919061345b565b61183781876133e4565b9550600e87858151811061184d5761184d6133f7565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161161c565b50505050565b6001600f5460ff1660038111156118c1576118c1612ffd565b141580156118e657506002600f5460ff1660038111156118e3576118e3612ffd565b14155b1561190b576002546040516310004b0160e11b815260048101919091526024016106ad565b60006119178383612aa3565b600c549091506000908190815b818110156119bd576000600c8281548110611941576119416133f7565b6000918252602090912060029091020180549091506001600160a01b03808a169116036119b45760018101546001600160a01b03808816911603611989575050505050505050565b600190810180546001600160a01b038881166001600160a01b031983161790925516945092506119bd565b50600101611924565b50816119de5785604051632fd768d360e11b81526004016106ad9190613094565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe728486604051611a19929190612fe3565b60405180910390a2505050505050565b600c5460005b81811015611aec576000600c8281548110611a4c57611a4c6133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff166003811115611a9257611a92612ffd565b14158015611aa05750600081115b15611ad957611ad96001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001683836129da565b611ae282612ac1565b5050600101611a2f565b50611af9600c6000612cae565b600f805460ff191690556040805160008082526020820190925281611b40565b6040805180820190915260008082526020820152815260200190600190039081611b195790505b5090506107b3816114e3565b6001600160a01b0381166000908152600a602052604081205490819003611b7257919050565b611b7b82612ac1565b600c5460005b81811015611c9657600c8181548110611b9c57611b9c6133f7565b60009182526020909120600290910201546001600160a01b0390811690851603611c8e57600c611bcd6001846133e4565b81548110611bdd57611bdd6133f7565b9060005260206000209060020201600c8281548110611bfe57611bfe6133f7565b60009182526020909120825460029092020180546001600160a01b039283166001600160a01b0319918216178255600193840154939091018054939092169216919091179055600c805480611c5557611c55613471565b60008281526020902060026000199092019182020180546001600160a01b03199081168255600191909101805490911690559055611c96565b600101611b81565b506001600160a01b0383166000908152600d6020526040902060018101805460ff191690556003600f5460ff166003811115611cd457611cd4612ffd565b03611ce25760009250611d42565b611d166001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001685856129da565b6002600f5460ff166003811115611d2f57611d2f612ffd565b03611d4257600f805460ff191660011790555b5050919050565b6000600f5460ff166003811115611d6257611d62612ffd565b14158015611d8757506001600f5460ff166003811115611d8457611d84612ffd565b14155b15611dac57600f54604051630295f95f60e51b81526106ad9160ff1690600401613013565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e2e9190613442565b7f000000000000000000000000000000000000000000000000000000000000000014611f12577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ef79190613442565b604051631248081b60e31b81526004016106ad9291906130a8565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f949190613442565b7f000000000000000000000000000000000000000000000000000000000000000014612039577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b6000600f5460ff16600381111561205257612052612ffd565b03612145577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316146120cd57827f00000000000000000000000000000000000000000000000000000000000000006040516316a4ebc160e21b81526004016106ad929190612fe3565b600f805460ff1916600117905560025460055460405161ffff90911681526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d602052604090208054158015906121725750600181015460ff16155b156121b257805482101561219d57805460405163454cd8e160e01b81526106ad9184916004016130a8565b6001818101805460ff19169091179055612207565b6001600160a01b0384166000908152600a60205260409020541580156121de57506121db610e62565b82105b1561220757816121ec610e62565b6040516310e45ff160e21b81526004016106ad9291906130a8565b6001600160a01b0384166000908152600a602052604081205490036122d85760006122328585612aa3565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c89091018054919093169116179055506122e2565b6122e284846118a8565b6001600160a01b0384166000908152600a60205260408120805484929061230a90849061340d565b90915550506001600160a01b0384166000908152600b6020526040812042905561233261096d565b9050600061233e610fce565b90507f000000000000000000000000000000000000000000000000000000000000000061236b828461340d565b11156123b05781817f000000000000000000000000000000000000000000000000000000000000000060405163513b428b60e11b81526004016106ad9392919061345b565b600c547f00000000000000000000000000000000000000000000000000000000000000001015612415576040516315b5c3d560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201526024016106ad565b7f000000000000000000000000000000000000000000000000000000000000000082036124a2576002546040516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa2856040516124dd91815260200190565b60405180910390a261251a6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016873087612ae8565b6002600f5460ff16600381111561253357612533612ffd565b1480156125485750600f54610100900460ff16155b15612555576125556125e7565b505050505050565b6000600f5460ff16600381111561257657612576612ffd565b1461259b57600f5460405163ad88fc8f60e01b81526106ad9160ff1690600401613013565b61271061ffff821611156125cf57604051624ae8fd60e41b815261ffff8216600482015261271060248201526044016106ad565b6005805461ffff191661ffff92909216919091179055565b6002600f5460ff16600381111561260057612600612ffd565b1461262557600f546040516383696f6f60e01b81526106ad9160ff1690600401613013565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b0381111561267b5761267b612d34565b6040519080825280602002602001820160405280156126b457816020015b6126a1612ccf565b8152602001906001900390816126995790505b50905060005b8281101561274f576000600c82815481106126d7576126d76133f7565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061273b5761273b6133f7565b6020908102919091010152506001016126ba565b5060405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906127de907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090600401613487565b6020604051808303816000875af11580156127fd573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061282191906134a0565b50604051632f1fbfb360e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bc7efecc90612877906000906006906002908790600401613504565b600060405180830381600087803b15801561289157600080fd5b505af1158015612555573d6000803e3d6000fd5b600f80549115156101000261ff0019909216919091179055565b6000600f5460ff1660038111156128d8576128d8612ffd565b146128fd57600f54604051639a0293fd60e01b81526106ad9160ff1690600401613013565b60405160016226579360e01b031981526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063ffd9a86d9061297290889088907f000000000000000000000000000000000000000000000000000000000000000090899060040161357b565b600060405180830381600087803b15801561298c57600080fd5b505af11580156129a0573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6111da83846001600160a01b031663a9059cbb8585604051602401612a00929190613487565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050612b21565b612a3a611a29565b612a5387878760000151886020015189604001516128bf565b612a60856060015161255d565b612a69846114e3565b612a72836128a5565b80156114da576114da7f00000000000000000000000000000000000000000000000000000000000000008383611d49565b60006001600160a01b03821615612aba5781610e02565b5090919050565b6001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b6040516001600160a01b0384811660248301528381166044830152606482018390526118a29186918216906323b872dd90608401612a00565b6000612b366001600160a01b03841683612b7b565b90508051600014158015612b5b575080806020019051810190612b5991906134a0565b155b156111da5782604051635274afe760e01b81526004016106ad9190613094565b6060610e028383600084600080856001600160a01b03168486604051612ba191906135b6565b60006040518083038185875af1925050503d8060008114612bde576040519150601f19603f3d011682016040523d82523d6000602084013e612be3565b606091505b5091509150612bf3868383612bfd565b9695505050505050565b606082612c1257612c0d82612c50565b610e02565b8151158015612c2957506001600160a01b0384163b155b15612c495783604051639996b31560e01b81526004016106ad9190613094565b5080610e02565b805115612c605780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b604080518082019091526000808252602082015290565b50805460008255906000526020600020908101906106bf9190612cef565b50805460008255600202906000526020600020908101906106bf9190612d08565b6040518060400160405280612ce2612c79565b8152602001600081525090565b5b80821115612d045760008155600101612cf0565b5090565b5b80821115612d045780546001600160a01b031990811682556001820180549091169055600201612d09565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715612d6c57612d6c612d34565b60405290565b604051608081016001600160401b0381118282101715612d6c57612d6c612d34565b604051601f8201601f191681016001600160401b0381118282101715612dbc57612dbc612d34565b604052919050565b80356001600160a01b0381168114612ddb57600080fd5b919050565b600082601f830112612df157600080fd5b81356001600160401b03811115612e0a57612e0a612d34565b612e1960208260051b01612d94565b8082825260208201915060208360061b860101925085831115612e3b57600080fd5b602085015b83811015612e855760408188031215612e5857600080fd5b612e60612d4a565b612e6982612dc4565b8152602082810135818301529084529290920191604001612e40565b5095945050505050565b600060208284031215612ea157600080fd5b81356001600160401b03811115612eb757600080fd5b612ec384828501612de0565b949350505050565b600060208284031215612edd57600080fd5b610e0282612dc4565b600081518084526020840193506020830160005b82811015612f215781516001600160a01b0316865260209586019590910190600101612efa565b5093949350505050565b600081518084526020840193506020830160005b82811015612f21578151865260209586019590910190600101612f3f565b606081526000612f706060830186612ee6565b8281036020840152612f828186612f2b565b83810360408501528451808252602080870193509091019060005b81811015612fbd5783511515835260209384019390920191600101612f9d565b5090979650505050505050565b600060208284031215612fdc57600080fd5b5035919050565b6001600160a01b0392831681529116602082015260400190565b634e487b7160e01b600052602160045260246000fd5b602081016004831061303557634e487b7160e01b600052602160045260246000fd5b91905290565b6000806040838503121561304e57600080fd5b8235915061305e60208401612dc4565b90509250929050565b803561ffff81168114612ddb57600080fd5b60006020828403121561308b57600080fd5b610e0282613067565b6001600160a01b0391909116815260200190565b918252602082015260400190565b805182526020810151602083015260408101516040830152606081015160608301525050565b60808101610fc882846130b6565b80151581146106bf57600080fd5b8035612ddb816130ea565b600080600080600060a0868803121561311b57600080fd5b61312486613067565b945060208601356001600160401b0381111561313f57600080fd5b61314b88828901612de0565b945050604086013561315c816130ea565b925061316a60608701612dc4565b949793965091946080013592915050565b60006040828403121561318d57600080fd5b613195612d4a565b823581526020928301359281019290925250919050565b6000608082840312156131be57600080fd5b6131c6612d72565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b6000806000806000610120868803121561320a57600080fd5b613214878761317b565b945061322387604088016131ac565b949794965050505060c08301359260e081013592610100909101359150565b60006020828403121561325457600080fd5b8135610e02816130ea565b60008060006060848603121561327457600080fd5b505081359360208301359350604090920135919050565b80516001600160a01b03908116835260209182015116910152565b60408101610fc8828461328b565b6060815260006132c76060830186612ee6565b82810360208401526132d98186612ee6565b90508281036040840152612bf38185612f2b565b60008060008060008060008789036101c081121561330a57600080fd5b6133148a8a61317b565b97506133238a60408b016131ac565b9650608060bf198201121561333757600080fd5b50613340612d72565b60c0890135815260e0890135602082015261010089013560408201526133696101208a01613067565b606082015294506101408801356001600160401b0381111561338a57600080fd5b6133968a828b01612de0565b9450506133a661016089016130f8565b92506133b56101808901612dc4565b9699959850939692959194919350506101a09091013590565b634e487b7160e01b600052601160045260246000fd5b81810381811115610fc857610fc86133ce565b634e487b7160e01b600052603260045260246000fd5b80820180821115610fc857610fc86133ce565b60008261343d57634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561345457600080fd5b5051919050565b9283526020830191909152604082015260600190565b634e487b7160e01b600052603160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000602082840312156134b257600080fd5b8151610e02816130ea565b600081518084526020840193506020830160005b82811015612f215781516134e687825161328b565b602090810151604088015260609096019591909101906001016134d1565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000612bf36101608301846134bd565b8451815260208086015190820152610100810161359b60408301866130b6565b6001600160a01b039390931660c082015260e0015292915050565b6000825160005b818110156135d757602081860181015185830152016135bd565b50600092019182525091905056fea164736f6c634300081a000a", + "bytecode": "0x610120604052600f805460ff1916905534801561001b57600080fd5b5060405161543d38038061543d83398101604081905261003a9161196c565b866001600160a01b0381166100a45760405162461bcd60e51b815260206004820152602560248201527f5368617265643a205a65726f2d61646472657373206973206e6f74207065726d6044820152641a5d1d195960da1b60648201526084015b60405180910390fd5b86806000036100f55760405162461bcd60e51b815260206004820152601b60248201527f5368617265643a2075696e7420696e70757420697320656d7074790000000000604482015260640161009b565b6001600160a01b03891660a081905260408051630ada733d60e31b815290516356d399e8916004808201926020929091908290030181865afa15801561013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101639190611a30565b60c0818152505060a0516001600160a01b0316637c89d2f06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ce9190611a49565b6001600160a01b03166080526101008890523260e05260006101f588888888888680610204565b50505050505050505050611c7f565b61020c61026c565b845160208601516040870151610225928a928a92610398565b6060850151610233906104ad565b61023c84610537565b600f805461ff0019166101008515150217905580156102635760e051610263908383610859565b50505050505050565b600c5460005b81811015610334576000600c828154811061028f5761028f611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff1660038111156102d5576102d5611751565b141580156102e35750600081115b156102ff576080516102ff906001600160a01b03168383610e71565b61032a826001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b5050600101610272565b50610341600c60006116cd565b600f805460ff191690556040805160008082526020820190925281610388565b60408051808201909152600080825260208201528152602001906001900390816103615790505b50905061039481610537565b5050565b6000600f5460ff1660038111156103b1576103b1611751565b146103d657600f54604051639a0293fd60e01b815261009b9160ff1690600401611a7c565b60a05160e0516040805160016226579360e01b03198152885160048201526020808a01516024830152885160448301528801516064820152908701516084820152606087015160a48201526001600160a01b0391821660c482015260e4810186905291169063ffd9a86d9061010401600060405180830381600087803b15801561045f57600080fd5b505af1158015610473573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6000600f5460ff1660038111156104c6576104c6611751565b146104eb57600f5460405163ad88fc8f60e01b815261009b9160ff1690600401611a7c565b61271061ffff8216111561051f57604051624ae8fd60e41b815261ffff82166004820152612710602482015260440161009b565b6005805461ffff191661ffff92909216919091179055565b6000600f5460ff16600381111561055057610550611751565b1461057557600f546040516312ba63f960e11b815261009b9160ff1690600401611a7c565b600e5460005b818110156105d457600d6000600e838154811061059a5761059a611a66565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff191690550161057b565b506105e1600e60006116ee565b5060c051610100518251111561061957815161010051604051630962235760e31b81526004810192909252602482015260440161009b565b815160005b8181101561085357806000036106955760e0516001600160a01b031684828151811061064c5761064c611a66565b6020026020010151600001516001600160a01b0316146106955760e05160405163ef77b46760e01b8152600481018390526001600160a01b03909116602482015260440161009b565b60006001600160a01b03168482815181106106b2576106b2611a66565b6020026020010151600001516001600160a01b0316036106e85760405163621caef560e11b81526004810182905260240161009b565b6000600d600086848151811061070057610700611a66565b6020026020010151600001516001600160a01b03166001600160a01b0316815260200190815260200160002090508060000154600014610756576040516308308c2360e21b81526004810183905260240161009b565b600061076c858461010051610ece60201b60201c565b9050600086848151811061078257610782611a66565b6020026020010151602001519050818110156107b75783818360405163c4aa8aa360e01b815260040161009b93929190611aa4565b808610156107de57838187604051636d3ae3a760e01b815260040161009b93929190611aa4565b6107e88187611ad0565b9550600e8785815181106107fe576107fe611a66565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161061e565b50505050565b6000600f5460ff16600381111561087257610872611751565b1415801561089757506001600f5460ff16600381111561089457610894611751565b14155b156108bc57600f54604051630295f95f60e51b815261009b9160ff1690600401611a7c565b60a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108fc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109209190611a30565b61010051146109b3576101005160a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109919190611a30565b604051631248081b60e31b81526004810192909252602482015260440161009b565b60a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a179190611a30565b60c05114610a625760c05160a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b6000600f5460ff166003811115610a7b57610a7b611751565b03610b145760e0516001600160a01b0316836001600160a01b031614610ab95760e0516040516316a4ebc160e21b815261009b918591600401611ae3565b600f805460ff1916600117905560e05160025460055460405161ffff90911681526001600160a01b03909216917fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d60205260409020805415801590610b415750600181015460ff16155b15610b8a578054821015610b7557805460405163454cd8e160e01b815261009b918491600401918252602082015260400190565b6001818101805460ff19169091179055610be6565b6001600160a01b0384166000908152600a6020526040902054158015610bb65750610bb3610f65565b82105b15610be65781610bc4610f65565b6040516310e45ff160e21b81526004810192909252602482015260440161009b565b6001600160a01b0384166000908152600a60205260408120549003610cb7576000610c11858561102b565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8909101805491909316911617905550610cc1565b610cc18484611049565b6001600160a01b0384166000908152600a602052604081208054849290610ce9908490611afd565b90915550506001600160a01b0384166000908152600b60205260408120429055610d116111ca565b90506000610d1d611236565b60c051909150610d2d8284611afd565b1115610d5457818160c05160405163513b428b60e11b815260040161009b93929190611aa4565b61010051600c541115610d8157610100516040516315b5c3d560e21b815260040161009b91815260200190565b60c0518203610dd35760e0516002546040516001600160a01b03909216917f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa285604051610e0e91815260200190565b60405180910390a2608051610e2e906001600160a01b03168730876112a8565b6002600f5460ff166003811115610e4757610e47611751565b148015610e5c5750600f54610100900460ff16155b15610e6957610e696112e1565b505050505050565b610ec983846001600160a01b031663a9059cbb8585604051602401610e97929190611b10565b60408051808303601f1901815291905260208101805160e09390931b6001600160e01b03938416179052915061154216565b505050565b6000828211610efa576040516376675ed960e11b8152600481018490526024810183905260440161009b565b82600003610f2b576004610f0f600186611ad0565b610f199190611b29565b610f24906001611afd565b9050610f5e565b6000610f378484611ad0565b905080610f45600187611ad0565b610f4f9190611b29565b610f5a906001611afd565b9150505b9392505050565b600e5460009081908190815b81811015610fe8576000600e8281548110610f8e57610f8e611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610fde578054610fce9087611afd565b9550610fdb600186611afd565b94505b5050600101610f71565b5061102383610ff56111ca565b60c0516110029190611ad0565b61100c9190611ad0565b600c5461101a908590611afd565b61010051610ece565b935050505090565b60006001600160a01b038216156110425781610f5e565b5090919050565b6001600f5460ff16600381111561106257611062611751565b1415801561108757506002600f5460ff16600381111561108457611084611751565b14155b156110ac576002546040516310004b0160e11b8152600481019190915260240161009b565b60006110b8838361102b565b600c549091506000908190815b8181101561115e576000600c82815481106110e2576110e2611a66565b6000918252602090912060029091020180549091506001600160a01b03808a169116036111555760018101546001600160a01b0380881691160361112a575050505050505050565b600190810180546001600160a01b038881166001600160a01b0319831617909255169450925061115e565b506001016110c5565b508161117f5785604051632fd768d360e11b815260040161009b9190611b4b565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe7284866040516111ba929190611ae3565b60405180910390a2505050505050565b600c54600090815b81811015611231576000600c82815481106111ef576111ef611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506112269085611afd565b9350506001016111d2565b505090565b600e54600090815b81811015611231576000600e828154811061125b5761125b611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff1661129e57805461129b9086611afd565b94505b505060010161123e565b6040516001600160a01b0384811660248301528381166044830152606482018390526108539186918216906323b872dd90608401610e97565b6002600f5460ff1660038111156112fa576112fa611751565b1461131f57600f546040516383696f6f60e01b815261009b9160ff1690600401611a7c565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b038111156113755761137561177c565b6040519080825280602002602001820160405280156113c957816020015b60408051608081018252600091810182815260608201839052815260208101919091528152602001906001900390816113935790505b50905060005b82811015611464576000600c82815481106113ec576113ec611a66565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061145057611450611a66565b6020908102919091010152506001016113cf565b506080516001600160a01b031663095ea7b360a05160c0516040518363ffffffff1660e01b8152600401611499929190611b10565b6020604051808303816000875af11580156114b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114dc9190611b5f565b5060a0516001600160a01b031663bc7efecc600060066002856040518563ffffffff1660e01b81526004016115149493929190611bd9565b600060405180830381600087803b15801561152e57600080fd5b505af1158015610e69573d6000803e3d6000fd5b60006115576001600160a01b0384168361159c565b9050805160001415801561157c57508080602001905181019061157a9190611b5f565b155b15610ec95782604051635274afe760e01b815260040161009b9190611b4b565b6060610f5e838360006115b0565b92915050565b6060814710156115d5573060405163cd78605960e01b815260040161009b9190611b4b565b600080856001600160a01b031684866040516115f19190611c50565b60006040518083038185875af1925050503d806000811461162e576040519150601f19603f3d011682016040523d82523d6000602084013e611633565b606091505b50909250905061164486838361164e565b9695505050505050565b6060826116635761165e826116a1565b610f5e565b815115801561167a57506001600160a01b0384163b155b1561169a5783604051639996b31560e01b815260040161009b9190611b4b565b5080610f5e565b8051156116b15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b50805460008255600202906000526020600020908101906116ca919061170c565b50805460008255906000526020600020908101906116ca919061173c565b5b808211156117385780546001600160a01b03199081168255600182018054909116905560020161170d565b5090565b5b80821115611738576000815560010161173d565b634e487b7160e01b600052602160045260246000fd5b6001600160a01b03811681146116ca57600080fd5b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b03811182821017156117b4576117b461177c565b60405290565b604080519081016001600160401b03811182821017156117b4576117b461177c565b604051601f8201601f191681016001600160401b03811182821017156118045761180461177c565b604052919050565b60006080828403121561181e57600080fd5b611826611792565b8251815260208084015190820152604080840151908201526060928301519281019290925250919050565b60006080828403121561186357600080fd5b61186b611792565b825181526020808401519082015260408084015190820152606083015190915061ffff8116811461189b57600080fd5b606082015292915050565b600082601f8301126118b757600080fd5b81516001600160401b038111156118d0576118d061177c565b6118df60208260051b016117dc565b8082825260208201915060208360061b86010192508583111561190157600080fd5b602085015b8381101561194d576040818803121561191e57600080fd5b6119266117ba565b815161193181611767565b8152602082810151818301529084529290920191604001611906565b5095945050505050565b8051801515811461196757600080fd5b919050565b60008060008060008060008789036101c081121561198957600080fd5b885161199481611767565b60208a015190985096506040603f19820112156119b057600080fd5b506119b96117ba565b604089015181526060890151602082015294506119d98960808a0161180c565b93506119e9896101008a01611851565b6101808901519093506001600160401b03811115611a0657600080fd5b611a128a828b016118a6565b925050611a226101a08901611957565b905092959891949750929550565b600060208284031215611a4257600080fd5b5051919050565b600060208284031215611a5b57600080fd5b8151610f5e81611767565b634e487b7160e01b600052603260045260246000fd5b6020810160048310611a9e57634e487b7160e01b600052602160045260246000fd5b91905290565b9283526020830191909152604082015260600190565b634e487b7160e01b600052601160045260246000fd5b818103818111156115aa576115aa611aba565b6001600160a01b0392831681529116602082015260400190565b808201808211156115aa576115aa611aba565b6001600160a01b03929092168252602082015260400190565b600082611b4657634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b0391909116815260200190565b600060208284031215611b7157600080fd5b610f5e82611957565b600081518084526020840193506020830160005b82811015611bcf578151805180516001600160a01b039081168952602091820151168189015290810151604088015260609096019590910190600101611b8e565b5093949350505050565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000611644610160830184611b7a565b6000825160005b81811015611c715760208186018101518583015201611c57565b506000920191825250919050565b60805160a05160c05160e051610100516135f2611e4b6000396000818161050501528181610f36015281816115b4015281816115dd0152818161179701528181611e3001528181611e56015281816123b501526123e90152600081816104660152818161064b01528181610673015281816106d601528181610a2801528181610a5001528181610afe01528181610b2601528181610bcb01528181610bf301528181610c5b01528181610c9301528181610cbb01528181610d0d01528181610d350152818161104b01528181611073015281816111b5015281816112140152818161123c01528181611469015281816114910152818161162e015281816116a00152818161205901528181612093015281816120f50152818161244c015281816129480152612a7d01526000818161043f01528181610ef80152818161159201528181611f9601528181611fbc01528181612342015281816123750152818161241701526127b601526000818161034a01528181611dae01528181611e7701528181611f1401528181611fdd015281816127940152818161283901526129170152600081816105a601528181611ab201528181611cef015281816124f2015261276701526135f26000f3fe608060405234801561001057600080fd5b50600436106101e25760003560e01c8062e9a407146101e75780630aaffd2a146101fc5780630d616d201461020f5780630d9639ba146102175780630dcf4b8f146102375780630ebb172a1461024d57806312fa329b1461026f578063200d2ed2146102905780632816ee73146102aa5780632c6cda93146102bd5780632c7baaf3146102d0578063313f336b1461030f5780633362379414610345578063356c299d1461037957806342e94c90146103b75780634564168b146103d75780634a387cdf146103f35780634bb278f3146103fb5780634e7320ce14610403578063565a9d481461041857806356d399e81461043a578063570ca7351461046157806358b810d3146104885780636786452c1461049b5780636cf72aa4146104ae5780637c0b4bfb146104c157806386fa5063146105005780638c7e7a31146105275780638dd085571461053a578063937e09b11461055a57806396a608c0146105625780639ca002a61461056a578063a4b6aa831461057d578063bc063e1a14610585578063c3aca558146105a1578063ccec3716146105c8578063d26146a5146105db578063d826f88f146105ee578063dda0a81a146105f6578063e020e4c41461060d578063e50d50d814610620575b600080fd5b6101fa6101f5366004612e8f565b610640565b005b6101fa61020a366004612ecb565b6106c2565b6101fa6106cc565b61021f6107b7565b60405161022e93929190612f5d565b60405180910390f35b61023f61096d565b60405190815260200161022e565b6102576201518081565b6040516001600160401b03909116815260200161022e565b61028261027d366004612fca565b6109d9565b60405161022e929190612fe3565b600f5461029d9060ff1681565b60405161022e9190613013565b6101fa6102b836600461303b565b610a12565b6101fa6102cb366004613079565b610a1d565b6102d8610a93565b60405161022e919081518152602080830151908201526040808301519082015260609182015161ffff169181019190915260800190565b6006546007546008546009546103259392919084565b60408051948552602085019390935291830152606082015260800161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b60405161022e9190613094565b6002546003546004546005546103939392919061ffff1684565b6040805194855260208501939093529183015261ffff16606082015260800161022e565b61023f6103c5366004612ecb565b600a6020526000908152604090205481565b6000546001546103e5919082565b60405161022e9291906130a8565b600c5461023f565b6101fa610af3565b61040b610b68565b60405161022e91906130dc565b600f5461042a90610100900460ff1681565b604051901515815260200161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa610496366004613103565b610bc0565b6101fa6104a93660046131f1565b610c88565b6101fa6104bc366004613242565b610d02565b6104eb6104cf366004612ecb565b600d602052600090815260409020805460019091015460ff1682565b6040805192835290151560208301520161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61023f61053536600461325f565b610d78565b61054d610548366004612fca565b610e09565b60405161022e91906132a6565b61023f610e62565b61023f610f62565b61023f610578366004612fca565b610fb9565b61023f610fce565b61058e61271081565b60405161ffff909116815260200161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa6105d6366004612ecb565b611040565b61036c6105e9366004612fca565b6111df565b6101fa611209565b6105fe61127e565b60405161022e939291906132b4565b6101fa61061b3660046132ed565b61145e565b61023f61062e366004612ecb565b600b6020526000908152604090205481565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106b657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b60405180910390fd5b6106bf816114e3565b50565b6106bf33826118a8565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016330361070657610704611a29565b565b336000908152600b602052604081205461072090426133e4565b90506201518081101561076a57336000908152600b602052604090819020549051630429b06960e31b815260048101919091524260248201526201518060448201526064016106ad565b600061077533611b4c565b905080156107b35760405181815233907f249a82fbe5056a1940b4e996665ba2a82c340ae9fa1e069fd1ababf5508f396e9060200160405180910390a25b5050565b600e5460609081908190806001600160401b038111156107d9576107d9612d34565b604051908082528060200260200182016040528015610802578160200160208202803683370190505b509350806001600160401b0381111561081d5761081d612d34565b604051908082528060200260200182016040528015610846578160200160208202803683370190505b509250806001600160401b0381111561086157610861612d34565b60405190808252806020026020018201604052801561088a578160200160208202803683370190505b50915060005b81811015610966576000600e82815481106108ad576108ad6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912087519192509082908890859081106108eb576108eb6133f7565b60200260200101906001600160a01b031690816001600160a01b0316815250508060000154868481518110610922576109226133f7565b60209081029190910101526001810154855160ff9091169086908590811061094c5761094c6133f7565b911515602092830291909101909101525050600101610890565b5050909192565b600c54600090815b818110156109d4576000600c8281548110610992576109926133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506109c9908561340d565b935050600101610975565b505090565b600c81815481106109e957600080fd5b6000918252602090912060029091020180546001909101546001600160a01b0391821692501682565b6107b3338284611d49565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a8a57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf8161255d565b610ac26040518060800160405280600081526020016000815260200160008152602001600061ffff1681525090565b5060408051608081018252600254815260035460208201526004549181019190915260055461ffff16606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b6057337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6107046125e7565b610b936040518060800160405280600081526020016000815260200160008152602001600081525090565b50604080516080810182526006548152600754602082015260085491810191909152600954606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610c2d57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c35611a29565b610c3e8561255d565b610c47846114e3565b610c50836128a5565b8015610c8157610c817f00000000000000000000000000000000000000000000000000000000000000008383611d49565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cf557337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c8185858585856128bf565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610d6f57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf816128a5565b6000828211610d9e5782826040516376675ed960e11b81526004016106ad9291906130a8565b82600003610dcf576004610db36001866133e4565b610dbd9190613420565b610dc890600161340d565b9050610e02565b6000610ddb84846133e4565b905080610de96001876133e4565b610df39190613420565b610dfe90600161340d565b9150505b9392505050565b610e11612c79565b600c8281548110610e2457610e246133f7565b60009182526020918290206040805180820190915260029092020180546001600160a01b039081168352600190910154169181019190915292915050565b600e5460009081908190815b81811015610ee5576000600e8281548110610e8b57610e8b6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610edb578054610ecb908761340d565b9550610ed860018661340d565b94505b5050600101610e6e565b50610f5a83610ef261096d565b610f1c907f00000000000000000000000000000000000000000000000000000000000000006133e4565b610f2691906133e4565b600c54610f3490859061340d565b7f0000000000000000000000000000000000000000000000000000000000000000610d78565b935050505090565b600c54600090610f725750600090565b600a6000600c600081548110610f8a57610f8a6133f7565b600091825260208083206002909202909101546001600160a01b03168352820192909252604001902054905090565b6000610fc88260006001610d78565b92915050565b600e54600090815b818110156109d4576000600e8281548110610ff357610ff36133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16611036578054611033908661340d565b94505b5050600101610fd6565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110ad57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6003600f5460ff1660038111156110c6576110c6612ffd565b141580156110ea57506000600f5460ff1660038111156110e8576110e8612ffd565b145b1561110f57600f54604051632f3e69dd60e01b81526106ad9160ff1690600401613013565b6040516370a0823160e01b815281906000906001600160a01b038316906370a0823190611140903090600401613094565b602060405180830381865afa15801561115d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111819190613442565b9050600081116111a6578260405163e932c57360e01b81526004016106ad9190613094565b6111da6001600160a01b0383167f0000000000000000000000000000000000000000000000000000000000000000836129da565b505050565b600e81815481106111ef57600080fd5b6000918252602090912001546001600160a01b0316905081565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461127657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610704611a29565b600c5460609081908190806001600160401b038111156112a0576112a0612d34565b6040519080825280602002602001820160405280156112c9578160200160208202803683370190505b509350806001600160401b038111156112e4576112e4612d34565b60405190808252806020026020018201604052801561130d578160200160208202803683370190505b509250806001600160401b0381111561132857611328612d34565b604051908082528060200260200182016040528015611351578160200160208202803683370190505b50915060005b81811015610966576000600c8281548110611374576113746133f7565b60009182526020909120600290910201805487519192506001600160a01b0316908790849081106113a7576113a76133f7565b6001600160a01b039283166020918202929092010152600182015486519116908690849081106113d9576113d96133f7565b60200260200101906001600160a01b031690816001600160a01b031681525050600a600087848151811061140f5761140f6133f7565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020016000205484838151811061144a5761144a6133f7565b602090810291909101015250600101611357565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114cb57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6114da87878787878787612a32565b50505050505050565b6000600f5460ff1660038111156114fc576114fc612ffd565b1461152157600f546040516312ba63f960e11b81526106ad9160ff1690600401613013565b600e5460005b8181101561158057600d6000600e8381548110611546576115466133f7565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff1916905501611527565b5061158d600e6000612c90565b5080517f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000010156116175781517f0000000000000000000000000000000000000000000000000000000000000000604051630962235760e31b81526004016106ad9291906130a8565b815160005b818110156118a257806000036116cd577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316848281518110611668576116686133f7565b6020026020010151600001516001600160a01b0316146116cd5760405163ef77b46760e01b8152600481018290526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660248201526044016106ad565b60006001600160a01b03168482815181106116ea576116ea6133f7565b6020026020010151600001516001600160a01b0316036117205760405163621caef560e11b8152600481018290526024016106ad565b6000600d6000868481518110611738576117386133f7565b6020026020010151600001516001600160a01b03166001600160a01b031681526020019081526020016000209050806000015460001461178e576040516308308c2360e21b8152600481018390526024016106ad565b60006117bb85847f0000000000000000000000000000000000000000000000000000000000000000610d78565b905060008684815181106117d1576117d16133f7565b6020026020010151602001519050818110156118065783818360405163c4aa8aa360e01b81526004016106ad9392919061345b565b8086101561182d57838187604051636d3ae3a760e01b81526004016106ad9392919061345b565b61183781876133e4565b9550600e87858151811061184d5761184d6133f7565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161161c565b50505050565b6001600f5460ff1660038111156118c1576118c1612ffd565b141580156118e657506002600f5460ff1660038111156118e3576118e3612ffd565b14155b1561190b576002546040516310004b0160e11b815260048101919091526024016106ad565b60006119178383612aa3565b600c549091506000908190815b818110156119bd576000600c8281548110611941576119416133f7565b6000918252602090912060029091020180549091506001600160a01b03808a169116036119b45760018101546001600160a01b03808816911603611989575050505050505050565b600190810180546001600160a01b038881166001600160a01b031983161790925516945092506119bd565b50600101611924565b50816119de5785604051632fd768d360e11b81526004016106ad9190613094565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe728486604051611a19929190612fe3565b60405180910390a2505050505050565b600c5460005b81811015611aec576000600c8281548110611a4c57611a4c6133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff166003811115611a9257611a92612ffd565b14158015611aa05750600081115b15611ad957611ad96001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001683836129da565b611ae282612ac1565b5050600101611a2f565b50611af9600c6000612cae565b600f805460ff191690556040805160008082526020820190925281611b40565b6040805180820190915260008082526020820152815260200190600190039081611b195790505b5090506107b3816114e3565b6001600160a01b0381166000908152600a602052604081205490819003611b7257919050565b611b7b82612ac1565b600c5460005b81811015611c9657600c8181548110611b9c57611b9c6133f7565b60009182526020909120600290910201546001600160a01b0390811690851603611c8e57600c611bcd6001846133e4565b81548110611bdd57611bdd6133f7565b9060005260206000209060020201600c8281548110611bfe57611bfe6133f7565b60009182526020909120825460029092020180546001600160a01b039283166001600160a01b0319918216178255600193840154939091018054939092169216919091179055600c805480611c5557611c55613471565b60008281526020902060026000199092019182020180546001600160a01b03199081168255600191909101805490911690559055611c96565b600101611b81565b506001600160a01b0383166000908152600d6020526040902060018101805460ff191690556003600f5460ff166003811115611cd457611cd4612ffd565b03611ce25760009250611d42565b611d166001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001685856129da565b6002600f5460ff166003811115611d2f57611d2f612ffd565b03611d4257600f805460ff191660011790555b5050919050565b6000600f5460ff166003811115611d6257611d62612ffd565b14158015611d8757506001600f5460ff166003811115611d8457611d84612ffd565b14155b15611dac57600f54604051630295f95f60e51b81526106ad9160ff1690600401613013565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e2e9190613442565b7f000000000000000000000000000000000000000000000000000000000000000014611f12577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ef79190613442565b604051631248081b60e31b81526004016106ad9291906130a8565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f949190613442565b7f000000000000000000000000000000000000000000000000000000000000000014612039577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b6000600f5460ff16600381111561205257612052612ffd565b03612145577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316146120cd57827f00000000000000000000000000000000000000000000000000000000000000006040516316a4ebc160e21b81526004016106ad929190612fe3565b600f805460ff1916600117905560025460055460405161ffff90911681526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d602052604090208054158015906121725750600181015460ff16155b156121b257805482101561219d57805460405163454cd8e160e01b81526106ad9184916004016130a8565b6001818101805460ff19169091179055612207565b6001600160a01b0384166000908152600a60205260409020541580156121de57506121db610e62565b82105b1561220757816121ec610e62565b6040516310e45ff160e21b81526004016106ad9291906130a8565b6001600160a01b0384166000908152600a602052604081205490036122d85760006122328585612aa3565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c89091018054919093169116179055506122e2565b6122e284846118a8565b6001600160a01b0384166000908152600a60205260408120805484929061230a90849061340d565b90915550506001600160a01b0384166000908152600b6020526040812042905561233261096d565b9050600061233e610fce565b90507f000000000000000000000000000000000000000000000000000000000000000061236b828461340d565b11156123b05781817f000000000000000000000000000000000000000000000000000000000000000060405163513b428b60e11b81526004016106ad9392919061345b565b600c547f00000000000000000000000000000000000000000000000000000000000000001015612415576040516315b5c3d560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201526024016106ad565b7f000000000000000000000000000000000000000000000000000000000000000082036124a2576002546040516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa2856040516124dd91815260200190565b60405180910390a261251a6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016873087612ae8565b6002600f5460ff16600381111561253357612533612ffd565b1480156125485750600f54610100900460ff16155b15612555576125556125e7565b505050505050565b6000600f5460ff16600381111561257657612576612ffd565b1461259b57600f5460405163ad88fc8f60e01b81526106ad9160ff1690600401613013565b61271061ffff821611156125cf57604051624ae8fd60e41b815261ffff8216600482015261271060248201526044016106ad565b6005805461ffff191661ffff92909216919091179055565b6002600f5460ff16600381111561260057612600612ffd565b1461262557600f546040516383696f6f60e01b81526106ad9160ff1690600401613013565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b0381111561267b5761267b612d34565b6040519080825280602002602001820160405280156126b457816020015b6126a1612ccf565b8152602001906001900390816126995790505b50905060005b8281101561274f576000600c82815481106126d7576126d76133f7565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061273b5761273b6133f7565b6020908102919091010152506001016126ba565b5060405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906127de907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090600401613487565b6020604051808303816000875af11580156127fd573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061282191906134a0565b50604051632f1fbfb360e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bc7efecc90612877906000906006906002908790600401613504565b600060405180830381600087803b15801561289157600080fd5b505af1158015612555573d6000803e3d6000fd5b600f80549115156101000261ff0019909216919091179055565b6000600f5460ff1660038111156128d8576128d8612ffd565b146128fd57600f54604051639a0293fd60e01b81526106ad9160ff1690600401613013565b60405160016226579360e01b031981526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063ffd9a86d9061297290889088907f000000000000000000000000000000000000000000000000000000000000000090899060040161357b565b600060405180830381600087803b15801561298c57600080fd5b505af11580156129a0573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6111da83846001600160a01b031663a9059cbb8585604051602401612a00929190613487565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050612b21565b612a3a611a29565b612a5387878760000151886020015189604001516128bf565b612a60856060015161255d565b612a69846114e3565b612a72836128a5565b80156114da576114da7f00000000000000000000000000000000000000000000000000000000000000008383611d49565b60006001600160a01b03821615612aba5781610e02565b5090919050565b6001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b6040516001600160a01b0384811660248301528381166044830152606482018390526118a29186918216906323b872dd90608401612a00565b6000612b366001600160a01b03841683612b7b565b90508051600014158015612b5b575080806020019051810190612b5991906134a0565b155b156111da5782604051635274afe760e01b81526004016106ad9190613094565b6060610e028383600084600080856001600160a01b03168486604051612ba191906135b6565b60006040518083038185875af1925050503d8060008114612bde576040519150601f19603f3d011682016040523d82523d6000602084013e612be3565b606091505b5091509150612bf3868383612bfd565b9695505050505050565b606082612c1257612c0d82612c50565b610e02565b8151158015612c2957506001600160a01b0384163b155b15612c495783604051639996b31560e01b81526004016106ad9190613094565b5080610e02565b805115612c605780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b604080518082019091526000808252602082015290565b50805460008255906000526020600020908101906106bf9190612cef565b50805460008255600202906000526020600020908101906106bf9190612d08565b6040518060400160405280612ce2612c79565b8152602001600081525090565b5b80821115612d045760008155600101612cf0565b5090565b5b80821115612d045780546001600160a01b031990811682556001820180549091169055600201612d09565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715612d6c57612d6c612d34565b60405290565b604051608081016001600160401b0381118282101715612d6c57612d6c612d34565b604051601f8201601f191681016001600160401b0381118282101715612dbc57612dbc612d34565b604052919050565b80356001600160a01b0381168114612ddb57600080fd5b919050565b600082601f830112612df157600080fd5b81356001600160401b03811115612e0a57612e0a612d34565b612e1960208260051b01612d94565b8082825260208201915060208360061b860101925085831115612e3b57600080fd5b602085015b83811015612e855760408188031215612e5857600080fd5b612e60612d4a565b612e6982612dc4565b8152602082810135818301529084529290920191604001612e40565b5095945050505050565b600060208284031215612ea157600080fd5b81356001600160401b03811115612eb757600080fd5b612ec384828501612de0565b949350505050565b600060208284031215612edd57600080fd5b610e0282612dc4565b600081518084526020840193506020830160005b82811015612f215781516001600160a01b0316865260209586019590910190600101612efa565b5093949350505050565b600081518084526020840193506020830160005b82811015612f21578151865260209586019590910190600101612f3f565b606081526000612f706060830186612ee6565b8281036020840152612f828186612f2b565b83810360408501528451808252602080870193509091019060005b81811015612fbd5783511515835260209384019390920191600101612f9d565b5090979650505050505050565b600060208284031215612fdc57600080fd5b5035919050565b6001600160a01b0392831681529116602082015260400190565b634e487b7160e01b600052602160045260246000fd5b602081016004831061303557634e487b7160e01b600052602160045260246000fd5b91905290565b6000806040838503121561304e57600080fd5b8235915061305e60208401612dc4565b90509250929050565b803561ffff81168114612ddb57600080fd5b60006020828403121561308b57600080fd5b610e0282613067565b6001600160a01b0391909116815260200190565b918252602082015260400190565b805182526020810151602083015260408101516040830152606081015160608301525050565b60808101610fc882846130b6565b80151581146106bf57600080fd5b8035612ddb816130ea565b600080600080600060a0868803121561311b57600080fd5b61312486613067565b945060208601356001600160401b0381111561313f57600080fd5b61314b88828901612de0565b945050604086013561315c816130ea565b925061316a60608701612dc4565b949793965091946080013592915050565b60006040828403121561318d57600080fd5b613195612d4a565b823581526020928301359281019290925250919050565b6000608082840312156131be57600080fd5b6131c6612d72565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b6000806000806000610120868803121561320a57600080fd5b613214878761317b565b945061322387604088016131ac565b949794965050505060c08301359260e081013592610100909101359150565b60006020828403121561325457600080fd5b8135610e02816130ea565b60008060006060848603121561327457600080fd5b505081359360208301359350604090920135919050565b80516001600160a01b03908116835260209182015116910152565b60408101610fc8828461328b565b6060815260006132c76060830186612ee6565b82810360208401526132d98186612ee6565b90508281036040840152612bf38185612f2b565b60008060008060008060008789036101c081121561330a57600080fd5b6133148a8a61317b565b97506133238a60408b016131ac565b9650608060bf198201121561333757600080fd5b50613340612d72565b60c0890135815260e0890135602082015261010089013560408201526133696101208a01613067565b606082015294506101408801356001600160401b0381111561338a57600080fd5b6133968a828b01612de0565b9450506133a661016089016130f8565b92506133b56101808901612dc4565b9699959850939692959194919350506101a09091013590565b634e487b7160e01b600052601160045260246000fd5b81810381811115610fc857610fc86133ce565b634e487b7160e01b600052603260045260246000fd5b80820180821115610fc857610fc86133ce565b60008261343d57634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561345457600080fd5b5051919050565b9283526020830191909152604082015260600190565b634e487b7160e01b600052603160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000602082840312156134b257600080fd5b8151610e02816130ea565b600081518084526020840193506020830160005b82811015612f215781516134e687825161328b565b602090810151604088015260609096019591909101906001016134d1565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000612bf36101608301846134bd565b8451815260208086015190820152610100810161359b60408301866130b6565b6001600160a01b039390931660c082015260e0015292915050565b6000825160005b818110156135d757602081860181015185830152016135bd565b50600092019182525091905056fea164736f6c634300081a000a", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101e25760003560e01c8062e9a407146101e75780630aaffd2a146101fc5780630d616d201461020f5780630d9639ba146102175780630dcf4b8f146102375780630ebb172a1461024d57806312fa329b1461026f578063200d2ed2146102905780632816ee73146102aa5780632c6cda93146102bd5780632c7baaf3146102d0578063313f336b1461030f5780633362379414610345578063356c299d1461037957806342e94c90146103b75780634564168b146103d75780634a387cdf146103f35780634bb278f3146103fb5780634e7320ce14610403578063565a9d481461041857806356d399e81461043a578063570ca7351461046157806358b810d3146104885780636786452c1461049b5780636cf72aa4146104ae5780637c0b4bfb146104c157806386fa5063146105005780638c7e7a31146105275780638dd085571461053a578063937e09b11461055a57806396a608c0146105625780639ca002a61461056a578063a4b6aa831461057d578063bc063e1a14610585578063c3aca558146105a1578063ccec3716146105c8578063d26146a5146105db578063d826f88f146105ee578063dda0a81a146105f6578063e020e4c41461060d578063e50d50d814610620575b600080fd5b6101fa6101f5366004612e8f565b610640565b005b6101fa61020a366004612ecb565b6106c2565b6101fa6106cc565b61021f6107b7565b60405161022e93929190612f5d565b60405180910390f35b61023f61096d565b60405190815260200161022e565b6102576201518081565b6040516001600160401b03909116815260200161022e565b61028261027d366004612fca565b6109d9565b60405161022e929190612fe3565b600f5461029d9060ff1681565b60405161022e9190613013565b6101fa6102b836600461303b565b610a12565b6101fa6102cb366004613079565b610a1d565b6102d8610a93565b60405161022e919081518152602080830151908201526040808301519082015260609182015161ffff169181019190915260800190565b6006546007546008546009546103259392919084565b60408051948552602085019390935291830152606082015260800161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b60405161022e9190613094565b6002546003546004546005546103939392919061ffff1684565b6040805194855260208501939093529183015261ffff16606082015260800161022e565b61023f6103c5366004612ecb565b600a6020526000908152604090205481565b6000546001546103e5919082565b60405161022e9291906130a8565b600c5461023f565b6101fa610af3565b61040b610b68565b60405161022e91906130dc565b600f5461042a90610100900460ff1681565b604051901515815260200161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa610496366004613103565b610bc0565b6101fa6104a93660046131f1565b610c88565b6101fa6104bc366004613242565b610d02565b6104eb6104cf366004612ecb565b600d602052600090815260409020805460019091015460ff1682565b6040805192835290151560208301520161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61023f61053536600461325f565b610d78565b61054d610548366004612fca565b610e09565b60405161022e91906132a6565b61023f610e62565b61023f610f62565b61023f610578366004612fca565b610fb9565b61023f610fce565b61058e61271081565b60405161ffff909116815260200161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa6105d6366004612ecb565b611040565b61036c6105e9366004612fca565b6111df565b6101fa611209565b6105fe61127e565b60405161022e939291906132b4565b6101fa61061b3660046132ed565b61145e565b61023f61062e366004612ecb565b600b6020526000908152604090205481565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106b657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b60405180910390fd5b6106bf816114e3565b50565b6106bf33826118a8565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016330361070657610704611a29565b565b336000908152600b602052604081205461072090426133e4565b90506201518081101561076a57336000908152600b602052604090819020549051630429b06960e31b815260048101919091524260248201526201518060448201526064016106ad565b600061077533611b4c565b905080156107b35760405181815233907f249a82fbe5056a1940b4e996665ba2a82c340ae9fa1e069fd1ababf5508f396e9060200160405180910390a25b5050565b600e5460609081908190806001600160401b038111156107d9576107d9612d34565b604051908082528060200260200182016040528015610802578160200160208202803683370190505b509350806001600160401b0381111561081d5761081d612d34565b604051908082528060200260200182016040528015610846578160200160208202803683370190505b509250806001600160401b0381111561086157610861612d34565b60405190808252806020026020018201604052801561088a578160200160208202803683370190505b50915060005b81811015610966576000600e82815481106108ad576108ad6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912087519192509082908890859081106108eb576108eb6133f7565b60200260200101906001600160a01b031690816001600160a01b0316815250508060000154868481518110610922576109226133f7565b60209081029190910101526001810154855160ff9091169086908590811061094c5761094c6133f7565b911515602092830291909101909101525050600101610890565b5050909192565b600c54600090815b818110156109d4576000600c8281548110610992576109926133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506109c9908561340d565b935050600101610975565b505090565b600c81815481106109e957600080fd5b6000918252602090912060029091020180546001909101546001600160a01b0391821692501682565b6107b3338284611d49565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a8a57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf8161255d565b610ac26040518060800160405280600081526020016000815260200160008152602001600061ffff1681525090565b5060408051608081018252600254815260035460208201526004549181019190915260055461ffff16606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b6057337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6107046125e7565b610b936040518060800160405280600081526020016000815260200160008152602001600081525090565b50604080516080810182526006548152600754602082015260085491810191909152600954606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610c2d57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c35611a29565b610c3e8561255d565b610c47846114e3565b610c50836128a5565b8015610c8157610c817f00000000000000000000000000000000000000000000000000000000000000008383611d49565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cf557337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c8185858585856128bf565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610d6f57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf816128a5565b6000828211610d9e5782826040516376675ed960e11b81526004016106ad9291906130a8565b82600003610dcf576004610db36001866133e4565b610dbd9190613420565b610dc890600161340d565b9050610e02565b6000610ddb84846133e4565b905080610de96001876133e4565b610df39190613420565b610dfe90600161340d565b9150505b9392505050565b610e11612c79565b600c8281548110610e2457610e246133f7565b60009182526020918290206040805180820190915260029092020180546001600160a01b039081168352600190910154169181019190915292915050565b600e5460009081908190815b81811015610ee5576000600e8281548110610e8b57610e8b6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610edb578054610ecb908761340d565b9550610ed860018661340d565b94505b5050600101610e6e565b50610f5a83610ef261096d565b610f1c907f00000000000000000000000000000000000000000000000000000000000000006133e4565b610f2691906133e4565b600c54610f3490859061340d565b7f0000000000000000000000000000000000000000000000000000000000000000610d78565b935050505090565b600c54600090610f725750600090565b600a6000600c600081548110610f8a57610f8a6133f7565b600091825260208083206002909202909101546001600160a01b03168352820192909252604001902054905090565b6000610fc88260006001610d78565b92915050565b600e54600090815b818110156109d4576000600e8281548110610ff357610ff36133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16611036578054611033908661340d565b94505b5050600101610fd6565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110ad57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6003600f5460ff1660038111156110c6576110c6612ffd565b141580156110ea57506000600f5460ff1660038111156110e8576110e8612ffd565b145b1561110f57600f54604051632f3e69dd60e01b81526106ad9160ff1690600401613013565b6040516370a0823160e01b815281906000906001600160a01b038316906370a0823190611140903090600401613094565b602060405180830381865afa15801561115d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111819190613442565b9050600081116111a6578260405163e932c57360e01b81526004016106ad9190613094565b6111da6001600160a01b0383167f0000000000000000000000000000000000000000000000000000000000000000836129da565b505050565b600e81815481106111ef57600080fd5b6000918252602090912001546001600160a01b0316905081565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461127657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610704611a29565b600c5460609081908190806001600160401b038111156112a0576112a0612d34565b6040519080825280602002602001820160405280156112c9578160200160208202803683370190505b509350806001600160401b038111156112e4576112e4612d34565b60405190808252806020026020018201604052801561130d578160200160208202803683370190505b509250806001600160401b0381111561132857611328612d34565b604051908082528060200260200182016040528015611351578160200160208202803683370190505b50915060005b81811015610966576000600c8281548110611374576113746133f7565b60009182526020909120600290910201805487519192506001600160a01b0316908790849081106113a7576113a76133f7565b6001600160a01b039283166020918202929092010152600182015486519116908690849081106113d9576113d96133f7565b60200260200101906001600160a01b031690816001600160a01b031681525050600a600087848151811061140f5761140f6133f7565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020016000205484838151811061144a5761144a6133f7565b602090810291909101015250600101611357565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114cb57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6114da87878787878787612a32565b50505050505050565b6000600f5460ff1660038111156114fc576114fc612ffd565b1461152157600f546040516312ba63f960e11b81526106ad9160ff1690600401613013565b600e5460005b8181101561158057600d6000600e8381548110611546576115466133f7565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff1916905501611527565b5061158d600e6000612c90565b5080517f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000010156116175781517f0000000000000000000000000000000000000000000000000000000000000000604051630962235760e31b81526004016106ad9291906130a8565b815160005b818110156118a257806000036116cd577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316848281518110611668576116686133f7565b6020026020010151600001516001600160a01b0316146116cd5760405163ef77b46760e01b8152600481018290526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660248201526044016106ad565b60006001600160a01b03168482815181106116ea576116ea6133f7565b6020026020010151600001516001600160a01b0316036117205760405163621caef560e11b8152600481018290526024016106ad565b6000600d6000868481518110611738576117386133f7565b6020026020010151600001516001600160a01b03166001600160a01b031681526020019081526020016000209050806000015460001461178e576040516308308c2360e21b8152600481018390526024016106ad565b60006117bb85847f0000000000000000000000000000000000000000000000000000000000000000610d78565b905060008684815181106117d1576117d16133f7565b6020026020010151602001519050818110156118065783818360405163c4aa8aa360e01b81526004016106ad9392919061345b565b8086101561182d57838187604051636d3ae3a760e01b81526004016106ad9392919061345b565b61183781876133e4565b9550600e87858151811061184d5761184d6133f7565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161161c565b50505050565b6001600f5460ff1660038111156118c1576118c1612ffd565b141580156118e657506002600f5460ff1660038111156118e3576118e3612ffd565b14155b1561190b576002546040516310004b0160e11b815260048101919091526024016106ad565b60006119178383612aa3565b600c549091506000908190815b818110156119bd576000600c8281548110611941576119416133f7565b6000918252602090912060029091020180549091506001600160a01b03808a169116036119b45760018101546001600160a01b03808816911603611989575050505050505050565b600190810180546001600160a01b038881166001600160a01b031983161790925516945092506119bd565b50600101611924565b50816119de5785604051632fd768d360e11b81526004016106ad9190613094565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe728486604051611a19929190612fe3565b60405180910390a2505050505050565b600c5460005b81811015611aec576000600c8281548110611a4c57611a4c6133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff166003811115611a9257611a92612ffd565b14158015611aa05750600081115b15611ad957611ad96001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001683836129da565b611ae282612ac1565b5050600101611a2f565b50611af9600c6000612cae565b600f805460ff191690556040805160008082526020820190925281611b40565b6040805180820190915260008082526020820152815260200190600190039081611b195790505b5090506107b3816114e3565b6001600160a01b0381166000908152600a602052604081205490819003611b7257919050565b611b7b82612ac1565b600c5460005b81811015611c9657600c8181548110611b9c57611b9c6133f7565b60009182526020909120600290910201546001600160a01b0390811690851603611c8e57600c611bcd6001846133e4565b81548110611bdd57611bdd6133f7565b9060005260206000209060020201600c8281548110611bfe57611bfe6133f7565b60009182526020909120825460029092020180546001600160a01b039283166001600160a01b0319918216178255600193840154939091018054939092169216919091179055600c805480611c5557611c55613471565b60008281526020902060026000199092019182020180546001600160a01b03199081168255600191909101805490911690559055611c96565b600101611b81565b506001600160a01b0383166000908152600d6020526040902060018101805460ff191690556003600f5460ff166003811115611cd457611cd4612ffd565b03611ce25760009250611d42565b611d166001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001685856129da565b6002600f5460ff166003811115611d2f57611d2f612ffd565b03611d4257600f805460ff191660011790555b5050919050565b6000600f5460ff166003811115611d6257611d62612ffd565b14158015611d8757506001600f5460ff166003811115611d8457611d84612ffd565b14155b15611dac57600f54604051630295f95f60e51b81526106ad9160ff1690600401613013565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e2e9190613442565b7f000000000000000000000000000000000000000000000000000000000000000014611f12577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ef79190613442565b604051631248081b60e31b81526004016106ad9291906130a8565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f949190613442565b7f000000000000000000000000000000000000000000000000000000000000000014612039577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b6000600f5460ff16600381111561205257612052612ffd565b03612145577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316146120cd57827f00000000000000000000000000000000000000000000000000000000000000006040516316a4ebc160e21b81526004016106ad929190612fe3565b600f805460ff1916600117905560025460055460405161ffff90911681526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d602052604090208054158015906121725750600181015460ff16155b156121b257805482101561219d57805460405163454cd8e160e01b81526106ad9184916004016130a8565b6001818101805460ff19169091179055612207565b6001600160a01b0384166000908152600a60205260409020541580156121de57506121db610e62565b82105b1561220757816121ec610e62565b6040516310e45ff160e21b81526004016106ad9291906130a8565b6001600160a01b0384166000908152600a602052604081205490036122d85760006122328585612aa3565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c89091018054919093169116179055506122e2565b6122e284846118a8565b6001600160a01b0384166000908152600a60205260408120805484929061230a90849061340d565b90915550506001600160a01b0384166000908152600b6020526040812042905561233261096d565b9050600061233e610fce565b90507f000000000000000000000000000000000000000000000000000000000000000061236b828461340d565b11156123b05781817f000000000000000000000000000000000000000000000000000000000000000060405163513b428b60e11b81526004016106ad9392919061345b565b600c547f00000000000000000000000000000000000000000000000000000000000000001015612415576040516315b5c3d560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201526024016106ad565b7f000000000000000000000000000000000000000000000000000000000000000082036124a2576002546040516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa2856040516124dd91815260200190565b60405180910390a261251a6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016873087612ae8565b6002600f5460ff16600381111561253357612533612ffd565b1480156125485750600f54610100900460ff16155b15612555576125556125e7565b505050505050565b6000600f5460ff16600381111561257657612576612ffd565b1461259b57600f5460405163ad88fc8f60e01b81526106ad9160ff1690600401613013565b61271061ffff821611156125cf57604051624ae8fd60e41b815261ffff8216600482015261271060248201526044016106ad565b6005805461ffff191661ffff92909216919091179055565b6002600f5460ff16600381111561260057612600612ffd565b1461262557600f546040516383696f6f60e01b81526106ad9160ff1690600401613013565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b0381111561267b5761267b612d34565b6040519080825280602002602001820160405280156126b457816020015b6126a1612ccf565b8152602001906001900390816126995790505b50905060005b8281101561274f576000600c82815481106126d7576126d76133f7565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061273b5761273b6133f7565b6020908102919091010152506001016126ba565b5060405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906127de907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090600401613487565b6020604051808303816000875af11580156127fd573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061282191906134a0565b50604051632f1fbfb360e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bc7efecc90612877906000906006906002908790600401613504565b600060405180830381600087803b15801561289157600080fd5b505af1158015612555573d6000803e3d6000fd5b600f80549115156101000261ff0019909216919091179055565b6000600f5460ff1660038111156128d8576128d8612ffd565b146128fd57600f54604051639a0293fd60e01b81526106ad9160ff1690600401613013565b60405160016226579360e01b031981526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063ffd9a86d9061297290889088907f000000000000000000000000000000000000000000000000000000000000000090899060040161357b565b600060405180830381600087803b15801561298c57600080fd5b505af11580156129a0573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6111da83846001600160a01b031663a9059cbb8585604051602401612a00929190613487565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050612b21565b612a3a611a29565b612a5387878760000151886020015189604001516128bf565b612a60856060015161255d565b612a69846114e3565b612a72836128a5565b80156114da576114da7f00000000000000000000000000000000000000000000000000000000000000008383611d49565b60006001600160a01b03821615612aba5781610e02565b5090919050565b6001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b6040516001600160a01b0384811660248301528381166044830152606482018390526118a29186918216906323b872dd90608401612a00565b6000612b366001600160a01b03841683612b7b565b90508051600014158015612b5b575080806020019051810190612b5991906134a0565b155b156111da5782604051635274afe760e01b81526004016106ad9190613094565b6060610e028383600084600080856001600160a01b03168486604051612ba191906135b6565b60006040518083038185875af1925050503d8060008114612bde576040519150601f19603f3d011682016040523d82523d6000602084013e612be3565b606091505b5091509150612bf3868383612bfd565b9695505050505050565b606082612c1257612c0d82612c50565b610e02565b8151158015612c2957506001600160a01b0384163b155b15612c495783604051639996b31560e01b81526004016106ad9190613094565b5080610e02565b805115612c605780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b604080518082019091526000808252602082015290565b50805460008255906000526020600020908101906106bf9190612cef565b50805460008255600202906000526020600020908101906106bf9190612d08565b6040518060400160405280612ce2612c79565b8152602001600081525090565b5b80821115612d045760008155600101612cf0565b5090565b5b80821115612d045780546001600160a01b031990811682556001820180549091169055600201612d09565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715612d6c57612d6c612d34565b60405290565b604051608081016001600160401b0381118282101715612d6c57612d6c612d34565b604051601f8201601f191681016001600160401b0381118282101715612dbc57612dbc612d34565b604052919050565b80356001600160a01b0381168114612ddb57600080fd5b919050565b600082601f830112612df157600080fd5b81356001600160401b03811115612e0a57612e0a612d34565b612e1960208260051b01612d94565b8082825260208201915060208360061b860101925085831115612e3b57600080fd5b602085015b83811015612e855760408188031215612e5857600080fd5b612e60612d4a565b612e6982612dc4565b8152602082810135818301529084529290920191604001612e40565b5095945050505050565b600060208284031215612ea157600080fd5b81356001600160401b03811115612eb757600080fd5b612ec384828501612de0565b949350505050565b600060208284031215612edd57600080fd5b610e0282612dc4565b600081518084526020840193506020830160005b82811015612f215781516001600160a01b0316865260209586019590910190600101612efa565b5093949350505050565b600081518084526020840193506020830160005b82811015612f21578151865260209586019590910190600101612f3f565b606081526000612f706060830186612ee6565b8281036020840152612f828186612f2b565b83810360408501528451808252602080870193509091019060005b81811015612fbd5783511515835260209384019390920191600101612f9d565b5090979650505050505050565b600060208284031215612fdc57600080fd5b5035919050565b6001600160a01b0392831681529116602082015260400190565b634e487b7160e01b600052602160045260246000fd5b602081016004831061303557634e487b7160e01b600052602160045260246000fd5b91905290565b6000806040838503121561304e57600080fd5b8235915061305e60208401612dc4565b90509250929050565b803561ffff81168114612ddb57600080fd5b60006020828403121561308b57600080fd5b610e0282613067565b6001600160a01b0391909116815260200190565b918252602082015260400190565b805182526020810151602083015260408101516040830152606081015160608301525050565b60808101610fc882846130b6565b80151581146106bf57600080fd5b8035612ddb816130ea565b600080600080600060a0868803121561311b57600080fd5b61312486613067565b945060208601356001600160401b0381111561313f57600080fd5b61314b88828901612de0565b945050604086013561315c816130ea565b925061316a60608701612dc4565b949793965091946080013592915050565b60006040828403121561318d57600080fd5b613195612d4a565b823581526020928301359281019290925250919050565b6000608082840312156131be57600080fd5b6131c6612d72565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b6000806000806000610120868803121561320a57600080fd5b613214878761317b565b945061322387604088016131ac565b949794965050505060c08301359260e081013592610100909101359150565b60006020828403121561325457600080fd5b8135610e02816130ea565b60008060006060848603121561327457600080fd5b505081359360208301359350604090920135919050565b80516001600160a01b03908116835260209182015116910152565b60408101610fc8828461328b565b6060815260006132c76060830186612ee6565b82810360208401526132d98186612ee6565b90508281036040840152612bf38185612f2b565b60008060008060008060008789036101c081121561330a57600080fd5b6133148a8a61317b565b97506133238a60408b016131ac565b9650608060bf198201121561333757600080fd5b50613340612d72565b60c0890135815260e0890135602082015261010089013560408201526133696101208a01613067565b606082015294506101408801356001600160401b0381111561338a57600080fd5b6133968a828b01612de0565b9450506133a661016089016130f8565b92506133b56101808901612dc4565b9699959850939692959194919350506101a09091013590565b634e487b7160e01b600052601160045260246000fd5b81810381811115610fc857610fc86133ce565b634e487b7160e01b600052603260045260246000fd5b80820180821115610fc857610fc86133ce565b60008261343d57634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561345457600080fd5b5051919050565b9283526020830191909152604082015260600190565b634e487b7160e01b600052603160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000602082840312156134b257600080fd5b8151610e02816130ea565b600081518084526020840193506020830160005b82811015612f215781516134e687825161328b565b602090810151604088015260609096019591909101906001016134d1565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000612bf36101608301846134bd565b8451815260208086015190820152610100810161359b60408301866130b6565b6001600160a01b039390931660c082015260e0015292915050565b6000825160005b818110156135d757602081860181015185830152016135bd565b50600092019182525091905056fea164736f6c634300081a000a", "linkReferences": {}, "deployedLinkReferences": {} } diff --git a/abis/ServiceNodeContributionFactory.json b/src/web3client/abis/ServiceNodeContributionFactory.json similarity index 97% rename from abis/ServiceNodeContributionFactory.json rename to src/web3client/abis/ServiceNodeContributionFactory.json index e1aac29..68ab48d 100644 --- a/abis/ServiceNodeContributionFactory.json +++ b/src/web3client/abis/ServiceNodeContributionFactory.json @@ -393,8 +393,8 @@ "type": "function" } ], - "bytecode": "0x6080604052348015600f57600080fd5b50615ec18061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100af5760003560e01c806333623794146100b45780633f4ba83a146100dd5780635c975abb146100e7578063715018a6146100ff57806379ba5097146101075780638456cb591461010f5780638da5cb5b146101175780638faff8621461011f578063c4d66de814610132578063c70242ad14610145578063e30c397814610168578063f2fde38b14610170578063f7bc39bf14610183575b600080fd5b6000546100c7906001600160a01b031681565b6040516100d49190610809565b60405180910390f35b6100e56101af565b005b6100ef6101c1565b60405190151581526020016100d4565b6100e56101d6565b6100e56101e8565b6100e5610230565b6100c7610240565b6100c761012d36600461084a565b61025b565b6100e5610140366004610925565b610387565b6100ef610153366004610925565b60016020526000908152604090205460ff1681565b6100c76104a3565b6100e561017e366004610925565b6104ae565b6100ef610191366004610925565b6001600160a01b031660009081526001602052604090205460ff1690565b6101b761051f565b6101bf610551565b565b6000806101cc6105a8565b5460ff1692915050565b6101de61051f565b6101bf60006105cc565b33806101f26104a3565b6001600160a01b031614610224578060405163118cdaa760e01b815260040161021b9190610809565b60405180910390fd5b61022d816105cc565b50565b61023861051f565b6101bf6105f3565b60008061024b61063a565b546001600160a01b031692915050565b600061026561065e565b60008054604080516386fa506360e01b815290516001600160a01b039092169182916386fa50639160048083019260209291908290030181865afa1580156102b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102d59190610947565b8989898989896040516102e7906107fc565b6102f89897969594939291906109eb565b604051809103906000f080158015610314573d6000803e3d6000fd5b506001600160a01b038116600081815260016020819052604091829020805460ff1916909117905551919350839250907feac84630ba02e5ab324a651281c90ec45563a21f07fdf52b6f601f312e2de27a90610374908935815260200190565b60405180910390a2509695505050505050565b6000610391610684565b805490915060ff600160401b82041615906001600160401b03166000811580156103b85750825b90506000826001600160401b031660011480156103d45750303b155b9050811580156103e2575080155b156104005760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b0319166001178555831561042957845460ff60401b1916600160401b1785555b600080546001600160a01b0319166001600160a01b03881617905561044d336106a8565b6104556106b9565b831561049b57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b60008061024b6106c9565b6104b661051f565b60006104c06106c9565b80546001600160a01b0319166001600160a01b03841690811782559091506104e6610240565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b33610528610240565b6001600160a01b0316146101bf573360405163118cdaa760e01b815260040161021b9190610809565b6105596106ed565b60006105636105a8565b805460ff1916815590507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b60405161059d9190610809565b60405180910390a150565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330090565b60006105d66106c9565b80546001600160a01b031916815590506105ef82610712565b5050565b6105fb61065e565b60006106056105a8565b805460ff1916600117815590507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586105903390565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b6106666101c1565b156101bf5760405163d93c066560e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6106b061076e565b61022d81610793565b6106c161076e565b6101bf6107c5565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b6106f56101c1565b6101bf57604051638dfc202b60e01b815260040160405180910390fd5b600061071c61063a565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b6107766107e2565b6101bf57604051631afcd79f60e31b815260040160405180910390fd5b61079b61076e565b6001600160a01b038116610224576000604051631e4fbdf760e01b815260040161021b9190610809565b6107cd61076e565b60006107d76105a8565b805460ff1916905550565b60006107ec610684565b54600160401b900460ff16919050565b61543d80610a7883390190565b6001600160a01b0391909116815260200190565b60006080828403121561082f57600080fd5b50919050565b8035801515811461084557600080fd5b919050565b60008060008060008086880361018081121561086557600080fd5b604081121561087357600080fd5b50869550610884886040890161081d565b94506108938860c0890161081d565b93506101408701356001600160401b038111156108af57600080fd5b8701601f810189136108c057600080fd5b80356001600160401b038111156108d657600080fd5b8960208260061b84010111156108eb57600080fd5b602091909101935091506109026101608801610835565b90509295509295509295565b80356001600160a01b038116811461084557600080fd5b60006020828403121561093757600080fd5b6109408261090e565b9392505050565b60006020828403121561095957600080fd5b5051919050565b803582526020808201359083015260408082013590830152606081013561ffff811680821461098e57600080fd5b80606085015250505050565b81835260208301925060008160005b848110156109e1576001600160a01b036109c28361090e565b16865260208281013590870152604095860195909101906001016109a9565b5093949350505050565b6001600160a01b03891681526020808201899052873560408084019190915288820135606080850191909152883560808501529188013560a084015287013560c083015286013560e08201526000610a47610100830187610960565b6101c0610180830152610a5f6101c08301858761099a565b8315156101a08401529050999850505050505050505056fe610120604052600f805460ff1916905534801561001b57600080fd5b5060405161543d38038061543d83398101604081905261003a9161196c565b866001600160a01b0381166100a45760405162461bcd60e51b815260206004820152602560248201527f5368617265643a205a65726f2d61646472657373206973206e6f74207065726d6044820152641a5d1d195960da1b60648201526084015b60405180910390fd5b86806000036100f55760405162461bcd60e51b815260206004820152601b60248201527f5368617265643a2075696e7420696e70757420697320656d7074790000000000604482015260640161009b565b6001600160a01b03891660a081905260408051630ada733d60e31b815290516356d399e8916004808201926020929091908290030181865afa15801561013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101639190611a30565b60c0818152505060a0516001600160a01b0316637c89d2f06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ce9190611a49565b6001600160a01b03166080526101008890523260e05260006101f588888888888680610204565b50505050505050505050611c7f565b61020c61026c565b845160208601516040870151610225928a928a92610398565b6060850151610233906104ad565b61023c84610537565b600f805461ff0019166101008515150217905580156102635760e051610263908383610859565b50505050505050565b600c5460005b81811015610334576000600c828154811061028f5761028f611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff1660038111156102d5576102d5611751565b141580156102e35750600081115b156102ff576080516102ff906001600160a01b03168383610e71565b61032a826001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b5050600101610272565b50610341600c60006116cd565b600f805460ff191690556040805160008082526020820190925281610388565b60408051808201909152600080825260208201528152602001906001900390816103615790505b50905061039481610537565b5050565b6000600f5460ff1660038111156103b1576103b1611751565b146103d657600f54604051639a0293fd60e01b815261009b9160ff1690600401611a7c565b60a05160e0516040805160016226579360e01b03198152885160048201526020808a01516024830152885160448301528801516064820152908701516084820152606087015160a48201526001600160a01b0391821660c482015260e4810186905291169063ffd9a86d9061010401600060405180830381600087803b15801561045f57600080fd5b505af1158015610473573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6000600f5460ff1660038111156104c6576104c6611751565b146104eb57600f5460405163ad88fc8f60e01b815261009b9160ff1690600401611a7c565b61271061ffff8216111561051f57604051624ae8fd60e41b815261ffff82166004820152612710602482015260440161009b565b6005805461ffff191661ffff92909216919091179055565b6000600f5460ff16600381111561055057610550611751565b1461057557600f546040516312ba63f960e11b815261009b9160ff1690600401611a7c565b600e5460005b818110156105d457600d6000600e838154811061059a5761059a611a66565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff191690550161057b565b506105e1600e60006116ee565b5060c051610100518251111561061957815161010051604051630962235760e31b81526004810192909252602482015260440161009b565b815160005b8181101561085357806000036106955760e0516001600160a01b031684828151811061064c5761064c611a66565b6020026020010151600001516001600160a01b0316146106955760e05160405163ef77b46760e01b8152600481018390526001600160a01b03909116602482015260440161009b565b60006001600160a01b03168482815181106106b2576106b2611a66565b6020026020010151600001516001600160a01b0316036106e85760405163621caef560e11b81526004810182905260240161009b565b6000600d600086848151811061070057610700611a66565b6020026020010151600001516001600160a01b03166001600160a01b0316815260200190815260200160002090508060000154600014610756576040516308308c2360e21b81526004810183905260240161009b565b600061076c858461010051610ece60201b60201c565b9050600086848151811061078257610782611a66565b6020026020010151602001519050818110156107b75783818360405163c4aa8aa360e01b815260040161009b93929190611aa4565b808610156107de57838187604051636d3ae3a760e01b815260040161009b93929190611aa4565b6107e88187611ad0565b9550600e8785815181106107fe576107fe611a66565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161061e565b50505050565b6000600f5460ff16600381111561087257610872611751565b1415801561089757506001600f5460ff16600381111561089457610894611751565b14155b156108bc57600f54604051630295f95f60e51b815261009b9160ff1690600401611a7c565b60a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108fc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109209190611a30565b61010051146109b3576101005160a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109919190611a30565b604051631248081b60e31b81526004810192909252602482015260440161009b565b60a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a179190611a30565b60c05114610a625760c05160a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b6000600f5460ff166003811115610a7b57610a7b611751565b03610b145760e0516001600160a01b0316836001600160a01b031614610ab95760e0516040516316a4ebc160e21b815261009b918591600401611ae3565b600f805460ff1916600117905560e05160025460055460405161ffff90911681526001600160a01b03909216917fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d60205260409020805415801590610b415750600181015460ff16155b15610b8a578054821015610b7557805460405163454cd8e160e01b815261009b918491600401918252602082015260400190565b6001818101805460ff19169091179055610be6565b6001600160a01b0384166000908152600a6020526040902054158015610bb65750610bb3610f65565b82105b15610be65781610bc4610f65565b6040516310e45ff160e21b81526004810192909252602482015260440161009b565b6001600160a01b0384166000908152600a60205260408120549003610cb7576000610c11858561102b565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8909101805491909316911617905550610cc1565b610cc18484611049565b6001600160a01b0384166000908152600a602052604081208054849290610ce9908490611afd565b90915550506001600160a01b0384166000908152600b60205260408120429055610d116111ca565b90506000610d1d611236565b60c051909150610d2d8284611afd565b1115610d5457818160c05160405163513b428b60e11b815260040161009b93929190611aa4565b61010051600c541115610d8157610100516040516315b5c3d560e21b815260040161009b91815260200190565b60c0518203610dd35760e0516002546040516001600160a01b03909216917f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa285604051610e0e91815260200190565b60405180910390a2608051610e2e906001600160a01b03168730876112a8565b6002600f5460ff166003811115610e4757610e47611751565b148015610e5c5750600f54610100900460ff16155b15610e6957610e696112e1565b505050505050565b610ec983846001600160a01b031663a9059cbb8585604051602401610e97929190611b10565b60408051808303601f1901815291905260208101805160e09390931b6001600160e01b03938416179052915061154216565b505050565b6000828211610efa576040516376675ed960e11b8152600481018490526024810183905260440161009b565b82600003610f2b576004610f0f600186611ad0565b610f199190611b29565b610f24906001611afd565b9050610f5e565b6000610f378484611ad0565b905080610f45600187611ad0565b610f4f9190611b29565b610f5a906001611afd565b9150505b9392505050565b600e5460009081908190815b81811015610fe8576000600e8281548110610f8e57610f8e611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610fde578054610fce9087611afd565b9550610fdb600186611afd565b94505b5050600101610f71565b5061102383610ff56111ca565b60c0516110029190611ad0565b61100c9190611ad0565b600c5461101a908590611afd565b61010051610ece565b935050505090565b60006001600160a01b038216156110425781610f5e565b5090919050565b6001600f5460ff16600381111561106257611062611751565b1415801561108757506002600f5460ff16600381111561108457611084611751565b14155b156110ac576002546040516310004b0160e11b8152600481019190915260240161009b565b60006110b8838361102b565b600c549091506000908190815b8181101561115e576000600c82815481106110e2576110e2611a66565b6000918252602090912060029091020180549091506001600160a01b03808a169116036111555760018101546001600160a01b0380881691160361112a575050505050505050565b600190810180546001600160a01b038881166001600160a01b0319831617909255169450925061115e565b506001016110c5565b508161117f5785604051632fd768d360e11b815260040161009b9190611b4b565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe7284866040516111ba929190611ae3565b60405180910390a2505050505050565b600c54600090815b81811015611231576000600c82815481106111ef576111ef611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506112269085611afd565b9350506001016111d2565b505090565b600e54600090815b81811015611231576000600e828154811061125b5761125b611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff1661129e57805461129b9086611afd565b94505b505060010161123e565b6040516001600160a01b0384811660248301528381166044830152606482018390526108539186918216906323b872dd90608401610e97565b6002600f5460ff1660038111156112fa576112fa611751565b1461131f57600f546040516383696f6f60e01b815261009b9160ff1690600401611a7c565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b038111156113755761137561177c565b6040519080825280602002602001820160405280156113c957816020015b60408051608081018252600091810182815260608201839052815260208101919091528152602001906001900390816113935790505b50905060005b82811015611464576000600c82815481106113ec576113ec611a66565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061145057611450611a66565b6020908102919091010152506001016113cf565b506080516001600160a01b031663095ea7b360a05160c0516040518363ffffffff1660e01b8152600401611499929190611b10565b6020604051808303816000875af11580156114b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114dc9190611b5f565b5060a0516001600160a01b031663bc7efecc600060066002856040518563ffffffff1660e01b81526004016115149493929190611bd9565b600060405180830381600087803b15801561152e57600080fd5b505af1158015610e69573d6000803e3d6000fd5b60006115576001600160a01b0384168361159c565b9050805160001415801561157c57508080602001905181019061157a9190611b5f565b155b15610ec95782604051635274afe760e01b815260040161009b9190611b4b565b6060610f5e838360006115b0565b92915050565b6060814710156115d5573060405163cd78605960e01b815260040161009b9190611b4b565b600080856001600160a01b031684866040516115f19190611c50565b60006040518083038185875af1925050503d806000811461162e576040519150601f19603f3d011682016040523d82523d6000602084013e611633565b606091505b50909250905061164486838361164e565b9695505050505050565b6060826116635761165e826116a1565b610f5e565b815115801561167a57506001600160a01b0384163b155b1561169a5783604051639996b31560e01b815260040161009b9190611b4b565b5080610f5e565b8051156116b15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b50805460008255600202906000526020600020908101906116ca919061170c565b50805460008255906000526020600020908101906116ca919061173c565b5b808211156117385780546001600160a01b03199081168255600182018054909116905560020161170d565b5090565b5b80821115611738576000815560010161173d565b634e487b7160e01b600052602160045260246000fd5b6001600160a01b03811681146116ca57600080fd5b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b03811182821017156117b4576117b461177c565b60405290565b604080519081016001600160401b03811182821017156117b4576117b461177c565b604051601f8201601f191681016001600160401b03811182821017156118045761180461177c565b604052919050565b60006080828403121561181e57600080fd5b611826611792565b8251815260208084015190820152604080840151908201526060928301519281019290925250919050565b60006080828403121561186357600080fd5b61186b611792565b825181526020808401519082015260408084015190820152606083015190915061ffff8116811461189b57600080fd5b606082015292915050565b600082601f8301126118b757600080fd5b81516001600160401b038111156118d0576118d061177c565b6118df60208260051b016117dc565b8082825260208201915060208360061b86010192508583111561190157600080fd5b602085015b8381101561194d576040818803121561191e57600080fd5b6119266117ba565b815161193181611767565b8152602082810151818301529084529290920191604001611906565b5095945050505050565b8051801515811461196757600080fd5b919050565b60008060008060008060008789036101c081121561198957600080fd5b885161199481611767565b60208a015190985096506040603f19820112156119b057600080fd5b506119b96117ba565b604089015181526060890151602082015294506119d98960808a0161180c565b93506119e9896101008a01611851565b6101808901519093506001600160401b03811115611a0657600080fd5b611a128a828b016118a6565b925050611a226101a08901611957565b905092959891949750929550565b600060208284031215611a4257600080fd5b5051919050565b600060208284031215611a5b57600080fd5b8151610f5e81611767565b634e487b7160e01b600052603260045260246000fd5b6020810160048310611a9e57634e487b7160e01b600052602160045260246000fd5b91905290565b9283526020830191909152604082015260600190565b634e487b7160e01b600052601160045260246000fd5b818103818111156115aa576115aa611aba565b6001600160a01b0392831681529116602082015260400190565b808201808211156115aa576115aa611aba565b6001600160a01b03929092168252602082015260400190565b600082611b4657634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b0391909116815260200190565b600060208284031215611b7157600080fd5b610f5e82611957565b600081518084526020840193506020830160005b82811015611bcf578151805180516001600160a01b039081168952602091820151168189015290810151604088015260609096019590910190600101611b8e565b5093949350505050565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000611644610160830184611b7a565b6000825160005b81811015611c715760208186018101518583015201611c57565b506000920191825250919050565b60805160a05160c05160e051610100516135f2611e4b6000396000818161052c01528181610f36015281816115b4015281816115dd0152818161179701528181611e3001528181611e56015281816123b501526123e90152600081816104660152818161064b01528181610673015281816106d601528181610a2801528181610a5001528181610afe01528181610b2601528181610bcb01528181610bf301528181610c5b01528181610c9301528181610cbb01528181610d0d01528181610d350152818161104b01528181611073015281816111b5015281816112140152818161123c01528181611469015281816114910152818161162e015281816116a00152818161205901528181612093015281816120f50152818161244c015281816129480152612a7d01526000818161043f01528181610ef80152818161159201528181611f9601528181611fbc01528181612342015281816123750152818161241701526127b601526000818161034a01528181611dae01528181611e7701528181611f1401528181611fdd015281816127940152818161283901526129170152600081816104a001528181611ab201528181611cef015281816124f2015261276701526135f26000f3fe608060405234801561001057600080fd5b50600436106101e25760003560e01c8062e9a407146101e75780630aaffd2a146101fc5780630d616d201461020f5780630d9639ba146102175780630dcf4b8f146102375780630ebb172a1461024d57806312fa329b1461026f578063200d2ed2146102905780632816ee73146102aa5780632c6cda93146102bd5780632c7baaf3146102d0578063313f336b1461030f5780633362379414610345578063356c299d1461037957806342e94c90146103b75780634564168b146103d75780634a387cdf146103f35780634bb278f3146103fb5780634e7320ce14610403578063565a9d481461041857806356d399e81461043a578063570ca7351461046157806358b810d31461048857806360e52ecb1461049b5780636786452c146104c25780636cf72aa4146104d55780637c0b4bfb146104e857806386fa5063146105275780638c7e7a311461054e5780638dd0855714610561578063937e09b11461058157806396a608c0146105895780639ca002a614610591578063a4b6aa83146105a4578063bc063e1a146105ac578063ccec3716146105c8578063d26146a5146105db578063d826f88f146105ee578063dda0a81a146105f6578063e020e4c41461060d578063e50d50d814610620575b600080fd5b6101fa6101f5366004612e8f565b610640565b005b6101fa61020a366004612ecb565b6106c2565b6101fa6106cc565b61021f6107b7565b60405161022e93929190612f5d565b60405180910390f35b61023f61096d565b60405190815260200161022e565b6102576201518081565b6040516001600160401b03909116815260200161022e565b61028261027d366004612fca565b6109d9565b60405161022e929190612fe3565b600f5461029d9060ff1681565b60405161022e9190613013565b6101fa6102b836600461303b565b610a12565b6101fa6102cb366004613079565b610a1d565b6102d8610a93565b60405161022e919081518152602080830151908201526040808301519082015260609182015161ffff169181019190915260800190565b6006546007546008546009546103259392919084565b60408051948552602085019390935291830152606082015260800161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b60405161022e9190613094565b6002546003546004546005546103939392919061ffff1684565b6040805194855260208501939093529183015261ffff16606082015260800161022e565b61023f6103c5366004612ecb565b600a6020526000908152604090205481565b6000546001546103e5919082565b60405161022e9291906130a8565b600c5461023f565b6101fa610af3565b61040b610b68565b60405161022e91906130dc565b600f5461042a90610100900460ff1681565b604051901515815260200161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa610496366004613103565b610bc0565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa6104d03660046131f1565b610c88565b6101fa6104e3366004613242565b610d02565b6105126104f6366004612ecb565b600d602052600090815260409020805460019091015460ff1682565b6040805192835290151560208301520161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61023f61055c36600461325f565b610d78565b61057461056f366004612fca565b610e09565b60405161022e91906132a6565b61023f610e62565b61023f610f62565b61023f61059f366004612fca565b610fb9565b61023f610fce565b6105b561271081565b60405161ffff909116815260200161022e565b6101fa6105d6366004612ecb565b611040565b61036c6105e9366004612fca565b6111df565b6101fa611209565b6105fe61127e565b60405161022e939291906132b4565b6101fa61061b3660046132ed565b61145e565b61023f61062e366004612ecb565b600b6020526000908152604090205481565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106b657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b60405180910390fd5b6106bf816114e3565b50565b6106bf33826118a8565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016330361070657610704611a29565b565b336000908152600b602052604081205461072090426133e4565b90506201518081101561076a57336000908152600b602052604090819020549051630429b06960e31b815260048101919091524260248201526201518060448201526064016106ad565b600061077533611b4c565b905080156107b35760405181815233907f249a82fbe5056a1940b4e996665ba2a82c340ae9fa1e069fd1ababf5508f396e9060200160405180910390a25b5050565b600e5460609081908190806001600160401b038111156107d9576107d9612d34565b604051908082528060200260200182016040528015610802578160200160208202803683370190505b509350806001600160401b0381111561081d5761081d612d34565b604051908082528060200260200182016040528015610846578160200160208202803683370190505b509250806001600160401b0381111561086157610861612d34565b60405190808252806020026020018201604052801561088a578160200160208202803683370190505b50915060005b81811015610966576000600e82815481106108ad576108ad6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912087519192509082908890859081106108eb576108eb6133f7565b60200260200101906001600160a01b031690816001600160a01b0316815250508060000154868481518110610922576109226133f7565b60209081029190910101526001810154855160ff9091169086908590811061094c5761094c6133f7565b911515602092830291909101909101525050600101610890565b5050909192565b600c54600090815b818110156109d4576000600c8281548110610992576109926133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506109c9908561340d565b935050600101610975565b505090565b600c81815481106109e957600080fd5b6000918252602090912060029091020180546001909101546001600160a01b0391821692501682565b6107b3338284611d49565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a8a57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf8161255d565b610ac26040518060800160405280600081526020016000815260200160008152602001600061ffff1681525090565b5060408051608081018252600254815260035460208201526004549181019190915260055461ffff16606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b6057337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6107046125e7565b610b936040518060800160405280600081526020016000815260200160008152602001600081525090565b50604080516080810182526006548152600754602082015260085491810191909152600954606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610c2d57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c35611a29565b610c3e8561255d565b610c47846114e3565b610c50836128a5565b8015610c8157610c817f00000000000000000000000000000000000000000000000000000000000000008383611d49565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cf557337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c8185858585856128bf565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610d6f57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf816128a5565b6000828211610d9e5782826040516376675ed960e11b81526004016106ad9291906130a8565b82600003610dcf576004610db36001866133e4565b610dbd9190613420565b610dc890600161340d565b9050610e02565b6000610ddb84846133e4565b905080610de96001876133e4565b610df39190613420565b610dfe90600161340d565b9150505b9392505050565b610e11612c79565b600c8281548110610e2457610e246133f7565b60009182526020918290206040805180820190915260029092020180546001600160a01b039081168352600190910154169181019190915292915050565b600e5460009081908190815b81811015610ee5576000600e8281548110610e8b57610e8b6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610edb578054610ecb908761340d565b9550610ed860018661340d565b94505b5050600101610e6e565b50610f5a83610ef261096d565b610f1c907f00000000000000000000000000000000000000000000000000000000000000006133e4565b610f2691906133e4565b600c54610f3490859061340d565b7f0000000000000000000000000000000000000000000000000000000000000000610d78565b935050505090565b600c54600090610f725750600090565b600a6000600c600081548110610f8a57610f8a6133f7565b600091825260208083206002909202909101546001600160a01b03168352820192909252604001902054905090565b6000610fc88260006001610d78565b92915050565b600e54600090815b818110156109d4576000600e8281548110610ff357610ff36133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16611036578054611033908661340d565b94505b5050600101610fd6565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110ad57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6003600f5460ff1660038111156110c6576110c6612ffd565b141580156110ea57506000600f5460ff1660038111156110e8576110e8612ffd565b145b1561110f57600f54604051632f3e69dd60e01b81526106ad9160ff1690600401613013565b6040516370a0823160e01b815281906000906001600160a01b038316906370a0823190611140903090600401613094565b602060405180830381865afa15801561115d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111819190613442565b9050600081116111a6578260405163e932c57360e01b81526004016106ad9190613094565b6111da6001600160a01b0383167f0000000000000000000000000000000000000000000000000000000000000000836129da565b505050565b600e81815481106111ef57600080fd5b6000918252602090912001546001600160a01b0316905081565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461127657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610704611a29565b600c5460609081908190806001600160401b038111156112a0576112a0612d34565b6040519080825280602002602001820160405280156112c9578160200160208202803683370190505b509350806001600160401b038111156112e4576112e4612d34565b60405190808252806020026020018201604052801561130d578160200160208202803683370190505b509250806001600160401b0381111561132857611328612d34565b604051908082528060200260200182016040528015611351578160200160208202803683370190505b50915060005b81811015610966576000600c8281548110611374576113746133f7565b60009182526020909120600290910201805487519192506001600160a01b0316908790849081106113a7576113a76133f7565b6001600160a01b039283166020918202929092010152600182015486519116908690849081106113d9576113d96133f7565b60200260200101906001600160a01b031690816001600160a01b031681525050600a600087848151811061140f5761140f6133f7565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020016000205484838151811061144a5761144a6133f7565b602090810291909101015250600101611357565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114cb57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6114da87878787878787612a32565b50505050505050565b6000600f5460ff1660038111156114fc576114fc612ffd565b1461152157600f546040516312ba63f960e11b81526106ad9160ff1690600401613013565b600e5460005b8181101561158057600d6000600e8381548110611546576115466133f7565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff1916905501611527565b5061158d600e6000612c90565b5080517f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000010156116175781517f0000000000000000000000000000000000000000000000000000000000000000604051630962235760e31b81526004016106ad9291906130a8565b815160005b818110156118a257806000036116cd577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316848281518110611668576116686133f7565b6020026020010151600001516001600160a01b0316146116cd5760405163ef77b46760e01b8152600481018290526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660248201526044016106ad565b60006001600160a01b03168482815181106116ea576116ea6133f7565b6020026020010151600001516001600160a01b0316036117205760405163621caef560e11b8152600481018290526024016106ad565b6000600d6000868481518110611738576117386133f7565b6020026020010151600001516001600160a01b03166001600160a01b031681526020019081526020016000209050806000015460001461178e576040516308308c2360e21b8152600481018390526024016106ad565b60006117bb85847f0000000000000000000000000000000000000000000000000000000000000000610d78565b905060008684815181106117d1576117d16133f7565b6020026020010151602001519050818110156118065783818360405163c4aa8aa360e01b81526004016106ad9392919061345b565b8086101561182d57838187604051636d3ae3a760e01b81526004016106ad9392919061345b565b61183781876133e4565b9550600e87858151811061184d5761184d6133f7565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161161c565b50505050565b6001600f5460ff1660038111156118c1576118c1612ffd565b141580156118e657506002600f5460ff1660038111156118e3576118e3612ffd565b14155b1561190b576002546040516310004b0160e11b815260048101919091526024016106ad565b60006119178383612aa3565b600c549091506000908190815b818110156119bd576000600c8281548110611941576119416133f7565b6000918252602090912060029091020180549091506001600160a01b03808a169116036119b45760018101546001600160a01b03808816911603611989575050505050505050565b600190810180546001600160a01b038881166001600160a01b031983161790925516945092506119bd565b50600101611924565b50816119de5785604051632fd768d360e11b81526004016106ad9190613094565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe728486604051611a19929190612fe3565b60405180910390a2505050505050565b600c5460005b81811015611aec576000600c8281548110611a4c57611a4c6133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff166003811115611a9257611a92612ffd565b14158015611aa05750600081115b15611ad957611ad96001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001683836129da565b611ae282612ac1565b5050600101611a2f565b50611af9600c6000612cae565b600f805460ff191690556040805160008082526020820190925281611b40565b6040805180820190915260008082526020820152815260200190600190039081611b195790505b5090506107b3816114e3565b6001600160a01b0381166000908152600a602052604081205490819003611b7257919050565b611b7b82612ac1565b600c5460005b81811015611c9657600c8181548110611b9c57611b9c6133f7565b60009182526020909120600290910201546001600160a01b0390811690851603611c8e57600c611bcd6001846133e4565b81548110611bdd57611bdd6133f7565b9060005260206000209060020201600c8281548110611bfe57611bfe6133f7565b60009182526020909120825460029092020180546001600160a01b039283166001600160a01b0319918216178255600193840154939091018054939092169216919091179055600c805480611c5557611c55613471565b60008281526020902060026000199092019182020180546001600160a01b03199081168255600191909101805490911690559055611c96565b600101611b81565b506001600160a01b0383166000908152600d6020526040902060018101805460ff191690556003600f5460ff166003811115611cd457611cd4612ffd565b03611ce25760009250611d42565b611d166001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001685856129da565b6002600f5460ff166003811115611d2f57611d2f612ffd565b03611d4257600f805460ff191660011790555b5050919050565b6000600f5460ff166003811115611d6257611d62612ffd565b14158015611d8757506001600f5460ff166003811115611d8457611d84612ffd565b14155b15611dac57600f54604051630295f95f60e51b81526106ad9160ff1690600401613013565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e2e9190613442565b7f000000000000000000000000000000000000000000000000000000000000000014611f12577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ef79190613442565b604051631248081b60e31b81526004016106ad9291906130a8565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f949190613442565b7f000000000000000000000000000000000000000000000000000000000000000014612039577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b6000600f5460ff16600381111561205257612052612ffd565b03612145577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316146120cd57827f00000000000000000000000000000000000000000000000000000000000000006040516316a4ebc160e21b81526004016106ad929190612fe3565b600f805460ff1916600117905560025460055460405161ffff90911681526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d602052604090208054158015906121725750600181015460ff16155b156121b257805482101561219d57805460405163454cd8e160e01b81526106ad9184916004016130a8565b6001818101805460ff19169091179055612207565b6001600160a01b0384166000908152600a60205260409020541580156121de57506121db610e62565b82105b1561220757816121ec610e62565b6040516310e45ff160e21b81526004016106ad9291906130a8565b6001600160a01b0384166000908152600a602052604081205490036122d85760006122328585612aa3565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c89091018054919093169116179055506122e2565b6122e284846118a8565b6001600160a01b0384166000908152600a60205260408120805484929061230a90849061340d565b90915550506001600160a01b0384166000908152600b6020526040812042905561233261096d565b9050600061233e610fce565b90507f000000000000000000000000000000000000000000000000000000000000000061236b828461340d565b11156123b05781817f000000000000000000000000000000000000000000000000000000000000000060405163513b428b60e11b81526004016106ad9392919061345b565b600c547f00000000000000000000000000000000000000000000000000000000000000001015612415576040516315b5c3d560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201526024016106ad565b7f000000000000000000000000000000000000000000000000000000000000000082036124a2576002546040516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa2856040516124dd91815260200190565b60405180910390a261251a6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016873087612ae8565b6002600f5460ff16600381111561253357612533612ffd565b1480156125485750600f54610100900460ff16155b15612555576125556125e7565b505050505050565b6000600f5460ff16600381111561257657612576612ffd565b1461259b57600f5460405163ad88fc8f60e01b81526106ad9160ff1690600401613013565b61271061ffff821611156125cf57604051624ae8fd60e41b815261ffff8216600482015261271060248201526044016106ad565b6005805461ffff191661ffff92909216919091179055565b6002600f5460ff16600381111561260057612600612ffd565b1461262557600f546040516383696f6f60e01b81526106ad9160ff1690600401613013565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b0381111561267b5761267b612d34565b6040519080825280602002602001820160405280156126b457816020015b6126a1612ccf565b8152602001906001900390816126995790505b50905060005b8281101561274f576000600c82815481106126d7576126d76133f7565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061273b5761273b6133f7565b6020908102919091010152506001016126ba565b5060405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906127de907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090600401613487565b6020604051808303816000875af11580156127fd573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061282191906134a0565b50604051632f1fbfb360e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bc7efecc90612877906000906006906002908790600401613504565b600060405180830381600087803b15801561289157600080fd5b505af1158015612555573d6000803e3d6000fd5b600f80549115156101000261ff0019909216919091179055565b6000600f5460ff1660038111156128d8576128d8612ffd565b146128fd57600f54604051639a0293fd60e01b81526106ad9160ff1690600401613013565b60405160016226579360e01b031981526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063ffd9a86d9061297290889088907f000000000000000000000000000000000000000000000000000000000000000090899060040161357b565b600060405180830381600087803b15801561298c57600080fd5b505af11580156129a0573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6111da83846001600160a01b031663a9059cbb8585604051602401612a00929190613487565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050612b21565b612a3a611a29565b612a5387878760000151886020015189604001516128bf565b612a60856060015161255d565b612a69846114e3565b612a72836128a5565b80156114da576114da7f00000000000000000000000000000000000000000000000000000000000000008383611d49565b60006001600160a01b03821615612aba5781610e02565b5090919050565b6001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b6040516001600160a01b0384811660248301528381166044830152606482018390526118a29186918216906323b872dd90608401612a00565b6000612b366001600160a01b03841683612b7b565b90508051600014158015612b5b575080806020019051810190612b5991906134a0565b155b156111da5782604051635274afe760e01b81526004016106ad9190613094565b6060610e028383600084600080856001600160a01b03168486604051612ba191906135b6565b60006040518083038185875af1925050503d8060008114612bde576040519150601f19603f3d011682016040523d82523d6000602084013e612be3565b606091505b5091509150612bf3868383612bfd565b9695505050505050565b606082612c1257612c0d82612c50565b610e02565b8151158015612c2957506001600160a01b0384163b155b15612c495783604051639996b31560e01b81526004016106ad9190613094565b5080610e02565b805115612c605780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b604080518082019091526000808252602082015290565b50805460008255906000526020600020908101906106bf9190612cef565b50805460008255600202906000526020600020908101906106bf9190612d08565b6040518060400160405280612ce2612c79565b8152602001600081525090565b5b80821115612d045760008155600101612cf0565b5090565b5b80821115612d045780546001600160a01b031990811682556001820180549091169055600201612d09565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715612d6c57612d6c612d34565b60405290565b604051608081016001600160401b0381118282101715612d6c57612d6c612d34565b604051601f8201601f191681016001600160401b0381118282101715612dbc57612dbc612d34565b604052919050565b80356001600160a01b0381168114612ddb57600080fd5b919050565b600082601f830112612df157600080fd5b81356001600160401b03811115612e0a57612e0a612d34565b612e1960208260051b01612d94565b8082825260208201915060208360061b860101925085831115612e3b57600080fd5b602085015b83811015612e855760408188031215612e5857600080fd5b612e60612d4a565b612e6982612dc4565b8152602082810135818301529084529290920191604001612e40565b5095945050505050565b600060208284031215612ea157600080fd5b81356001600160401b03811115612eb757600080fd5b612ec384828501612de0565b949350505050565b600060208284031215612edd57600080fd5b610e0282612dc4565b600081518084526020840193506020830160005b82811015612f215781516001600160a01b0316865260209586019590910190600101612efa565b5093949350505050565b600081518084526020840193506020830160005b82811015612f21578151865260209586019590910190600101612f3f565b606081526000612f706060830186612ee6565b8281036020840152612f828186612f2b565b83810360408501528451808252602080870193509091019060005b81811015612fbd5783511515835260209384019390920191600101612f9d565b5090979650505050505050565b600060208284031215612fdc57600080fd5b5035919050565b6001600160a01b0392831681529116602082015260400190565b634e487b7160e01b600052602160045260246000fd5b602081016004831061303557634e487b7160e01b600052602160045260246000fd5b91905290565b6000806040838503121561304e57600080fd5b8235915061305e60208401612dc4565b90509250929050565b803561ffff81168114612ddb57600080fd5b60006020828403121561308b57600080fd5b610e0282613067565b6001600160a01b0391909116815260200190565b918252602082015260400190565b805182526020810151602083015260408101516040830152606081015160608301525050565b60808101610fc882846130b6565b80151581146106bf57600080fd5b8035612ddb816130ea565b600080600080600060a0868803121561311b57600080fd5b61312486613067565b945060208601356001600160401b0381111561313f57600080fd5b61314b88828901612de0565b945050604086013561315c816130ea565b925061316a60608701612dc4565b949793965091946080013592915050565b60006040828403121561318d57600080fd5b613195612d4a565b823581526020928301359281019290925250919050565b6000608082840312156131be57600080fd5b6131c6612d72565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b6000806000806000610120868803121561320a57600080fd5b613214878761317b565b945061322387604088016131ac565b949794965050505060c08301359260e081013592610100909101359150565b60006020828403121561325457600080fd5b8135610e02816130ea565b60008060006060848603121561327457600080fd5b505081359360208301359350604090920135919050565b80516001600160a01b03908116835260209182015116910152565b60408101610fc8828461328b565b6060815260006132c76060830186612ee6565b82810360208401526132d98186612ee6565b90508281036040840152612bf38185612f2b565b60008060008060008060008789036101c081121561330a57600080fd5b6133148a8a61317b565b97506133238a60408b016131ac565b9650608060bf198201121561333757600080fd5b50613340612d72565b60c0890135815260e0890135602082015261010089013560408201526133696101208a01613067565b606082015294506101408801356001600160401b0381111561338a57600080fd5b6133968a828b01612de0565b9450506133a661016089016130f8565b92506133b56101808901612dc4565b9699959850939692959194919350506101a09091013590565b634e487b7160e01b600052601160045260246000fd5b81810381811115610fc857610fc86133ce565b634e487b7160e01b600052603260045260246000fd5b80820180821115610fc857610fc86133ce565b60008261343d57634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561345457600080fd5b5051919050565b9283526020830191909152604082015260600190565b634e487b7160e01b600052603160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000602082840312156134b257600080fd5b8151610e02816130ea565b600081518084526020840193506020830160005b82811015612f215781516134e687825161328b565b602090810151604088015260609096019591909101906001016134d1565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000612bf36101608301846134bd565b8451815260208086015190820152610100810161359b60408301866130b6565b6001600160a01b039390931660c082015260e0015292915050565b6000825160005b818110156135d757602081860181015185830152016135bd565b50600092019182525091905056fea164736f6c634300081a000aa164736f6c634300081a000a", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100af5760003560e01c806333623794146100b45780633f4ba83a146100dd5780635c975abb146100e7578063715018a6146100ff57806379ba5097146101075780638456cb591461010f5780638da5cb5b146101175780638faff8621461011f578063c4d66de814610132578063c70242ad14610145578063e30c397814610168578063f2fde38b14610170578063f7bc39bf14610183575b600080fd5b6000546100c7906001600160a01b031681565b6040516100d49190610809565b60405180910390f35b6100e56101af565b005b6100ef6101c1565b60405190151581526020016100d4565b6100e56101d6565b6100e56101e8565b6100e5610230565b6100c7610240565b6100c761012d36600461084a565b61025b565b6100e5610140366004610925565b610387565b6100ef610153366004610925565b60016020526000908152604090205460ff1681565b6100c76104a3565b6100e561017e366004610925565b6104ae565b6100ef610191366004610925565b6001600160a01b031660009081526001602052604090205460ff1690565b6101b761051f565b6101bf610551565b565b6000806101cc6105a8565b5460ff1692915050565b6101de61051f565b6101bf60006105cc565b33806101f26104a3565b6001600160a01b031614610224578060405163118cdaa760e01b815260040161021b9190610809565b60405180910390fd5b61022d816105cc565b50565b61023861051f565b6101bf6105f3565b60008061024b61063a565b546001600160a01b031692915050565b600061026561065e565b60008054604080516386fa506360e01b815290516001600160a01b039092169182916386fa50639160048083019260209291908290030181865afa1580156102b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102d59190610947565b8989898989896040516102e7906107fc565b6102f89897969594939291906109eb565b604051809103906000f080158015610314573d6000803e3d6000fd5b506001600160a01b038116600081815260016020819052604091829020805460ff1916909117905551919350839250907feac84630ba02e5ab324a651281c90ec45563a21f07fdf52b6f601f312e2de27a90610374908935815260200190565b60405180910390a2509695505050505050565b6000610391610684565b805490915060ff600160401b82041615906001600160401b03166000811580156103b85750825b90506000826001600160401b031660011480156103d45750303b155b9050811580156103e2575080155b156104005760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b0319166001178555831561042957845460ff60401b1916600160401b1785555b600080546001600160a01b0319166001600160a01b03881617905561044d336106a8565b6104556106b9565b831561049b57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b60008061024b6106c9565b6104b661051f565b60006104c06106c9565b80546001600160a01b0319166001600160a01b03841690811782559091506104e6610240565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b33610528610240565b6001600160a01b0316146101bf573360405163118cdaa760e01b815260040161021b9190610809565b6105596106ed565b60006105636105a8565b805460ff1916815590507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b60405161059d9190610809565b60405180910390a150565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330090565b60006105d66106c9565b80546001600160a01b031916815590506105ef82610712565b5050565b6105fb61065e565b60006106056105a8565b805460ff1916600117815590507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586105903390565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b6106666101c1565b156101bf5760405163d93c066560e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6106b061076e565b61022d81610793565b6106c161076e565b6101bf6107c5565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b6106f56101c1565b6101bf57604051638dfc202b60e01b815260040160405180910390fd5b600061071c61063a565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b6107766107e2565b6101bf57604051631afcd79f60e31b815260040160405180910390fd5b61079b61076e565b6001600160a01b038116610224576000604051631e4fbdf760e01b815260040161021b9190610809565b6107cd61076e565b60006107d76105a8565b805460ff1916905550565b60006107ec610684565b54600160401b900460ff16919050565b61543d80610a7883390190565b6001600160a01b0391909116815260200190565b60006080828403121561082f57600080fd5b50919050565b8035801515811461084557600080fd5b919050565b60008060008060008086880361018081121561086557600080fd5b604081121561087357600080fd5b50869550610884886040890161081d565b94506108938860c0890161081d565b93506101408701356001600160401b038111156108af57600080fd5b8701601f810189136108c057600080fd5b80356001600160401b038111156108d657600080fd5b8960208260061b84010111156108eb57600080fd5b602091909101935091506109026101608801610835565b90509295509295509295565b80356001600160a01b038116811461084557600080fd5b60006020828403121561093757600080fd5b6109408261090e565b9392505050565b60006020828403121561095957600080fd5b5051919050565b803582526020808201359083015260408082013590830152606081013561ffff811680821461098e57600080fd5b80606085015250505050565b81835260208301925060008160005b848110156109e1576001600160a01b036109c28361090e565b16865260208281013590870152604095860195909101906001016109a9565b5093949350505050565b6001600160a01b03891681526020808201899052873560408084019190915288820135606080850191909152883560808501529188013560a084015287013560c083015286013560e08201526000610a47610100830187610960565b6101c0610180830152610a5f6101c08301858761099a565b8315156101a08401529050999850505050505050505056fe610120604052600f805460ff1916905534801561001b57600080fd5b5060405161543d38038061543d83398101604081905261003a9161196c565b866001600160a01b0381166100a45760405162461bcd60e51b815260206004820152602560248201527f5368617265643a205a65726f2d61646472657373206973206e6f74207065726d6044820152641a5d1d195960da1b60648201526084015b60405180910390fd5b86806000036100f55760405162461bcd60e51b815260206004820152601b60248201527f5368617265643a2075696e7420696e70757420697320656d7074790000000000604482015260640161009b565b6001600160a01b03891660a081905260408051630ada733d60e31b815290516356d399e8916004808201926020929091908290030181865afa15801561013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101639190611a30565b60c0818152505060a0516001600160a01b0316637c89d2f06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ce9190611a49565b6001600160a01b03166080526101008890523260e05260006101f588888888888680610204565b50505050505050505050611c7f565b61020c61026c565b845160208601516040870151610225928a928a92610398565b6060850151610233906104ad565b61023c84610537565b600f805461ff0019166101008515150217905580156102635760e051610263908383610859565b50505050505050565b600c5460005b81811015610334576000600c828154811061028f5761028f611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff1660038111156102d5576102d5611751565b141580156102e35750600081115b156102ff576080516102ff906001600160a01b03168383610e71565b61032a826001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b5050600101610272565b50610341600c60006116cd565b600f805460ff191690556040805160008082526020820190925281610388565b60408051808201909152600080825260208201528152602001906001900390816103615790505b50905061039481610537565b5050565b6000600f5460ff1660038111156103b1576103b1611751565b146103d657600f54604051639a0293fd60e01b815261009b9160ff1690600401611a7c565b60a05160e0516040805160016226579360e01b03198152885160048201526020808a01516024830152885160448301528801516064820152908701516084820152606087015160a48201526001600160a01b0391821660c482015260e4810186905291169063ffd9a86d9061010401600060405180830381600087803b15801561045f57600080fd5b505af1158015610473573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6000600f5460ff1660038111156104c6576104c6611751565b146104eb57600f5460405163ad88fc8f60e01b815261009b9160ff1690600401611a7c565b61271061ffff8216111561051f57604051624ae8fd60e41b815261ffff82166004820152612710602482015260440161009b565b6005805461ffff191661ffff92909216919091179055565b6000600f5460ff16600381111561055057610550611751565b1461057557600f546040516312ba63f960e11b815261009b9160ff1690600401611a7c565b600e5460005b818110156105d457600d6000600e838154811061059a5761059a611a66565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff191690550161057b565b506105e1600e60006116ee565b5060c051610100518251111561061957815161010051604051630962235760e31b81526004810192909252602482015260440161009b565b815160005b8181101561085357806000036106955760e0516001600160a01b031684828151811061064c5761064c611a66565b6020026020010151600001516001600160a01b0316146106955760e05160405163ef77b46760e01b8152600481018390526001600160a01b03909116602482015260440161009b565b60006001600160a01b03168482815181106106b2576106b2611a66565b6020026020010151600001516001600160a01b0316036106e85760405163621caef560e11b81526004810182905260240161009b565b6000600d600086848151811061070057610700611a66565b6020026020010151600001516001600160a01b03166001600160a01b0316815260200190815260200160002090508060000154600014610756576040516308308c2360e21b81526004810183905260240161009b565b600061076c858461010051610ece60201b60201c565b9050600086848151811061078257610782611a66565b6020026020010151602001519050818110156107b75783818360405163c4aa8aa360e01b815260040161009b93929190611aa4565b808610156107de57838187604051636d3ae3a760e01b815260040161009b93929190611aa4565b6107e88187611ad0565b9550600e8785815181106107fe576107fe611a66565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161061e565b50505050565b6000600f5460ff16600381111561087257610872611751565b1415801561089757506001600f5460ff16600381111561089457610894611751565b14155b156108bc57600f54604051630295f95f60e51b815261009b9160ff1690600401611a7c565b60a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108fc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109209190611a30565b61010051146109b3576101005160a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109919190611a30565b604051631248081b60e31b81526004810192909252602482015260440161009b565b60a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a179190611a30565b60c05114610a625760c05160a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b6000600f5460ff166003811115610a7b57610a7b611751565b03610b145760e0516001600160a01b0316836001600160a01b031614610ab95760e0516040516316a4ebc160e21b815261009b918591600401611ae3565b600f805460ff1916600117905560e05160025460055460405161ffff90911681526001600160a01b03909216917fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d60205260409020805415801590610b415750600181015460ff16155b15610b8a578054821015610b7557805460405163454cd8e160e01b815261009b918491600401918252602082015260400190565b6001818101805460ff19169091179055610be6565b6001600160a01b0384166000908152600a6020526040902054158015610bb65750610bb3610f65565b82105b15610be65781610bc4610f65565b6040516310e45ff160e21b81526004810192909252602482015260440161009b565b6001600160a01b0384166000908152600a60205260408120549003610cb7576000610c11858561102b565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8909101805491909316911617905550610cc1565b610cc18484611049565b6001600160a01b0384166000908152600a602052604081208054849290610ce9908490611afd565b90915550506001600160a01b0384166000908152600b60205260408120429055610d116111ca565b90506000610d1d611236565b60c051909150610d2d8284611afd565b1115610d5457818160c05160405163513b428b60e11b815260040161009b93929190611aa4565b61010051600c541115610d8157610100516040516315b5c3d560e21b815260040161009b91815260200190565b60c0518203610dd35760e0516002546040516001600160a01b03909216917f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa285604051610e0e91815260200190565b60405180910390a2608051610e2e906001600160a01b03168730876112a8565b6002600f5460ff166003811115610e4757610e47611751565b148015610e5c5750600f54610100900460ff16155b15610e6957610e696112e1565b505050505050565b610ec983846001600160a01b031663a9059cbb8585604051602401610e97929190611b10565b60408051808303601f1901815291905260208101805160e09390931b6001600160e01b03938416179052915061154216565b505050565b6000828211610efa576040516376675ed960e11b8152600481018490526024810183905260440161009b565b82600003610f2b576004610f0f600186611ad0565b610f199190611b29565b610f24906001611afd565b9050610f5e565b6000610f378484611ad0565b905080610f45600187611ad0565b610f4f9190611b29565b610f5a906001611afd565b9150505b9392505050565b600e5460009081908190815b81811015610fe8576000600e8281548110610f8e57610f8e611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610fde578054610fce9087611afd565b9550610fdb600186611afd565b94505b5050600101610f71565b5061102383610ff56111ca565b60c0516110029190611ad0565b61100c9190611ad0565b600c5461101a908590611afd565b61010051610ece565b935050505090565b60006001600160a01b038216156110425781610f5e565b5090919050565b6001600f5460ff16600381111561106257611062611751565b1415801561108757506002600f5460ff16600381111561108457611084611751565b14155b156110ac576002546040516310004b0160e11b8152600481019190915260240161009b565b60006110b8838361102b565b600c549091506000908190815b8181101561115e576000600c82815481106110e2576110e2611a66565b6000918252602090912060029091020180549091506001600160a01b03808a169116036111555760018101546001600160a01b0380881691160361112a575050505050505050565b600190810180546001600160a01b038881166001600160a01b0319831617909255169450925061115e565b506001016110c5565b508161117f5785604051632fd768d360e11b815260040161009b9190611b4b565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe7284866040516111ba929190611ae3565b60405180910390a2505050505050565b600c54600090815b81811015611231576000600c82815481106111ef576111ef611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506112269085611afd565b9350506001016111d2565b505090565b600e54600090815b81811015611231576000600e828154811061125b5761125b611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff1661129e57805461129b9086611afd565b94505b505060010161123e565b6040516001600160a01b0384811660248301528381166044830152606482018390526108539186918216906323b872dd90608401610e97565b6002600f5460ff1660038111156112fa576112fa611751565b1461131f57600f546040516383696f6f60e01b815261009b9160ff1690600401611a7c565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b038111156113755761137561177c565b6040519080825280602002602001820160405280156113c957816020015b60408051608081018252600091810182815260608201839052815260208101919091528152602001906001900390816113935790505b50905060005b82811015611464576000600c82815481106113ec576113ec611a66565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061145057611450611a66565b6020908102919091010152506001016113cf565b506080516001600160a01b031663095ea7b360a05160c0516040518363ffffffff1660e01b8152600401611499929190611b10565b6020604051808303816000875af11580156114b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114dc9190611b5f565b5060a0516001600160a01b031663bc7efecc600060066002856040518563ffffffff1660e01b81526004016115149493929190611bd9565b600060405180830381600087803b15801561152e57600080fd5b505af1158015610e69573d6000803e3d6000fd5b60006115576001600160a01b0384168361159c565b9050805160001415801561157c57508080602001905181019061157a9190611b5f565b155b15610ec95782604051635274afe760e01b815260040161009b9190611b4b565b6060610f5e838360006115b0565b92915050565b6060814710156115d5573060405163cd78605960e01b815260040161009b9190611b4b565b600080856001600160a01b031684866040516115f19190611c50565b60006040518083038185875af1925050503d806000811461162e576040519150601f19603f3d011682016040523d82523d6000602084013e611633565b606091505b50909250905061164486838361164e565b9695505050505050565b6060826116635761165e826116a1565b610f5e565b815115801561167a57506001600160a01b0384163b155b1561169a5783604051639996b31560e01b815260040161009b9190611b4b565b5080610f5e565b8051156116b15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b50805460008255600202906000526020600020908101906116ca919061170c565b50805460008255906000526020600020908101906116ca919061173c565b5b808211156117385780546001600160a01b03199081168255600182018054909116905560020161170d565b5090565b5b80821115611738576000815560010161173d565b634e487b7160e01b600052602160045260246000fd5b6001600160a01b03811681146116ca57600080fd5b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b03811182821017156117b4576117b461177c565b60405290565b604080519081016001600160401b03811182821017156117b4576117b461177c565b604051601f8201601f191681016001600160401b03811182821017156118045761180461177c565b604052919050565b60006080828403121561181e57600080fd5b611826611792565b8251815260208084015190820152604080840151908201526060928301519281019290925250919050565b60006080828403121561186357600080fd5b61186b611792565b825181526020808401519082015260408084015190820152606083015190915061ffff8116811461189b57600080fd5b606082015292915050565b600082601f8301126118b757600080fd5b81516001600160401b038111156118d0576118d061177c565b6118df60208260051b016117dc565b8082825260208201915060208360061b86010192508583111561190157600080fd5b602085015b8381101561194d576040818803121561191e57600080fd5b6119266117ba565b815161193181611767565b8152602082810151818301529084529290920191604001611906565b5095945050505050565b8051801515811461196757600080fd5b919050565b60008060008060008060008789036101c081121561198957600080fd5b885161199481611767565b60208a015190985096506040603f19820112156119b057600080fd5b506119b96117ba565b604089015181526060890151602082015294506119d98960808a0161180c565b93506119e9896101008a01611851565b6101808901519093506001600160401b03811115611a0657600080fd5b611a128a828b016118a6565b925050611a226101a08901611957565b905092959891949750929550565b600060208284031215611a4257600080fd5b5051919050565b600060208284031215611a5b57600080fd5b8151610f5e81611767565b634e487b7160e01b600052603260045260246000fd5b6020810160048310611a9e57634e487b7160e01b600052602160045260246000fd5b91905290565b9283526020830191909152604082015260600190565b634e487b7160e01b600052601160045260246000fd5b818103818111156115aa576115aa611aba565b6001600160a01b0392831681529116602082015260400190565b808201808211156115aa576115aa611aba565b6001600160a01b03929092168252602082015260400190565b600082611b4657634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b0391909116815260200190565b600060208284031215611b7157600080fd5b610f5e82611957565b600081518084526020840193506020830160005b82811015611bcf578151805180516001600160a01b039081168952602091820151168189015290810151604088015260609096019590910190600101611b8e565b5093949350505050565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000611644610160830184611b7a565b6000825160005b81811015611c715760208186018101518583015201611c57565b506000920191825250919050565b60805160a05160c05160e051610100516135f2611e4b6000396000818161052c01528181610f36015281816115b4015281816115dd0152818161179701528181611e3001528181611e56015281816123b501526123e90152600081816104660152818161064b01528181610673015281816106d601528181610a2801528181610a5001528181610afe01528181610b2601528181610bcb01528181610bf301528181610c5b01528181610c9301528181610cbb01528181610d0d01528181610d350152818161104b01528181611073015281816111b5015281816112140152818161123c01528181611469015281816114910152818161162e015281816116a00152818161205901528181612093015281816120f50152818161244c015281816129480152612a7d01526000818161043f01528181610ef80152818161159201528181611f9601528181611fbc01528181612342015281816123750152818161241701526127b601526000818161034a01528181611dae01528181611e7701528181611f1401528181611fdd015281816127940152818161283901526129170152600081816104a001528181611ab201528181611cef015281816124f2015261276701526135f26000f3fe608060405234801561001057600080fd5b50600436106101e25760003560e01c8062e9a407146101e75780630aaffd2a146101fc5780630d616d201461020f5780630d9639ba146102175780630dcf4b8f146102375780630ebb172a1461024d57806312fa329b1461026f578063200d2ed2146102905780632816ee73146102aa5780632c6cda93146102bd5780632c7baaf3146102d0578063313f336b1461030f5780633362379414610345578063356c299d1461037957806342e94c90146103b75780634564168b146103d75780634a387cdf146103f35780634bb278f3146103fb5780634e7320ce14610403578063565a9d481461041857806356d399e81461043a578063570ca7351461046157806358b810d31461048857806360e52ecb1461049b5780636786452c146104c25780636cf72aa4146104d55780637c0b4bfb146104e857806386fa5063146105275780638c7e7a311461054e5780638dd0855714610561578063937e09b11461058157806396a608c0146105895780639ca002a614610591578063a4b6aa83146105a4578063bc063e1a146105ac578063ccec3716146105c8578063d26146a5146105db578063d826f88f146105ee578063dda0a81a146105f6578063e020e4c41461060d578063e50d50d814610620575b600080fd5b6101fa6101f5366004612e8f565b610640565b005b6101fa61020a366004612ecb565b6106c2565b6101fa6106cc565b61021f6107b7565b60405161022e93929190612f5d565b60405180910390f35b61023f61096d565b60405190815260200161022e565b6102576201518081565b6040516001600160401b03909116815260200161022e565b61028261027d366004612fca565b6109d9565b60405161022e929190612fe3565b600f5461029d9060ff1681565b60405161022e9190613013565b6101fa6102b836600461303b565b610a12565b6101fa6102cb366004613079565b610a1d565b6102d8610a93565b60405161022e919081518152602080830151908201526040808301519082015260609182015161ffff169181019190915260800190565b6006546007546008546009546103259392919084565b60408051948552602085019390935291830152606082015260800161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b60405161022e9190613094565b6002546003546004546005546103939392919061ffff1684565b6040805194855260208501939093529183015261ffff16606082015260800161022e565b61023f6103c5366004612ecb565b600a6020526000908152604090205481565b6000546001546103e5919082565b60405161022e9291906130a8565b600c5461023f565b6101fa610af3565b61040b610b68565b60405161022e91906130dc565b600f5461042a90610100900460ff1681565b604051901515815260200161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa610496366004613103565b610bc0565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa6104d03660046131f1565b610c88565b6101fa6104e3366004613242565b610d02565b6105126104f6366004612ecb565b600d602052600090815260409020805460019091015460ff1682565b6040805192835290151560208301520161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61023f61055c36600461325f565b610d78565b61057461056f366004612fca565b610e09565b60405161022e91906132a6565b61023f610e62565b61023f610f62565b61023f61059f366004612fca565b610fb9565b61023f610fce565b6105b561271081565b60405161ffff909116815260200161022e565b6101fa6105d6366004612ecb565b611040565b61036c6105e9366004612fca565b6111df565b6101fa611209565b6105fe61127e565b60405161022e939291906132b4565b6101fa61061b3660046132ed565b61145e565b61023f61062e366004612ecb565b600b6020526000908152604090205481565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106b657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b60405180910390fd5b6106bf816114e3565b50565b6106bf33826118a8565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016330361070657610704611a29565b565b336000908152600b602052604081205461072090426133e4565b90506201518081101561076a57336000908152600b602052604090819020549051630429b06960e31b815260048101919091524260248201526201518060448201526064016106ad565b600061077533611b4c565b905080156107b35760405181815233907f249a82fbe5056a1940b4e996665ba2a82c340ae9fa1e069fd1ababf5508f396e9060200160405180910390a25b5050565b600e5460609081908190806001600160401b038111156107d9576107d9612d34565b604051908082528060200260200182016040528015610802578160200160208202803683370190505b509350806001600160401b0381111561081d5761081d612d34565b604051908082528060200260200182016040528015610846578160200160208202803683370190505b509250806001600160401b0381111561086157610861612d34565b60405190808252806020026020018201604052801561088a578160200160208202803683370190505b50915060005b81811015610966576000600e82815481106108ad576108ad6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912087519192509082908890859081106108eb576108eb6133f7565b60200260200101906001600160a01b031690816001600160a01b0316815250508060000154868481518110610922576109226133f7565b60209081029190910101526001810154855160ff9091169086908590811061094c5761094c6133f7565b911515602092830291909101909101525050600101610890565b5050909192565b600c54600090815b818110156109d4576000600c8281548110610992576109926133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506109c9908561340d565b935050600101610975565b505090565b600c81815481106109e957600080fd5b6000918252602090912060029091020180546001909101546001600160a01b0391821692501682565b6107b3338284611d49565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a8a57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf8161255d565b610ac26040518060800160405280600081526020016000815260200160008152602001600061ffff1681525090565b5060408051608081018252600254815260035460208201526004549181019190915260055461ffff16606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b6057337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6107046125e7565b610b936040518060800160405280600081526020016000815260200160008152602001600081525090565b50604080516080810182526006548152600754602082015260085491810191909152600954606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610c2d57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c35611a29565b610c3e8561255d565b610c47846114e3565b610c50836128a5565b8015610c8157610c817f00000000000000000000000000000000000000000000000000000000000000008383611d49565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cf557337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c8185858585856128bf565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610d6f57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf816128a5565b6000828211610d9e5782826040516376675ed960e11b81526004016106ad9291906130a8565b82600003610dcf576004610db36001866133e4565b610dbd9190613420565b610dc890600161340d565b9050610e02565b6000610ddb84846133e4565b905080610de96001876133e4565b610df39190613420565b610dfe90600161340d565b9150505b9392505050565b610e11612c79565b600c8281548110610e2457610e246133f7565b60009182526020918290206040805180820190915260029092020180546001600160a01b039081168352600190910154169181019190915292915050565b600e5460009081908190815b81811015610ee5576000600e8281548110610e8b57610e8b6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610edb578054610ecb908761340d565b9550610ed860018661340d565b94505b5050600101610e6e565b50610f5a83610ef261096d565b610f1c907f00000000000000000000000000000000000000000000000000000000000000006133e4565b610f2691906133e4565b600c54610f3490859061340d565b7f0000000000000000000000000000000000000000000000000000000000000000610d78565b935050505090565b600c54600090610f725750600090565b600a6000600c600081548110610f8a57610f8a6133f7565b600091825260208083206002909202909101546001600160a01b03168352820192909252604001902054905090565b6000610fc88260006001610d78565b92915050565b600e54600090815b818110156109d4576000600e8281548110610ff357610ff36133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16611036578054611033908661340d565b94505b5050600101610fd6565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110ad57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6003600f5460ff1660038111156110c6576110c6612ffd565b141580156110ea57506000600f5460ff1660038111156110e8576110e8612ffd565b145b1561110f57600f54604051632f3e69dd60e01b81526106ad9160ff1690600401613013565b6040516370a0823160e01b815281906000906001600160a01b038316906370a0823190611140903090600401613094565b602060405180830381865afa15801561115d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111819190613442565b9050600081116111a6578260405163e932c57360e01b81526004016106ad9190613094565b6111da6001600160a01b0383167f0000000000000000000000000000000000000000000000000000000000000000836129da565b505050565b600e81815481106111ef57600080fd5b6000918252602090912001546001600160a01b0316905081565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461127657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610704611a29565b600c5460609081908190806001600160401b038111156112a0576112a0612d34565b6040519080825280602002602001820160405280156112c9578160200160208202803683370190505b509350806001600160401b038111156112e4576112e4612d34565b60405190808252806020026020018201604052801561130d578160200160208202803683370190505b509250806001600160401b0381111561132857611328612d34565b604051908082528060200260200182016040528015611351578160200160208202803683370190505b50915060005b81811015610966576000600c8281548110611374576113746133f7565b60009182526020909120600290910201805487519192506001600160a01b0316908790849081106113a7576113a76133f7565b6001600160a01b039283166020918202929092010152600182015486519116908690849081106113d9576113d96133f7565b60200260200101906001600160a01b031690816001600160a01b031681525050600a600087848151811061140f5761140f6133f7565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020016000205484838151811061144a5761144a6133f7565b602090810291909101015250600101611357565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114cb57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6114da87878787878787612a32565b50505050505050565b6000600f5460ff1660038111156114fc576114fc612ffd565b1461152157600f546040516312ba63f960e11b81526106ad9160ff1690600401613013565b600e5460005b8181101561158057600d6000600e8381548110611546576115466133f7565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff1916905501611527565b5061158d600e6000612c90565b5080517f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000010156116175781517f0000000000000000000000000000000000000000000000000000000000000000604051630962235760e31b81526004016106ad9291906130a8565b815160005b818110156118a257806000036116cd577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316848281518110611668576116686133f7565b6020026020010151600001516001600160a01b0316146116cd5760405163ef77b46760e01b8152600481018290526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660248201526044016106ad565b60006001600160a01b03168482815181106116ea576116ea6133f7565b6020026020010151600001516001600160a01b0316036117205760405163621caef560e11b8152600481018290526024016106ad565b6000600d6000868481518110611738576117386133f7565b6020026020010151600001516001600160a01b03166001600160a01b031681526020019081526020016000209050806000015460001461178e576040516308308c2360e21b8152600481018390526024016106ad565b60006117bb85847f0000000000000000000000000000000000000000000000000000000000000000610d78565b905060008684815181106117d1576117d16133f7565b6020026020010151602001519050818110156118065783818360405163c4aa8aa360e01b81526004016106ad9392919061345b565b8086101561182d57838187604051636d3ae3a760e01b81526004016106ad9392919061345b565b61183781876133e4565b9550600e87858151811061184d5761184d6133f7565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161161c565b50505050565b6001600f5460ff1660038111156118c1576118c1612ffd565b141580156118e657506002600f5460ff1660038111156118e3576118e3612ffd565b14155b1561190b576002546040516310004b0160e11b815260048101919091526024016106ad565b60006119178383612aa3565b600c549091506000908190815b818110156119bd576000600c8281548110611941576119416133f7565b6000918252602090912060029091020180549091506001600160a01b03808a169116036119b45760018101546001600160a01b03808816911603611989575050505050505050565b600190810180546001600160a01b038881166001600160a01b031983161790925516945092506119bd565b50600101611924565b50816119de5785604051632fd768d360e11b81526004016106ad9190613094565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe728486604051611a19929190612fe3565b60405180910390a2505050505050565b600c5460005b81811015611aec576000600c8281548110611a4c57611a4c6133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff166003811115611a9257611a92612ffd565b14158015611aa05750600081115b15611ad957611ad96001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001683836129da565b611ae282612ac1565b5050600101611a2f565b50611af9600c6000612cae565b600f805460ff191690556040805160008082526020820190925281611b40565b6040805180820190915260008082526020820152815260200190600190039081611b195790505b5090506107b3816114e3565b6001600160a01b0381166000908152600a602052604081205490819003611b7257919050565b611b7b82612ac1565b600c5460005b81811015611c9657600c8181548110611b9c57611b9c6133f7565b60009182526020909120600290910201546001600160a01b0390811690851603611c8e57600c611bcd6001846133e4565b81548110611bdd57611bdd6133f7565b9060005260206000209060020201600c8281548110611bfe57611bfe6133f7565b60009182526020909120825460029092020180546001600160a01b039283166001600160a01b0319918216178255600193840154939091018054939092169216919091179055600c805480611c5557611c55613471565b60008281526020902060026000199092019182020180546001600160a01b03199081168255600191909101805490911690559055611c96565b600101611b81565b506001600160a01b0383166000908152600d6020526040902060018101805460ff191690556003600f5460ff166003811115611cd457611cd4612ffd565b03611ce25760009250611d42565b611d166001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001685856129da565b6002600f5460ff166003811115611d2f57611d2f612ffd565b03611d4257600f805460ff191660011790555b5050919050565b6000600f5460ff166003811115611d6257611d62612ffd565b14158015611d8757506001600f5460ff166003811115611d8457611d84612ffd565b14155b15611dac57600f54604051630295f95f60e51b81526106ad9160ff1690600401613013565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e2e9190613442565b7f000000000000000000000000000000000000000000000000000000000000000014611f12577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ef79190613442565b604051631248081b60e31b81526004016106ad9291906130a8565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f949190613442565b7f000000000000000000000000000000000000000000000000000000000000000014612039577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b6000600f5460ff16600381111561205257612052612ffd565b03612145577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316146120cd57827f00000000000000000000000000000000000000000000000000000000000000006040516316a4ebc160e21b81526004016106ad929190612fe3565b600f805460ff1916600117905560025460055460405161ffff90911681526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d602052604090208054158015906121725750600181015460ff16155b156121b257805482101561219d57805460405163454cd8e160e01b81526106ad9184916004016130a8565b6001818101805460ff19169091179055612207565b6001600160a01b0384166000908152600a60205260409020541580156121de57506121db610e62565b82105b1561220757816121ec610e62565b6040516310e45ff160e21b81526004016106ad9291906130a8565b6001600160a01b0384166000908152600a602052604081205490036122d85760006122328585612aa3565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c89091018054919093169116179055506122e2565b6122e284846118a8565b6001600160a01b0384166000908152600a60205260408120805484929061230a90849061340d565b90915550506001600160a01b0384166000908152600b6020526040812042905561233261096d565b9050600061233e610fce565b90507f000000000000000000000000000000000000000000000000000000000000000061236b828461340d565b11156123b05781817f000000000000000000000000000000000000000000000000000000000000000060405163513b428b60e11b81526004016106ad9392919061345b565b600c547f00000000000000000000000000000000000000000000000000000000000000001015612415576040516315b5c3d560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201526024016106ad565b7f000000000000000000000000000000000000000000000000000000000000000082036124a2576002546040516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa2856040516124dd91815260200190565b60405180910390a261251a6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016873087612ae8565b6002600f5460ff16600381111561253357612533612ffd565b1480156125485750600f54610100900460ff16155b15612555576125556125e7565b505050505050565b6000600f5460ff16600381111561257657612576612ffd565b1461259b57600f5460405163ad88fc8f60e01b81526106ad9160ff1690600401613013565b61271061ffff821611156125cf57604051624ae8fd60e41b815261ffff8216600482015261271060248201526044016106ad565b6005805461ffff191661ffff92909216919091179055565b6002600f5460ff16600381111561260057612600612ffd565b1461262557600f546040516383696f6f60e01b81526106ad9160ff1690600401613013565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b0381111561267b5761267b612d34565b6040519080825280602002602001820160405280156126b457816020015b6126a1612ccf565b8152602001906001900390816126995790505b50905060005b8281101561274f576000600c82815481106126d7576126d76133f7565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061273b5761273b6133f7565b6020908102919091010152506001016126ba565b5060405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906127de907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090600401613487565b6020604051808303816000875af11580156127fd573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061282191906134a0565b50604051632f1fbfb360e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bc7efecc90612877906000906006906002908790600401613504565b600060405180830381600087803b15801561289157600080fd5b505af1158015612555573d6000803e3d6000fd5b600f80549115156101000261ff0019909216919091179055565b6000600f5460ff1660038111156128d8576128d8612ffd565b146128fd57600f54604051639a0293fd60e01b81526106ad9160ff1690600401613013565b60405160016226579360e01b031981526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063ffd9a86d9061297290889088907f000000000000000000000000000000000000000000000000000000000000000090899060040161357b565b600060405180830381600087803b15801561298c57600080fd5b505af11580156129a0573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6111da83846001600160a01b031663a9059cbb8585604051602401612a00929190613487565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050612b21565b612a3a611a29565b612a5387878760000151886020015189604001516128bf565b612a60856060015161255d565b612a69846114e3565b612a72836128a5565b80156114da576114da7f00000000000000000000000000000000000000000000000000000000000000008383611d49565b60006001600160a01b03821615612aba5781610e02565b5090919050565b6001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b6040516001600160a01b0384811660248301528381166044830152606482018390526118a29186918216906323b872dd90608401612a00565b6000612b366001600160a01b03841683612b7b565b90508051600014158015612b5b575080806020019051810190612b5991906134a0565b155b156111da5782604051635274afe760e01b81526004016106ad9190613094565b6060610e028383600084600080856001600160a01b03168486604051612ba191906135b6565b60006040518083038185875af1925050503d8060008114612bde576040519150601f19603f3d011682016040523d82523d6000602084013e612be3565b606091505b5091509150612bf3868383612bfd565b9695505050505050565b606082612c1257612c0d82612c50565b610e02565b8151158015612c2957506001600160a01b0384163b155b15612c495783604051639996b31560e01b81526004016106ad9190613094565b5080610e02565b805115612c605780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b604080518082019091526000808252602082015290565b50805460008255906000526020600020908101906106bf9190612cef565b50805460008255600202906000526020600020908101906106bf9190612d08565b6040518060400160405280612ce2612c79565b8152602001600081525090565b5b80821115612d045760008155600101612cf0565b5090565b5b80821115612d045780546001600160a01b031990811682556001820180549091169055600201612d09565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715612d6c57612d6c612d34565b60405290565b604051608081016001600160401b0381118282101715612d6c57612d6c612d34565b604051601f8201601f191681016001600160401b0381118282101715612dbc57612dbc612d34565b604052919050565b80356001600160a01b0381168114612ddb57600080fd5b919050565b600082601f830112612df157600080fd5b81356001600160401b03811115612e0a57612e0a612d34565b612e1960208260051b01612d94565b8082825260208201915060208360061b860101925085831115612e3b57600080fd5b602085015b83811015612e855760408188031215612e5857600080fd5b612e60612d4a565b612e6982612dc4565b8152602082810135818301529084529290920191604001612e40565b5095945050505050565b600060208284031215612ea157600080fd5b81356001600160401b03811115612eb757600080fd5b612ec384828501612de0565b949350505050565b600060208284031215612edd57600080fd5b610e0282612dc4565b600081518084526020840193506020830160005b82811015612f215781516001600160a01b0316865260209586019590910190600101612efa565b5093949350505050565b600081518084526020840193506020830160005b82811015612f21578151865260209586019590910190600101612f3f565b606081526000612f706060830186612ee6565b8281036020840152612f828186612f2b565b83810360408501528451808252602080870193509091019060005b81811015612fbd5783511515835260209384019390920191600101612f9d565b5090979650505050505050565b600060208284031215612fdc57600080fd5b5035919050565b6001600160a01b0392831681529116602082015260400190565b634e487b7160e01b600052602160045260246000fd5b602081016004831061303557634e487b7160e01b600052602160045260246000fd5b91905290565b6000806040838503121561304e57600080fd5b8235915061305e60208401612dc4565b90509250929050565b803561ffff81168114612ddb57600080fd5b60006020828403121561308b57600080fd5b610e0282613067565b6001600160a01b0391909116815260200190565b918252602082015260400190565b805182526020810151602083015260408101516040830152606081015160608301525050565b60808101610fc882846130b6565b80151581146106bf57600080fd5b8035612ddb816130ea565b600080600080600060a0868803121561311b57600080fd5b61312486613067565b945060208601356001600160401b0381111561313f57600080fd5b61314b88828901612de0565b945050604086013561315c816130ea565b925061316a60608701612dc4565b949793965091946080013592915050565b60006040828403121561318d57600080fd5b613195612d4a565b823581526020928301359281019290925250919050565b6000608082840312156131be57600080fd5b6131c6612d72565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b6000806000806000610120868803121561320a57600080fd5b613214878761317b565b945061322387604088016131ac565b949794965050505060c08301359260e081013592610100909101359150565b60006020828403121561325457600080fd5b8135610e02816130ea565b60008060006060848603121561327457600080fd5b505081359360208301359350604090920135919050565b80516001600160a01b03908116835260209182015116910152565b60408101610fc8828461328b565b6060815260006132c76060830186612ee6565b82810360208401526132d98186612ee6565b90508281036040840152612bf38185612f2b565b60008060008060008060008789036101c081121561330a57600080fd5b6133148a8a61317b565b97506133238a60408b016131ac565b9650608060bf198201121561333757600080fd5b50613340612d72565b60c0890135815260e0890135602082015261010089013560408201526133696101208a01613067565b606082015294506101408801356001600160401b0381111561338a57600080fd5b6133968a828b01612de0565b9450506133a661016089016130f8565b92506133b56101808901612dc4565b9699959850939692959194919350506101a09091013590565b634e487b7160e01b600052601160045260246000fd5b81810381811115610fc857610fc86133ce565b634e487b7160e01b600052603260045260246000fd5b80820180821115610fc857610fc86133ce565b60008261343d57634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561345457600080fd5b5051919050565b9283526020830191909152604082015260600190565b634e487b7160e01b600052603160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000602082840312156134b257600080fd5b8151610e02816130ea565b600081518084526020840193506020830160005b82811015612f215781516134e687825161328b565b602090810151604088015260609096019591909101906001016134d1565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000612bf36101608301846134bd565b8451815260208086015190820152610100810161359b60408301866130b6565b6001600160a01b039390931660c082015260e0015292915050565b6000825160005b818110156135d757602081860181015185830152016135bd565b50600092019182525091905056fea164736f6c634300081a000aa164736f6c634300081a000a", + "bytecode": "0x6080604052348015600f57600080fd5b50615ec18061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100af5760003560e01c806333623794146100b45780633f4ba83a146100dd5780635c975abb146100e7578063715018a6146100ff57806379ba5097146101075780638456cb591461010f5780638da5cb5b146101175780638faff8621461011f578063c4d66de814610132578063c70242ad14610145578063e30c397814610168578063f2fde38b14610170578063f7bc39bf14610183575b600080fd5b6000546100c7906001600160a01b031681565b6040516100d49190610809565b60405180910390f35b6100e56101af565b005b6100ef6101c1565b60405190151581526020016100d4565b6100e56101d6565b6100e56101e8565b6100e5610230565b6100c7610240565b6100c761012d36600461084a565b61025b565b6100e5610140366004610925565b610387565b6100ef610153366004610925565b60016020526000908152604090205460ff1681565b6100c76104a3565b6100e561017e366004610925565b6104ae565b6100ef610191366004610925565b6001600160a01b031660009081526001602052604090205460ff1690565b6101b761051f565b6101bf610551565b565b6000806101cc6105a8565b5460ff1692915050565b6101de61051f565b6101bf60006105cc565b33806101f26104a3565b6001600160a01b031614610224578060405163118cdaa760e01b815260040161021b9190610809565b60405180910390fd5b61022d816105cc565b50565b61023861051f565b6101bf6105f3565b60008061024b61063a565b546001600160a01b031692915050565b600061026561065e565b60008054604080516386fa506360e01b815290516001600160a01b039092169182916386fa50639160048083019260209291908290030181865afa1580156102b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102d59190610947565b8989898989896040516102e7906107fc565b6102f89897969594939291906109eb565b604051809103906000f080158015610314573d6000803e3d6000fd5b506001600160a01b038116600081815260016020819052604091829020805460ff1916909117905551919350839250907feac84630ba02e5ab324a651281c90ec45563a21f07fdf52b6f601f312e2de27a90610374908935815260200190565b60405180910390a2509695505050505050565b6000610391610684565b805490915060ff600160401b82041615906001600160401b03166000811580156103b85750825b90506000826001600160401b031660011480156103d45750303b155b9050811580156103e2575080155b156104005760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b0319166001178555831561042957845460ff60401b1916600160401b1785555b600080546001600160a01b0319166001600160a01b03881617905561044d336106a8565b6104556106b9565b831561049b57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b60008061024b6106c9565b6104b661051f565b60006104c06106c9565b80546001600160a01b0319166001600160a01b03841690811782559091506104e6610240565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b33610528610240565b6001600160a01b0316146101bf573360405163118cdaa760e01b815260040161021b9190610809565b6105596106ed565b60006105636105a8565b805460ff1916815590507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b60405161059d9190610809565b60405180910390a150565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330090565b60006105d66106c9565b80546001600160a01b031916815590506105ef82610712565b5050565b6105fb61065e565b60006106056105a8565b805460ff1916600117815590507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586105903390565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b6106666101c1565b156101bf5760405163d93c066560e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6106b061076e565b61022d81610793565b6106c161076e565b6101bf6107c5565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b6106f56101c1565b6101bf57604051638dfc202b60e01b815260040160405180910390fd5b600061071c61063a565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b6107766107e2565b6101bf57604051631afcd79f60e31b815260040160405180910390fd5b61079b61076e565b6001600160a01b038116610224576000604051631e4fbdf760e01b815260040161021b9190610809565b6107cd61076e565b60006107d76105a8565b805460ff1916905550565b60006107ec610684565b54600160401b900460ff16919050565b61543d80610a7883390190565b6001600160a01b0391909116815260200190565b60006080828403121561082f57600080fd5b50919050565b8035801515811461084557600080fd5b919050565b60008060008060008086880361018081121561086557600080fd5b604081121561087357600080fd5b50869550610884886040890161081d565b94506108938860c0890161081d565b93506101408701356001600160401b038111156108af57600080fd5b8701601f810189136108c057600080fd5b80356001600160401b038111156108d657600080fd5b8960208260061b84010111156108eb57600080fd5b602091909101935091506109026101608801610835565b90509295509295509295565b80356001600160a01b038116811461084557600080fd5b60006020828403121561093757600080fd5b6109408261090e565b9392505050565b60006020828403121561095957600080fd5b5051919050565b803582526020808201359083015260408082013590830152606081013561ffff811680821461098e57600080fd5b80606085015250505050565b81835260208301925060008160005b848110156109e1576001600160a01b036109c28361090e565b16865260208281013590870152604095860195909101906001016109a9565b5093949350505050565b6001600160a01b03891681526020808201899052873560408084019190915288820135606080850191909152883560808501529188013560a084015287013560c083015286013560e08201526000610a47610100830187610960565b6101c0610180830152610a5f6101c08301858761099a565b8315156101a08401529050999850505050505050505056fe610120604052600f805460ff1916905534801561001b57600080fd5b5060405161543d38038061543d83398101604081905261003a9161196c565b866001600160a01b0381166100a45760405162461bcd60e51b815260206004820152602560248201527f5368617265643a205a65726f2d61646472657373206973206e6f74207065726d6044820152641a5d1d195960da1b60648201526084015b60405180910390fd5b86806000036100f55760405162461bcd60e51b815260206004820152601b60248201527f5368617265643a2075696e7420696e70757420697320656d7074790000000000604482015260640161009b565b6001600160a01b03891660a081905260408051630ada733d60e31b815290516356d399e8916004808201926020929091908290030181865afa15801561013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101639190611a30565b60c0818152505060a0516001600160a01b0316637c89d2f06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ce9190611a49565b6001600160a01b03166080526101008890523260e05260006101f588888888888680610204565b50505050505050505050611c7f565b61020c61026c565b845160208601516040870151610225928a928a92610398565b6060850151610233906104ad565b61023c84610537565b600f805461ff0019166101008515150217905580156102635760e051610263908383610859565b50505050505050565b600c5460005b81811015610334576000600c828154811061028f5761028f611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff1660038111156102d5576102d5611751565b141580156102e35750600081115b156102ff576080516102ff906001600160a01b03168383610e71565b61032a826001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b5050600101610272565b50610341600c60006116cd565b600f805460ff191690556040805160008082526020820190925281610388565b60408051808201909152600080825260208201528152602001906001900390816103615790505b50905061039481610537565b5050565b6000600f5460ff1660038111156103b1576103b1611751565b146103d657600f54604051639a0293fd60e01b815261009b9160ff1690600401611a7c565b60a05160e0516040805160016226579360e01b03198152885160048201526020808a01516024830152885160448301528801516064820152908701516084820152606087015160a48201526001600160a01b0391821660c482015260e4810186905291169063ffd9a86d9061010401600060405180830381600087803b15801561045f57600080fd5b505af1158015610473573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6000600f5460ff1660038111156104c6576104c6611751565b146104eb57600f5460405163ad88fc8f60e01b815261009b9160ff1690600401611a7c565b61271061ffff8216111561051f57604051624ae8fd60e41b815261ffff82166004820152612710602482015260440161009b565b6005805461ffff191661ffff92909216919091179055565b6000600f5460ff16600381111561055057610550611751565b1461057557600f546040516312ba63f960e11b815261009b9160ff1690600401611a7c565b600e5460005b818110156105d457600d6000600e838154811061059a5761059a611a66565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff191690550161057b565b506105e1600e60006116ee565b5060c051610100518251111561061957815161010051604051630962235760e31b81526004810192909252602482015260440161009b565b815160005b8181101561085357806000036106955760e0516001600160a01b031684828151811061064c5761064c611a66565b6020026020010151600001516001600160a01b0316146106955760e05160405163ef77b46760e01b8152600481018390526001600160a01b03909116602482015260440161009b565b60006001600160a01b03168482815181106106b2576106b2611a66565b6020026020010151600001516001600160a01b0316036106e85760405163621caef560e11b81526004810182905260240161009b565b6000600d600086848151811061070057610700611a66565b6020026020010151600001516001600160a01b03166001600160a01b0316815260200190815260200160002090508060000154600014610756576040516308308c2360e21b81526004810183905260240161009b565b600061076c858461010051610ece60201b60201c565b9050600086848151811061078257610782611a66565b6020026020010151602001519050818110156107b75783818360405163c4aa8aa360e01b815260040161009b93929190611aa4565b808610156107de57838187604051636d3ae3a760e01b815260040161009b93929190611aa4565b6107e88187611ad0565b9550600e8785815181106107fe576107fe611a66565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161061e565b50505050565b6000600f5460ff16600381111561087257610872611751565b1415801561089757506001600f5460ff16600381111561089457610894611751565b14155b156108bc57600f54604051630295f95f60e51b815261009b9160ff1690600401611a7c565b60a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108fc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109209190611a30565b61010051146109b3576101005160a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109919190611a30565b604051631248081b60e31b81526004810192909252602482015260440161009b565b60a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a179190611a30565b60c05114610a625760c05160a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b6000600f5460ff166003811115610a7b57610a7b611751565b03610b145760e0516001600160a01b0316836001600160a01b031614610ab95760e0516040516316a4ebc160e21b815261009b918591600401611ae3565b600f805460ff1916600117905560e05160025460055460405161ffff90911681526001600160a01b03909216917fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d60205260409020805415801590610b415750600181015460ff16155b15610b8a578054821015610b7557805460405163454cd8e160e01b815261009b918491600401918252602082015260400190565b6001818101805460ff19169091179055610be6565b6001600160a01b0384166000908152600a6020526040902054158015610bb65750610bb3610f65565b82105b15610be65781610bc4610f65565b6040516310e45ff160e21b81526004810192909252602482015260440161009b565b6001600160a01b0384166000908152600a60205260408120549003610cb7576000610c11858561102b565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8909101805491909316911617905550610cc1565b610cc18484611049565b6001600160a01b0384166000908152600a602052604081208054849290610ce9908490611afd565b90915550506001600160a01b0384166000908152600b60205260408120429055610d116111ca565b90506000610d1d611236565b60c051909150610d2d8284611afd565b1115610d5457818160c05160405163513b428b60e11b815260040161009b93929190611aa4565b61010051600c541115610d8157610100516040516315b5c3d560e21b815260040161009b91815260200190565b60c0518203610dd35760e0516002546040516001600160a01b03909216917f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa285604051610e0e91815260200190565b60405180910390a2608051610e2e906001600160a01b03168730876112a8565b6002600f5460ff166003811115610e4757610e47611751565b148015610e5c5750600f54610100900460ff16155b15610e6957610e696112e1565b505050505050565b610ec983846001600160a01b031663a9059cbb8585604051602401610e97929190611b10565b60408051808303601f1901815291905260208101805160e09390931b6001600160e01b03938416179052915061154216565b505050565b6000828211610efa576040516376675ed960e11b8152600481018490526024810183905260440161009b565b82600003610f2b576004610f0f600186611ad0565b610f199190611b29565b610f24906001611afd565b9050610f5e565b6000610f378484611ad0565b905080610f45600187611ad0565b610f4f9190611b29565b610f5a906001611afd565b9150505b9392505050565b600e5460009081908190815b81811015610fe8576000600e8281548110610f8e57610f8e611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610fde578054610fce9087611afd565b9550610fdb600186611afd565b94505b5050600101610f71565b5061102383610ff56111ca565b60c0516110029190611ad0565b61100c9190611ad0565b600c5461101a908590611afd565b61010051610ece565b935050505090565b60006001600160a01b038216156110425781610f5e565b5090919050565b6001600f5460ff16600381111561106257611062611751565b1415801561108757506002600f5460ff16600381111561108457611084611751565b14155b156110ac576002546040516310004b0160e11b8152600481019190915260240161009b565b60006110b8838361102b565b600c549091506000908190815b8181101561115e576000600c82815481106110e2576110e2611a66565b6000918252602090912060029091020180549091506001600160a01b03808a169116036111555760018101546001600160a01b0380881691160361112a575050505050505050565b600190810180546001600160a01b038881166001600160a01b0319831617909255169450925061115e565b506001016110c5565b508161117f5785604051632fd768d360e11b815260040161009b9190611b4b565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe7284866040516111ba929190611ae3565b60405180910390a2505050505050565b600c54600090815b81811015611231576000600c82815481106111ef576111ef611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506112269085611afd565b9350506001016111d2565b505090565b600e54600090815b81811015611231576000600e828154811061125b5761125b611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff1661129e57805461129b9086611afd565b94505b505060010161123e565b6040516001600160a01b0384811660248301528381166044830152606482018390526108539186918216906323b872dd90608401610e97565b6002600f5460ff1660038111156112fa576112fa611751565b1461131f57600f546040516383696f6f60e01b815261009b9160ff1690600401611a7c565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b038111156113755761137561177c565b6040519080825280602002602001820160405280156113c957816020015b60408051608081018252600091810182815260608201839052815260208101919091528152602001906001900390816113935790505b50905060005b82811015611464576000600c82815481106113ec576113ec611a66565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061145057611450611a66565b6020908102919091010152506001016113cf565b506080516001600160a01b031663095ea7b360a05160c0516040518363ffffffff1660e01b8152600401611499929190611b10565b6020604051808303816000875af11580156114b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114dc9190611b5f565b5060a0516001600160a01b031663bc7efecc600060066002856040518563ffffffff1660e01b81526004016115149493929190611bd9565b600060405180830381600087803b15801561152e57600080fd5b505af1158015610e69573d6000803e3d6000fd5b60006115576001600160a01b0384168361159c565b9050805160001415801561157c57508080602001905181019061157a9190611b5f565b155b15610ec95782604051635274afe760e01b815260040161009b9190611b4b565b6060610f5e838360006115b0565b92915050565b6060814710156115d5573060405163cd78605960e01b815260040161009b9190611b4b565b600080856001600160a01b031684866040516115f19190611c50565b60006040518083038185875af1925050503d806000811461162e576040519150601f19603f3d011682016040523d82523d6000602084013e611633565b606091505b50909250905061164486838361164e565b9695505050505050565b6060826116635761165e826116a1565b610f5e565b815115801561167a57506001600160a01b0384163b155b1561169a5783604051639996b31560e01b815260040161009b9190611b4b565b5080610f5e565b8051156116b15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b50805460008255600202906000526020600020908101906116ca919061170c565b50805460008255906000526020600020908101906116ca919061173c565b5b808211156117385780546001600160a01b03199081168255600182018054909116905560020161170d565b5090565b5b80821115611738576000815560010161173d565b634e487b7160e01b600052602160045260246000fd5b6001600160a01b03811681146116ca57600080fd5b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b03811182821017156117b4576117b461177c565b60405290565b604080519081016001600160401b03811182821017156117b4576117b461177c565b604051601f8201601f191681016001600160401b03811182821017156118045761180461177c565b604052919050565b60006080828403121561181e57600080fd5b611826611792565b8251815260208084015190820152604080840151908201526060928301519281019290925250919050565b60006080828403121561186357600080fd5b61186b611792565b825181526020808401519082015260408084015190820152606083015190915061ffff8116811461189b57600080fd5b606082015292915050565b600082601f8301126118b757600080fd5b81516001600160401b038111156118d0576118d061177c565b6118df60208260051b016117dc565b8082825260208201915060208360061b86010192508583111561190157600080fd5b602085015b8381101561194d576040818803121561191e57600080fd5b6119266117ba565b815161193181611767565b8152602082810151818301529084529290920191604001611906565b5095945050505050565b8051801515811461196757600080fd5b919050565b60008060008060008060008789036101c081121561198957600080fd5b885161199481611767565b60208a015190985096506040603f19820112156119b057600080fd5b506119b96117ba565b604089015181526060890151602082015294506119d98960808a0161180c565b93506119e9896101008a01611851565b6101808901519093506001600160401b03811115611a0657600080fd5b611a128a828b016118a6565b925050611a226101a08901611957565b905092959891949750929550565b600060208284031215611a4257600080fd5b5051919050565b600060208284031215611a5b57600080fd5b8151610f5e81611767565b634e487b7160e01b600052603260045260246000fd5b6020810160048310611a9e57634e487b7160e01b600052602160045260246000fd5b91905290565b9283526020830191909152604082015260600190565b634e487b7160e01b600052601160045260246000fd5b818103818111156115aa576115aa611aba565b6001600160a01b0392831681529116602082015260400190565b808201808211156115aa576115aa611aba565b6001600160a01b03929092168252602082015260400190565b600082611b4657634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b0391909116815260200190565b600060208284031215611b7157600080fd5b610f5e82611957565b600081518084526020840193506020830160005b82811015611bcf578151805180516001600160a01b039081168952602091820151168189015290810151604088015260609096019590910190600101611b8e565b5093949350505050565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000611644610160830184611b7a565b6000825160005b81811015611c715760208186018101518583015201611c57565b506000920191825250919050565b60805160a05160c05160e051610100516135f2611e4b6000396000818161050501528181610f36015281816115b4015281816115dd0152818161179701528181611e3001528181611e56015281816123b501526123e90152600081816104660152818161064b01528181610673015281816106d601528181610a2801528181610a5001528181610afe01528181610b2601528181610bcb01528181610bf301528181610c5b01528181610c9301528181610cbb01528181610d0d01528181610d350152818161104b01528181611073015281816111b5015281816112140152818161123c01528181611469015281816114910152818161162e015281816116a00152818161205901528181612093015281816120f50152818161244c015281816129480152612a7d01526000818161043f01528181610ef80152818161159201528181611f9601528181611fbc01528181612342015281816123750152818161241701526127b601526000818161034a01528181611dae01528181611e7701528181611f1401528181611fdd015281816127940152818161283901526129170152600081816105a601528181611ab201528181611cef015281816124f2015261276701526135f26000f3fe608060405234801561001057600080fd5b50600436106101e25760003560e01c8062e9a407146101e75780630aaffd2a146101fc5780630d616d201461020f5780630d9639ba146102175780630dcf4b8f146102375780630ebb172a1461024d57806312fa329b1461026f578063200d2ed2146102905780632816ee73146102aa5780632c6cda93146102bd5780632c7baaf3146102d0578063313f336b1461030f5780633362379414610345578063356c299d1461037957806342e94c90146103b75780634564168b146103d75780634a387cdf146103f35780634bb278f3146103fb5780634e7320ce14610403578063565a9d481461041857806356d399e81461043a578063570ca7351461046157806358b810d3146104885780636786452c1461049b5780636cf72aa4146104ae5780637c0b4bfb146104c157806386fa5063146105005780638c7e7a31146105275780638dd085571461053a578063937e09b11461055a57806396a608c0146105625780639ca002a61461056a578063a4b6aa831461057d578063bc063e1a14610585578063c3aca558146105a1578063ccec3716146105c8578063d26146a5146105db578063d826f88f146105ee578063dda0a81a146105f6578063e020e4c41461060d578063e50d50d814610620575b600080fd5b6101fa6101f5366004612e8f565b610640565b005b6101fa61020a366004612ecb565b6106c2565b6101fa6106cc565b61021f6107b7565b60405161022e93929190612f5d565b60405180910390f35b61023f61096d565b60405190815260200161022e565b6102576201518081565b6040516001600160401b03909116815260200161022e565b61028261027d366004612fca565b6109d9565b60405161022e929190612fe3565b600f5461029d9060ff1681565b60405161022e9190613013565b6101fa6102b836600461303b565b610a12565b6101fa6102cb366004613079565b610a1d565b6102d8610a93565b60405161022e919081518152602080830151908201526040808301519082015260609182015161ffff169181019190915260800190565b6006546007546008546009546103259392919084565b60408051948552602085019390935291830152606082015260800161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b60405161022e9190613094565b6002546003546004546005546103939392919061ffff1684565b6040805194855260208501939093529183015261ffff16606082015260800161022e565b61023f6103c5366004612ecb565b600a6020526000908152604090205481565b6000546001546103e5919082565b60405161022e9291906130a8565b600c5461023f565b6101fa610af3565b61040b610b68565b60405161022e91906130dc565b600f5461042a90610100900460ff1681565b604051901515815260200161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa610496366004613103565b610bc0565b6101fa6104a93660046131f1565b610c88565b6101fa6104bc366004613242565b610d02565b6104eb6104cf366004612ecb565b600d602052600090815260409020805460019091015460ff1682565b6040805192835290151560208301520161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61023f61053536600461325f565b610d78565b61054d610548366004612fca565b610e09565b60405161022e91906132a6565b61023f610e62565b61023f610f62565b61023f610578366004612fca565b610fb9565b61023f610fce565b61058e61271081565b60405161ffff909116815260200161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa6105d6366004612ecb565b611040565b61036c6105e9366004612fca565b6111df565b6101fa611209565b6105fe61127e565b60405161022e939291906132b4565b6101fa61061b3660046132ed565b61145e565b61023f61062e366004612ecb565b600b6020526000908152604090205481565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106b657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b60405180910390fd5b6106bf816114e3565b50565b6106bf33826118a8565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016330361070657610704611a29565b565b336000908152600b602052604081205461072090426133e4565b90506201518081101561076a57336000908152600b602052604090819020549051630429b06960e31b815260048101919091524260248201526201518060448201526064016106ad565b600061077533611b4c565b905080156107b35760405181815233907f249a82fbe5056a1940b4e996665ba2a82c340ae9fa1e069fd1ababf5508f396e9060200160405180910390a25b5050565b600e5460609081908190806001600160401b038111156107d9576107d9612d34565b604051908082528060200260200182016040528015610802578160200160208202803683370190505b509350806001600160401b0381111561081d5761081d612d34565b604051908082528060200260200182016040528015610846578160200160208202803683370190505b509250806001600160401b0381111561086157610861612d34565b60405190808252806020026020018201604052801561088a578160200160208202803683370190505b50915060005b81811015610966576000600e82815481106108ad576108ad6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912087519192509082908890859081106108eb576108eb6133f7565b60200260200101906001600160a01b031690816001600160a01b0316815250508060000154868481518110610922576109226133f7565b60209081029190910101526001810154855160ff9091169086908590811061094c5761094c6133f7565b911515602092830291909101909101525050600101610890565b5050909192565b600c54600090815b818110156109d4576000600c8281548110610992576109926133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506109c9908561340d565b935050600101610975565b505090565b600c81815481106109e957600080fd5b6000918252602090912060029091020180546001909101546001600160a01b0391821692501682565b6107b3338284611d49565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a8a57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf8161255d565b610ac26040518060800160405280600081526020016000815260200160008152602001600061ffff1681525090565b5060408051608081018252600254815260035460208201526004549181019190915260055461ffff16606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b6057337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6107046125e7565b610b936040518060800160405280600081526020016000815260200160008152602001600081525090565b50604080516080810182526006548152600754602082015260085491810191909152600954606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610c2d57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c35611a29565b610c3e8561255d565b610c47846114e3565b610c50836128a5565b8015610c8157610c817f00000000000000000000000000000000000000000000000000000000000000008383611d49565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cf557337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c8185858585856128bf565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610d6f57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf816128a5565b6000828211610d9e5782826040516376675ed960e11b81526004016106ad9291906130a8565b82600003610dcf576004610db36001866133e4565b610dbd9190613420565b610dc890600161340d565b9050610e02565b6000610ddb84846133e4565b905080610de96001876133e4565b610df39190613420565b610dfe90600161340d565b9150505b9392505050565b610e11612c79565b600c8281548110610e2457610e246133f7565b60009182526020918290206040805180820190915260029092020180546001600160a01b039081168352600190910154169181019190915292915050565b600e5460009081908190815b81811015610ee5576000600e8281548110610e8b57610e8b6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610edb578054610ecb908761340d565b9550610ed860018661340d565b94505b5050600101610e6e565b50610f5a83610ef261096d565b610f1c907f00000000000000000000000000000000000000000000000000000000000000006133e4565b610f2691906133e4565b600c54610f3490859061340d565b7f0000000000000000000000000000000000000000000000000000000000000000610d78565b935050505090565b600c54600090610f725750600090565b600a6000600c600081548110610f8a57610f8a6133f7565b600091825260208083206002909202909101546001600160a01b03168352820192909252604001902054905090565b6000610fc88260006001610d78565b92915050565b600e54600090815b818110156109d4576000600e8281548110610ff357610ff36133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16611036578054611033908661340d565b94505b5050600101610fd6565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110ad57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6003600f5460ff1660038111156110c6576110c6612ffd565b141580156110ea57506000600f5460ff1660038111156110e8576110e8612ffd565b145b1561110f57600f54604051632f3e69dd60e01b81526106ad9160ff1690600401613013565b6040516370a0823160e01b815281906000906001600160a01b038316906370a0823190611140903090600401613094565b602060405180830381865afa15801561115d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111819190613442565b9050600081116111a6578260405163e932c57360e01b81526004016106ad9190613094565b6111da6001600160a01b0383167f0000000000000000000000000000000000000000000000000000000000000000836129da565b505050565b600e81815481106111ef57600080fd5b6000918252602090912001546001600160a01b0316905081565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461127657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610704611a29565b600c5460609081908190806001600160401b038111156112a0576112a0612d34565b6040519080825280602002602001820160405280156112c9578160200160208202803683370190505b509350806001600160401b038111156112e4576112e4612d34565b60405190808252806020026020018201604052801561130d578160200160208202803683370190505b509250806001600160401b0381111561132857611328612d34565b604051908082528060200260200182016040528015611351578160200160208202803683370190505b50915060005b81811015610966576000600c8281548110611374576113746133f7565b60009182526020909120600290910201805487519192506001600160a01b0316908790849081106113a7576113a76133f7565b6001600160a01b039283166020918202929092010152600182015486519116908690849081106113d9576113d96133f7565b60200260200101906001600160a01b031690816001600160a01b031681525050600a600087848151811061140f5761140f6133f7565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020016000205484838151811061144a5761144a6133f7565b602090810291909101015250600101611357565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114cb57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6114da87878787878787612a32565b50505050505050565b6000600f5460ff1660038111156114fc576114fc612ffd565b1461152157600f546040516312ba63f960e11b81526106ad9160ff1690600401613013565b600e5460005b8181101561158057600d6000600e8381548110611546576115466133f7565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff1916905501611527565b5061158d600e6000612c90565b5080517f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000010156116175781517f0000000000000000000000000000000000000000000000000000000000000000604051630962235760e31b81526004016106ad9291906130a8565b815160005b818110156118a257806000036116cd577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316848281518110611668576116686133f7565b6020026020010151600001516001600160a01b0316146116cd5760405163ef77b46760e01b8152600481018290526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660248201526044016106ad565b60006001600160a01b03168482815181106116ea576116ea6133f7565b6020026020010151600001516001600160a01b0316036117205760405163621caef560e11b8152600481018290526024016106ad565b6000600d6000868481518110611738576117386133f7565b6020026020010151600001516001600160a01b03166001600160a01b031681526020019081526020016000209050806000015460001461178e576040516308308c2360e21b8152600481018390526024016106ad565b60006117bb85847f0000000000000000000000000000000000000000000000000000000000000000610d78565b905060008684815181106117d1576117d16133f7565b6020026020010151602001519050818110156118065783818360405163c4aa8aa360e01b81526004016106ad9392919061345b565b8086101561182d57838187604051636d3ae3a760e01b81526004016106ad9392919061345b565b61183781876133e4565b9550600e87858151811061184d5761184d6133f7565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161161c565b50505050565b6001600f5460ff1660038111156118c1576118c1612ffd565b141580156118e657506002600f5460ff1660038111156118e3576118e3612ffd565b14155b1561190b576002546040516310004b0160e11b815260048101919091526024016106ad565b60006119178383612aa3565b600c549091506000908190815b818110156119bd576000600c8281548110611941576119416133f7565b6000918252602090912060029091020180549091506001600160a01b03808a169116036119b45760018101546001600160a01b03808816911603611989575050505050505050565b600190810180546001600160a01b038881166001600160a01b031983161790925516945092506119bd565b50600101611924565b50816119de5785604051632fd768d360e11b81526004016106ad9190613094565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe728486604051611a19929190612fe3565b60405180910390a2505050505050565b600c5460005b81811015611aec576000600c8281548110611a4c57611a4c6133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff166003811115611a9257611a92612ffd565b14158015611aa05750600081115b15611ad957611ad96001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001683836129da565b611ae282612ac1565b5050600101611a2f565b50611af9600c6000612cae565b600f805460ff191690556040805160008082526020820190925281611b40565b6040805180820190915260008082526020820152815260200190600190039081611b195790505b5090506107b3816114e3565b6001600160a01b0381166000908152600a602052604081205490819003611b7257919050565b611b7b82612ac1565b600c5460005b81811015611c9657600c8181548110611b9c57611b9c6133f7565b60009182526020909120600290910201546001600160a01b0390811690851603611c8e57600c611bcd6001846133e4565b81548110611bdd57611bdd6133f7565b9060005260206000209060020201600c8281548110611bfe57611bfe6133f7565b60009182526020909120825460029092020180546001600160a01b039283166001600160a01b0319918216178255600193840154939091018054939092169216919091179055600c805480611c5557611c55613471565b60008281526020902060026000199092019182020180546001600160a01b03199081168255600191909101805490911690559055611c96565b600101611b81565b506001600160a01b0383166000908152600d6020526040902060018101805460ff191690556003600f5460ff166003811115611cd457611cd4612ffd565b03611ce25760009250611d42565b611d166001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001685856129da565b6002600f5460ff166003811115611d2f57611d2f612ffd565b03611d4257600f805460ff191660011790555b5050919050565b6000600f5460ff166003811115611d6257611d62612ffd565b14158015611d8757506001600f5460ff166003811115611d8457611d84612ffd565b14155b15611dac57600f54604051630295f95f60e51b81526106ad9160ff1690600401613013565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e2e9190613442565b7f000000000000000000000000000000000000000000000000000000000000000014611f12577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ef79190613442565b604051631248081b60e31b81526004016106ad9291906130a8565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f949190613442565b7f000000000000000000000000000000000000000000000000000000000000000014612039577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b6000600f5460ff16600381111561205257612052612ffd565b03612145577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316146120cd57827f00000000000000000000000000000000000000000000000000000000000000006040516316a4ebc160e21b81526004016106ad929190612fe3565b600f805460ff1916600117905560025460055460405161ffff90911681526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d602052604090208054158015906121725750600181015460ff16155b156121b257805482101561219d57805460405163454cd8e160e01b81526106ad9184916004016130a8565b6001818101805460ff19169091179055612207565b6001600160a01b0384166000908152600a60205260409020541580156121de57506121db610e62565b82105b1561220757816121ec610e62565b6040516310e45ff160e21b81526004016106ad9291906130a8565b6001600160a01b0384166000908152600a602052604081205490036122d85760006122328585612aa3565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c89091018054919093169116179055506122e2565b6122e284846118a8565b6001600160a01b0384166000908152600a60205260408120805484929061230a90849061340d565b90915550506001600160a01b0384166000908152600b6020526040812042905561233261096d565b9050600061233e610fce565b90507f000000000000000000000000000000000000000000000000000000000000000061236b828461340d565b11156123b05781817f000000000000000000000000000000000000000000000000000000000000000060405163513b428b60e11b81526004016106ad9392919061345b565b600c547f00000000000000000000000000000000000000000000000000000000000000001015612415576040516315b5c3d560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201526024016106ad565b7f000000000000000000000000000000000000000000000000000000000000000082036124a2576002546040516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa2856040516124dd91815260200190565b60405180910390a261251a6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016873087612ae8565b6002600f5460ff16600381111561253357612533612ffd565b1480156125485750600f54610100900460ff16155b15612555576125556125e7565b505050505050565b6000600f5460ff16600381111561257657612576612ffd565b1461259b57600f5460405163ad88fc8f60e01b81526106ad9160ff1690600401613013565b61271061ffff821611156125cf57604051624ae8fd60e41b815261ffff8216600482015261271060248201526044016106ad565b6005805461ffff191661ffff92909216919091179055565b6002600f5460ff16600381111561260057612600612ffd565b1461262557600f546040516383696f6f60e01b81526106ad9160ff1690600401613013565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b0381111561267b5761267b612d34565b6040519080825280602002602001820160405280156126b457816020015b6126a1612ccf565b8152602001906001900390816126995790505b50905060005b8281101561274f576000600c82815481106126d7576126d76133f7565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061273b5761273b6133f7565b6020908102919091010152506001016126ba565b5060405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906127de907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090600401613487565b6020604051808303816000875af11580156127fd573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061282191906134a0565b50604051632f1fbfb360e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bc7efecc90612877906000906006906002908790600401613504565b600060405180830381600087803b15801561289157600080fd5b505af1158015612555573d6000803e3d6000fd5b600f80549115156101000261ff0019909216919091179055565b6000600f5460ff1660038111156128d8576128d8612ffd565b146128fd57600f54604051639a0293fd60e01b81526106ad9160ff1690600401613013565b60405160016226579360e01b031981526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063ffd9a86d9061297290889088907f000000000000000000000000000000000000000000000000000000000000000090899060040161357b565b600060405180830381600087803b15801561298c57600080fd5b505af11580156129a0573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6111da83846001600160a01b031663a9059cbb8585604051602401612a00929190613487565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050612b21565b612a3a611a29565b612a5387878760000151886020015189604001516128bf565b612a60856060015161255d565b612a69846114e3565b612a72836128a5565b80156114da576114da7f00000000000000000000000000000000000000000000000000000000000000008383611d49565b60006001600160a01b03821615612aba5781610e02565b5090919050565b6001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b6040516001600160a01b0384811660248301528381166044830152606482018390526118a29186918216906323b872dd90608401612a00565b6000612b366001600160a01b03841683612b7b565b90508051600014158015612b5b575080806020019051810190612b5991906134a0565b155b156111da5782604051635274afe760e01b81526004016106ad9190613094565b6060610e028383600084600080856001600160a01b03168486604051612ba191906135b6565b60006040518083038185875af1925050503d8060008114612bde576040519150601f19603f3d011682016040523d82523d6000602084013e612be3565b606091505b5091509150612bf3868383612bfd565b9695505050505050565b606082612c1257612c0d82612c50565b610e02565b8151158015612c2957506001600160a01b0384163b155b15612c495783604051639996b31560e01b81526004016106ad9190613094565b5080610e02565b805115612c605780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b604080518082019091526000808252602082015290565b50805460008255906000526020600020908101906106bf9190612cef565b50805460008255600202906000526020600020908101906106bf9190612d08565b6040518060400160405280612ce2612c79565b8152602001600081525090565b5b80821115612d045760008155600101612cf0565b5090565b5b80821115612d045780546001600160a01b031990811682556001820180549091169055600201612d09565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715612d6c57612d6c612d34565b60405290565b604051608081016001600160401b0381118282101715612d6c57612d6c612d34565b604051601f8201601f191681016001600160401b0381118282101715612dbc57612dbc612d34565b604052919050565b80356001600160a01b0381168114612ddb57600080fd5b919050565b600082601f830112612df157600080fd5b81356001600160401b03811115612e0a57612e0a612d34565b612e1960208260051b01612d94565b8082825260208201915060208360061b860101925085831115612e3b57600080fd5b602085015b83811015612e855760408188031215612e5857600080fd5b612e60612d4a565b612e6982612dc4565b8152602082810135818301529084529290920191604001612e40565b5095945050505050565b600060208284031215612ea157600080fd5b81356001600160401b03811115612eb757600080fd5b612ec384828501612de0565b949350505050565b600060208284031215612edd57600080fd5b610e0282612dc4565b600081518084526020840193506020830160005b82811015612f215781516001600160a01b0316865260209586019590910190600101612efa565b5093949350505050565b600081518084526020840193506020830160005b82811015612f21578151865260209586019590910190600101612f3f565b606081526000612f706060830186612ee6565b8281036020840152612f828186612f2b565b83810360408501528451808252602080870193509091019060005b81811015612fbd5783511515835260209384019390920191600101612f9d565b5090979650505050505050565b600060208284031215612fdc57600080fd5b5035919050565b6001600160a01b0392831681529116602082015260400190565b634e487b7160e01b600052602160045260246000fd5b602081016004831061303557634e487b7160e01b600052602160045260246000fd5b91905290565b6000806040838503121561304e57600080fd5b8235915061305e60208401612dc4565b90509250929050565b803561ffff81168114612ddb57600080fd5b60006020828403121561308b57600080fd5b610e0282613067565b6001600160a01b0391909116815260200190565b918252602082015260400190565b805182526020810151602083015260408101516040830152606081015160608301525050565b60808101610fc882846130b6565b80151581146106bf57600080fd5b8035612ddb816130ea565b600080600080600060a0868803121561311b57600080fd5b61312486613067565b945060208601356001600160401b0381111561313f57600080fd5b61314b88828901612de0565b945050604086013561315c816130ea565b925061316a60608701612dc4565b949793965091946080013592915050565b60006040828403121561318d57600080fd5b613195612d4a565b823581526020928301359281019290925250919050565b6000608082840312156131be57600080fd5b6131c6612d72565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b6000806000806000610120868803121561320a57600080fd5b613214878761317b565b945061322387604088016131ac565b949794965050505060c08301359260e081013592610100909101359150565b60006020828403121561325457600080fd5b8135610e02816130ea565b60008060006060848603121561327457600080fd5b505081359360208301359350604090920135919050565b80516001600160a01b03908116835260209182015116910152565b60408101610fc8828461328b565b6060815260006132c76060830186612ee6565b82810360208401526132d98186612ee6565b90508281036040840152612bf38185612f2b565b60008060008060008060008789036101c081121561330a57600080fd5b6133148a8a61317b565b97506133238a60408b016131ac565b9650608060bf198201121561333757600080fd5b50613340612d72565b60c0890135815260e0890135602082015261010089013560408201526133696101208a01613067565b606082015294506101408801356001600160401b0381111561338a57600080fd5b6133968a828b01612de0565b9450506133a661016089016130f8565b92506133b56101808901612dc4565b9699959850939692959194919350506101a09091013590565b634e487b7160e01b600052601160045260246000fd5b81810381811115610fc857610fc86133ce565b634e487b7160e01b600052603260045260246000fd5b80820180821115610fc857610fc86133ce565b60008261343d57634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561345457600080fd5b5051919050565b9283526020830191909152604082015260600190565b634e487b7160e01b600052603160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000602082840312156134b257600080fd5b8151610e02816130ea565b600081518084526020840193506020830160005b82811015612f215781516134e687825161328b565b602090810151604088015260609096019591909101906001016134d1565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000612bf36101608301846134bd565b8451815260208086015190820152610100810161359b60408301866130b6565b6001600160a01b039390931660c082015260e0015292915050565b6000825160005b818110156135d757602081860181015185830152016135bd565b50600092019182525091905056fea164736f6c634300081a000aa164736f6c634300081a000a", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100af5760003560e01c806333623794146100b45780633f4ba83a146100dd5780635c975abb146100e7578063715018a6146100ff57806379ba5097146101075780638456cb591461010f5780638da5cb5b146101175780638faff8621461011f578063c4d66de814610132578063c70242ad14610145578063e30c397814610168578063f2fde38b14610170578063f7bc39bf14610183575b600080fd5b6000546100c7906001600160a01b031681565b6040516100d49190610809565b60405180910390f35b6100e56101af565b005b6100ef6101c1565b60405190151581526020016100d4565b6100e56101d6565b6100e56101e8565b6100e5610230565b6100c7610240565b6100c761012d36600461084a565b61025b565b6100e5610140366004610925565b610387565b6100ef610153366004610925565b60016020526000908152604090205460ff1681565b6100c76104a3565b6100e561017e366004610925565b6104ae565b6100ef610191366004610925565b6001600160a01b031660009081526001602052604090205460ff1690565b6101b761051f565b6101bf610551565b565b6000806101cc6105a8565b5460ff1692915050565b6101de61051f565b6101bf60006105cc565b33806101f26104a3565b6001600160a01b031614610224578060405163118cdaa760e01b815260040161021b9190610809565b60405180910390fd5b61022d816105cc565b50565b61023861051f565b6101bf6105f3565b60008061024b61063a565b546001600160a01b031692915050565b600061026561065e565b60008054604080516386fa506360e01b815290516001600160a01b039092169182916386fa50639160048083019260209291908290030181865afa1580156102b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102d59190610947565b8989898989896040516102e7906107fc565b6102f89897969594939291906109eb565b604051809103906000f080158015610314573d6000803e3d6000fd5b506001600160a01b038116600081815260016020819052604091829020805460ff1916909117905551919350839250907feac84630ba02e5ab324a651281c90ec45563a21f07fdf52b6f601f312e2de27a90610374908935815260200190565b60405180910390a2509695505050505050565b6000610391610684565b805490915060ff600160401b82041615906001600160401b03166000811580156103b85750825b90506000826001600160401b031660011480156103d45750303b155b9050811580156103e2575080155b156104005760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b0319166001178555831561042957845460ff60401b1916600160401b1785555b600080546001600160a01b0319166001600160a01b03881617905561044d336106a8565b6104556106b9565b831561049b57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b60008061024b6106c9565b6104b661051f565b60006104c06106c9565b80546001600160a01b0319166001600160a01b03841690811782559091506104e6610240565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b33610528610240565b6001600160a01b0316146101bf573360405163118cdaa760e01b815260040161021b9190610809565b6105596106ed565b60006105636105a8565b805460ff1916815590507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b60405161059d9190610809565b60405180910390a150565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330090565b60006105d66106c9565b80546001600160a01b031916815590506105ef82610712565b5050565b6105fb61065e565b60006106056105a8565b805460ff1916600117815590507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586105903390565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b6106666101c1565b156101bf5760405163d93c066560e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6106b061076e565b61022d81610793565b6106c161076e565b6101bf6107c5565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b6106f56101c1565b6101bf57604051638dfc202b60e01b815260040160405180910390fd5b600061071c61063a565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b6107766107e2565b6101bf57604051631afcd79f60e31b815260040160405180910390fd5b61079b61076e565b6001600160a01b038116610224576000604051631e4fbdf760e01b815260040161021b9190610809565b6107cd61076e565b60006107d76105a8565b805460ff1916905550565b60006107ec610684565b54600160401b900460ff16919050565b61543d80610a7883390190565b6001600160a01b0391909116815260200190565b60006080828403121561082f57600080fd5b50919050565b8035801515811461084557600080fd5b919050565b60008060008060008086880361018081121561086557600080fd5b604081121561087357600080fd5b50869550610884886040890161081d565b94506108938860c0890161081d565b93506101408701356001600160401b038111156108af57600080fd5b8701601f810189136108c057600080fd5b80356001600160401b038111156108d657600080fd5b8960208260061b84010111156108eb57600080fd5b602091909101935091506109026101608801610835565b90509295509295509295565b80356001600160a01b038116811461084557600080fd5b60006020828403121561093757600080fd5b6109408261090e565b9392505050565b60006020828403121561095957600080fd5b5051919050565b803582526020808201359083015260408082013590830152606081013561ffff811680821461098e57600080fd5b80606085015250505050565b81835260208301925060008160005b848110156109e1576001600160a01b036109c28361090e565b16865260208281013590870152604095860195909101906001016109a9565b5093949350505050565b6001600160a01b03891681526020808201899052873560408084019190915288820135606080850191909152883560808501529188013560a084015287013560c083015286013560e08201526000610a47610100830187610960565b6101c0610180830152610a5f6101c08301858761099a565b8315156101a08401529050999850505050505050505056fe610120604052600f805460ff1916905534801561001b57600080fd5b5060405161543d38038061543d83398101604081905261003a9161196c565b866001600160a01b0381166100a45760405162461bcd60e51b815260206004820152602560248201527f5368617265643a205a65726f2d61646472657373206973206e6f74207065726d6044820152641a5d1d195960da1b60648201526084015b60405180910390fd5b86806000036100f55760405162461bcd60e51b815260206004820152601b60248201527f5368617265643a2075696e7420696e70757420697320656d7074790000000000604482015260640161009b565b6001600160a01b03891660a081905260408051630ada733d60e31b815290516356d399e8916004808201926020929091908290030181865afa15801561013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101639190611a30565b60c0818152505060a0516001600160a01b0316637c89d2f06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ce9190611a49565b6001600160a01b03166080526101008890523260e05260006101f588888888888680610204565b50505050505050505050611c7f565b61020c61026c565b845160208601516040870151610225928a928a92610398565b6060850151610233906104ad565b61023c84610537565b600f805461ff0019166101008515150217905580156102635760e051610263908383610859565b50505050505050565b600c5460005b81811015610334576000600c828154811061028f5761028f611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff1660038111156102d5576102d5611751565b141580156102e35750600081115b156102ff576080516102ff906001600160a01b03168383610e71565b61032a826001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b5050600101610272565b50610341600c60006116cd565b600f805460ff191690556040805160008082526020820190925281610388565b60408051808201909152600080825260208201528152602001906001900390816103615790505b50905061039481610537565b5050565b6000600f5460ff1660038111156103b1576103b1611751565b146103d657600f54604051639a0293fd60e01b815261009b9160ff1690600401611a7c565b60a05160e0516040805160016226579360e01b03198152885160048201526020808a01516024830152885160448301528801516064820152908701516084820152606087015160a48201526001600160a01b0391821660c482015260e4810186905291169063ffd9a86d9061010401600060405180830381600087803b15801561045f57600080fd5b505af1158015610473573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6000600f5460ff1660038111156104c6576104c6611751565b146104eb57600f5460405163ad88fc8f60e01b815261009b9160ff1690600401611a7c565b61271061ffff8216111561051f57604051624ae8fd60e41b815261ffff82166004820152612710602482015260440161009b565b6005805461ffff191661ffff92909216919091179055565b6000600f5460ff16600381111561055057610550611751565b1461057557600f546040516312ba63f960e11b815261009b9160ff1690600401611a7c565b600e5460005b818110156105d457600d6000600e838154811061059a5761059a611a66565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff191690550161057b565b506105e1600e60006116ee565b5060c051610100518251111561061957815161010051604051630962235760e31b81526004810192909252602482015260440161009b565b815160005b8181101561085357806000036106955760e0516001600160a01b031684828151811061064c5761064c611a66565b6020026020010151600001516001600160a01b0316146106955760e05160405163ef77b46760e01b8152600481018390526001600160a01b03909116602482015260440161009b565b60006001600160a01b03168482815181106106b2576106b2611a66565b6020026020010151600001516001600160a01b0316036106e85760405163621caef560e11b81526004810182905260240161009b565b6000600d600086848151811061070057610700611a66565b6020026020010151600001516001600160a01b03166001600160a01b0316815260200190815260200160002090508060000154600014610756576040516308308c2360e21b81526004810183905260240161009b565b600061076c858461010051610ece60201b60201c565b9050600086848151811061078257610782611a66565b6020026020010151602001519050818110156107b75783818360405163c4aa8aa360e01b815260040161009b93929190611aa4565b808610156107de57838187604051636d3ae3a760e01b815260040161009b93929190611aa4565b6107e88187611ad0565b9550600e8785815181106107fe576107fe611a66565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161061e565b50505050565b6000600f5460ff16600381111561087257610872611751565b1415801561089757506001600f5460ff16600381111561089457610894611751565b14155b156108bc57600f54604051630295f95f60e51b815261009b9160ff1690600401611a7c565b60a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108fc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109209190611a30565b61010051146109b3576101005160a0516001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109919190611a30565b604051631248081b60e31b81526004810192909252602482015260440161009b565b60a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109f3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a179190611a30565b60c05114610a625760c05160a0516001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa15801561096d573d6000803e3d6000fd5b6000600f5460ff166003811115610a7b57610a7b611751565b03610b145760e0516001600160a01b0316836001600160a01b031614610ab95760e0516040516316a4ebc160e21b815261009b918591600401611ae3565b600f805460ff1916600117905560e05160025460055460405161ffff90911681526001600160a01b03909216917fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d60205260409020805415801590610b415750600181015460ff16155b15610b8a578054821015610b7557805460405163454cd8e160e01b815261009b918491600401918252602082015260400190565b6001818101805460ff19169091179055610be6565b6001600160a01b0384166000908152600a6020526040902054158015610bb65750610bb3610f65565b82105b15610be65781610bc4610f65565b6040516310e45ff160e21b81526004810192909252602482015260440161009b565b6001600160a01b0384166000908152600a60205260408120549003610cb7576000610c11858561102b565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c8909101805491909316911617905550610cc1565b610cc18484611049565b6001600160a01b0384166000908152600a602052604081208054849290610ce9908490611afd565b90915550506001600160a01b0384166000908152600b60205260408120429055610d116111ca565b90506000610d1d611236565b60c051909150610d2d8284611afd565b1115610d5457818160c05160405163513b428b60e11b815260040161009b93929190611aa4565b61010051600c541115610d8157610100516040516315b5c3d560e21b815260040161009b91815260200190565b60c0518203610dd35760e0516002546040516001600160a01b03909216917f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa285604051610e0e91815260200190565b60405180910390a2608051610e2e906001600160a01b03168730876112a8565b6002600f5460ff166003811115610e4757610e47611751565b148015610e5c5750600f54610100900460ff16155b15610e6957610e696112e1565b505050505050565b610ec983846001600160a01b031663a9059cbb8585604051602401610e97929190611b10565b60408051808303601f1901815291905260208101805160e09390931b6001600160e01b03938416179052915061154216565b505050565b6000828211610efa576040516376675ed960e11b8152600481018490526024810183905260440161009b565b82600003610f2b576004610f0f600186611ad0565b610f199190611b29565b610f24906001611afd565b9050610f5e565b6000610f378484611ad0565b905080610f45600187611ad0565b610f4f9190611b29565b610f5a906001611afd565b9150505b9392505050565b600e5460009081908190815b81811015610fe8576000600e8281548110610f8e57610f8e611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610fde578054610fce9087611afd565b9550610fdb600186611afd565b94505b5050600101610f71565b5061102383610ff56111ca565b60c0516110029190611ad0565b61100c9190611ad0565b600c5461101a908590611afd565b61010051610ece565b935050505090565b60006001600160a01b038216156110425781610f5e565b5090919050565b6001600f5460ff16600381111561106257611062611751565b1415801561108757506002600f5460ff16600381111561108457611084611751565b14155b156110ac576002546040516310004b0160e11b8152600481019190915260240161009b565b60006110b8838361102b565b600c549091506000908190815b8181101561115e576000600c82815481106110e2576110e2611a66565b6000918252602090912060029091020180549091506001600160a01b03808a169116036111555760018101546001600160a01b0380881691160361112a575050505050505050565b600190810180546001600160a01b038881166001600160a01b0319831617909255169450925061115e565b506001016110c5565b508161117f5785604051632fd768d360e11b815260040161009b9190611b4b565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe7284866040516111ba929190611ae3565b60405180910390a2505050505050565b600c54600090815b81811015611231576000600c82815481106111ef576111ef611a66565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506112269085611afd565b9350506001016111d2565b505090565b600e54600090815b81811015611231576000600e828154811061125b5761125b611a66565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff1661129e57805461129b9086611afd565b94505b505060010161123e565b6040516001600160a01b0384811660248301528381166044830152606482018390526108539186918216906323b872dd90608401610e97565b6002600f5460ff1660038111156112fa576112fa611751565b1461131f57600f546040516383696f6f60e01b815261009b9160ff1690600401611a7c565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b038111156113755761137561177c565b6040519080825280602002602001820160405280156113c957816020015b60408051608081018252600091810182815260608201839052815260208101919091528152602001906001900390816113935790505b50905060005b82811015611464576000600c82815481106113ec576113ec611a66565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061145057611450611a66565b6020908102919091010152506001016113cf565b506080516001600160a01b031663095ea7b360a05160c0516040518363ffffffff1660e01b8152600401611499929190611b10565b6020604051808303816000875af11580156114b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114dc9190611b5f565b5060a0516001600160a01b031663bc7efecc600060066002856040518563ffffffff1660e01b81526004016115149493929190611bd9565b600060405180830381600087803b15801561152e57600080fd5b505af1158015610e69573d6000803e3d6000fd5b60006115576001600160a01b0384168361159c565b9050805160001415801561157c57508080602001905181019061157a9190611b5f565b155b15610ec95782604051635274afe760e01b815260040161009b9190611b4b565b6060610f5e838360006115b0565b92915050565b6060814710156115d5573060405163cd78605960e01b815260040161009b9190611b4b565b600080856001600160a01b031684866040516115f19190611c50565b60006040518083038185875af1925050503d806000811461162e576040519150601f19603f3d011682016040523d82523d6000602084013e611633565b606091505b50909250905061164486838361164e565b9695505050505050565b6060826116635761165e826116a1565b610f5e565b815115801561167a57506001600160a01b0384163b155b1561169a5783604051639996b31560e01b815260040161009b9190611b4b565b5080610f5e565b8051156116b15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b50805460008255600202906000526020600020908101906116ca919061170c565b50805460008255906000526020600020908101906116ca919061173c565b5b808211156117385780546001600160a01b03199081168255600182018054909116905560020161170d565b5090565b5b80821115611738576000815560010161173d565b634e487b7160e01b600052602160045260246000fd5b6001600160a01b03811681146116ca57600080fd5b634e487b7160e01b600052604160045260246000fd5b604051608081016001600160401b03811182821017156117b4576117b461177c565b60405290565b604080519081016001600160401b03811182821017156117b4576117b461177c565b604051601f8201601f191681016001600160401b03811182821017156118045761180461177c565b604052919050565b60006080828403121561181e57600080fd5b611826611792565b8251815260208084015190820152604080840151908201526060928301519281019290925250919050565b60006080828403121561186357600080fd5b61186b611792565b825181526020808401519082015260408084015190820152606083015190915061ffff8116811461189b57600080fd5b606082015292915050565b600082601f8301126118b757600080fd5b81516001600160401b038111156118d0576118d061177c565b6118df60208260051b016117dc565b8082825260208201915060208360061b86010192508583111561190157600080fd5b602085015b8381101561194d576040818803121561191e57600080fd5b6119266117ba565b815161193181611767565b8152602082810151818301529084529290920191604001611906565b5095945050505050565b8051801515811461196757600080fd5b919050565b60008060008060008060008789036101c081121561198957600080fd5b885161199481611767565b60208a015190985096506040603f19820112156119b057600080fd5b506119b96117ba565b604089015181526060890151602082015294506119d98960808a0161180c565b93506119e9896101008a01611851565b6101808901519093506001600160401b03811115611a0657600080fd5b611a128a828b016118a6565b925050611a226101a08901611957565b905092959891949750929550565b600060208284031215611a4257600080fd5b5051919050565b600060208284031215611a5b57600080fd5b8151610f5e81611767565b634e487b7160e01b600052603260045260246000fd5b6020810160048310611a9e57634e487b7160e01b600052602160045260246000fd5b91905290565b9283526020830191909152604082015260600190565b634e487b7160e01b600052601160045260246000fd5b818103818111156115aa576115aa611aba565b6001600160a01b0392831681529116602082015260400190565b808201808211156115aa576115aa611aba565b6001600160a01b03929092168252602082015260400190565b600082611b4657634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b0391909116815260200190565b600060208284031215611b7157600080fd5b610f5e82611957565b600081518084526020840193506020830160005b82811015611bcf578151805180516001600160a01b039081168952602091820151168189015290810151604088015260609096019590910190600101611b8e565b5093949350505050565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000611644610160830184611b7a565b6000825160005b81811015611c715760208186018101518583015201611c57565b506000920191825250919050565b60805160a05160c05160e051610100516135f2611e4b6000396000818161050501528181610f36015281816115b4015281816115dd0152818161179701528181611e3001528181611e56015281816123b501526123e90152600081816104660152818161064b01528181610673015281816106d601528181610a2801528181610a5001528181610afe01528181610b2601528181610bcb01528181610bf301528181610c5b01528181610c9301528181610cbb01528181610d0d01528181610d350152818161104b01528181611073015281816111b5015281816112140152818161123c01528181611469015281816114910152818161162e015281816116a00152818161205901528181612093015281816120f50152818161244c015281816129480152612a7d01526000818161043f01528181610ef80152818161159201528181611f9601528181611fbc01528181612342015281816123750152818161241701526127b601526000818161034a01528181611dae01528181611e7701528181611f1401528181611fdd015281816127940152818161283901526129170152600081816105a601528181611ab201528181611cef015281816124f2015261276701526135f26000f3fe608060405234801561001057600080fd5b50600436106101e25760003560e01c8062e9a407146101e75780630aaffd2a146101fc5780630d616d201461020f5780630d9639ba146102175780630dcf4b8f146102375780630ebb172a1461024d57806312fa329b1461026f578063200d2ed2146102905780632816ee73146102aa5780632c6cda93146102bd5780632c7baaf3146102d0578063313f336b1461030f5780633362379414610345578063356c299d1461037957806342e94c90146103b75780634564168b146103d75780634a387cdf146103f35780634bb278f3146103fb5780634e7320ce14610403578063565a9d481461041857806356d399e81461043a578063570ca7351461046157806358b810d3146104885780636786452c1461049b5780636cf72aa4146104ae5780637c0b4bfb146104c157806386fa5063146105005780638c7e7a31146105275780638dd085571461053a578063937e09b11461055a57806396a608c0146105625780639ca002a61461056a578063a4b6aa831461057d578063bc063e1a14610585578063c3aca558146105a1578063ccec3716146105c8578063d26146a5146105db578063d826f88f146105ee578063dda0a81a146105f6578063e020e4c41461060d578063e50d50d814610620575b600080fd5b6101fa6101f5366004612e8f565b610640565b005b6101fa61020a366004612ecb565b6106c2565b6101fa6106cc565b61021f6107b7565b60405161022e93929190612f5d565b60405180910390f35b61023f61096d565b60405190815260200161022e565b6102576201518081565b6040516001600160401b03909116815260200161022e565b61028261027d366004612fca565b6109d9565b60405161022e929190612fe3565b600f5461029d9060ff1681565b60405161022e9190613013565b6101fa6102b836600461303b565b610a12565b6101fa6102cb366004613079565b610a1d565b6102d8610a93565b60405161022e919081518152602080830151908201526040808301519082015260609182015161ffff169181019190915260800190565b6006546007546008546009546103259392919084565b60408051948552602085019390935291830152606082015260800161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b60405161022e9190613094565b6002546003546004546005546103939392919061ffff1684565b6040805194855260208501939093529183015261ffff16606082015260800161022e565b61023f6103c5366004612ecb565b600a6020526000908152604090205481565b6000546001546103e5919082565b60405161022e9291906130a8565b600c5461023f565b6101fa610af3565b61040b610b68565b60405161022e91906130dc565b600f5461042a90610100900460ff1681565b604051901515815260200161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa610496366004613103565b610bc0565b6101fa6104a93660046131f1565b610c88565b6101fa6104bc366004613242565b610d02565b6104eb6104cf366004612ecb565b600d602052600090815260409020805460019091015460ff1682565b6040805192835290151560208301520161022e565b61023f7f000000000000000000000000000000000000000000000000000000000000000081565b61023f61053536600461325f565b610d78565b61054d610548366004612fca565b610e09565b60405161022e91906132a6565b61023f610e62565b61023f610f62565b61023f610578366004612fca565b610fb9565b61023f610fce565b61058e61271081565b60405161ffff909116815260200161022e565b61036c7f000000000000000000000000000000000000000000000000000000000000000081565b6101fa6105d6366004612ecb565b611040565b61036c6105e9366004612fca565b6111df565b6101fa611209565b6105fe61127e565b60405161022e939291906132b4565b6101fa61061b3660046132ed565b61145e565b61023f61062e366004612ecb565b600b6020526000908152604090205481565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106b657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b60405180910390fd5b6106bf816114e3565b50565b6106bf33826118a8565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016330361070657610704611a29565b565b336000908152600b602052604081205461072090426133e4565b90506201518081101561076a57336000908152600b602052604090819020549051630429b06960e31b815260048101919091524260248201526201518060448201526064016106ad565b600061077533611b4c565b905080156107b35760405181815233907f249a82fbe5056a1940b4e996665ba2a82c340ae9fa1e069fd1ababf5508f396e9060200160405180910390a25b5050565b600e5460609081908190806001600160401b038111156107d9576107d9612d34565b604051908082528060200260200182016040528015610802578160200160208202803683370190505b509350806001600160401b0381111561081d5761081d612d34565b604051908082528060200260200182016040528015610846578160200160208202803683370190505b509250806001600160401b0381111561086157610861612d34565b60405190808252806020026020018201604052801561088a578160200160208202803683370190505b50915060005b81811015610966576000600e82815481106108ad576108ad6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912087519192509082908890859081106108eb576108eb6133f7565b60200260200101906001600160a01b031690816001600160a01b0316815250508060000154868481518110610922576109226133f7565b60209081029190910101526001810154855160ff9091169086908590811061094c5761094c6133f7565b911515602092830291909101909101525050600101610890565b5050909192565b600c54600090815b818110156109d4576000600c8281548110610992576109926133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506109c9908561340d565b935050600101610975565b505090565b600c81815481106109e957600080fd5b6000918252602090912060029091020180546001909101546001600160a01b0391821692501682565b6107b3338284611d49565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a8a57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf8161255d565b610ac26040518060800160405280600081526020016000815260200160008152602001600061ffff1681525090565b5060408051608081018252600254815260035460208201526004549181019190915260055461ffff16606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b6057337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6107046125e7565b610b936040518060800160405280600081526020016000815260200160008152602001600081525090565b50604080516080810182526006548152600754602082015260085491810191909152600954606082015290565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610c2d57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c35611a29565b610c3e8561255d565b610c47846114e3565b610c50836128a5565b8015610c8157610c817f00000000000000000000000000000000000000000000000000000000000000008383611d49565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610cf557337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610c8185858585856128bf565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610d6f57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6106bf816128a5565b6000828211610d9e5782826040516376675ed960e11b81526004016106ad9291906130a8565b82600003610dcf576004610db36001866133e4565b610dbd9190613420565b610dc890600161340d565b9050610e02565b6000610ddb84846133e4565b905080610de96001876133e4565b610df39190613420565b610dfe90600161340d565b9150505b9392505050565b610e11612c79565b600c8281548110610e2457610e246133f7565b60009182526020918290206040805180820190915260029092020180546001600160a01b039081168352600190910154169181019190915292915050565b600e5460009081908190815b81811015610ee5576000600e8281548110610e8b57610e8b6133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16610edb578054610ecb908761340d565b9550610ed860018661340d565b94505b5050600101610e6e565b50610f5a83610ef261096d565b610f1c907f00000000000000000000000000000000000000000000000000000000000000006133e4565b610f2691906133e4565b600c54610f3490859061340d565b7f0000000000000000000000000000000000000000000000000000000000000000610d78565b935050505090565b600c54600090610f725750600090565b600a6000600c600081548110610f8a57610f8a6133f7565b600091825260208083206002909202909101546001600160a01b03168352820192909252604001902054905090565b6000610fc88260006001610d78565b92915050565b600e54600090815b818110156109d4576000600e8281548110610ff357610ff36133f7565b60009182526020808320909101546001600160a01b0316808352600d909152604090912060018101549192509060ff16611036578054611033908661340d565b94505b5050600101610fd6565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110ad57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6003600f5460ff1660038111156110c6576110c6612ffd565b141580156110ea57506000600f5460ff1660038111156110e8576110e8612ffd565b145b1561110f57600f54604051632f3e69dd60e01b81526106ad9160ff1690600401613013565b6040516370a0823160e01b815281906000906001600160a01b038316906370a0823190611140903090600401613094565b602060405180830381865afa15801561115d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111819190613442565b9050600081116111a6578260405163e932c57360e01b81526004016106ad9190613094565b6111da6001600160a01b0383167f0000000000000000000000000000000000000000000000000000000000000000836129da565b505050565b600e81815481106111ef57600080fd5b6000918252602090912001546001600160a01b0316905081565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461127657337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b610704611a29565b600c5460609081908190806001600160401b038111156112a0576112a0612d34565b6040519080825280602002602001820160405280156112c9578160200160208202803683370190505b509350806001600160401b038111156112e4576112e4612d34565b60405190808252806020026020018201604052801561130d578160200160208202803683370190505b509250806001600160401b0381111561132857611328612d34565b604051908082528060200260200182016040528015611351578160200160208202803683370190505b50915060005b81811015610966576000600c8281548110611374576113746133f7565b60009182526020909120600290910201805487519192506001600160a01b0316908790849081106113a7576113a76133f7565b6001600160a01b039283166020918202929092010152600182015486519116908690849081106113d9576113d96133f7565b60200260200101906001600160a01b031690816001600160a01b031681525050600a600087848151811061140f5761140f6133f7565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020016000205484838151811061144a5761144a6133f7565b602090810291909101015250600101611357565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114cb57337f00000000000000000000000000000000000000000000000000000000000000006040516321afccb160e01b81526004016106ad929190612fe3565b6114da87878787878787612a32565b50505050505050565b6000600f5460ff1660038111156114fc576114fc612ffd565b1461152157600f546040516312ba63f960e11b81526106ad9160ff1690600401613013565b600e5460005b8181101561158057600d6000600e8381548110611546576115466133f7565b60009182526020808320909101546001600160a01b0316835282019290925260400181209081556001908101805460ff1916905501611527565b5061158d600e6000612c90565b5080517f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000010156116175781517f0000000000000000000000000000000000000000000000000000000000000000604051630962235760e31b81526004016106ad9291906130a8565b815160005b818110156118a257806000036116cd577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316848281518110611668576116686133f7565b6020026020010151600001516001600160a01b0316146116cd5760405163ef77b46760e01b8152600481018290526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660248201526044016106ad565b60006001600160a01b03168482815181106116ea576116ea6133f7565b6020026020010151600001516001600160a01b0316036117205760405163621caef560e11b8152600481018290526024016106ad565b6000600d6000868481518110611738576117386133f7565b6020026020010151600001516001600160a01b03166001600160a01b031681526020019081526020016000209050806000015460001461178e576040516308308c2360e21b8152600481018390526024016106ad565b60006117bb85847f0000000000000000000000000000000000000000000000000000000000000000610d78565b905060008684815181106117d1576117d16133f7565b6020026020010151602001519050818110156118065783818360405163c4aa8aa360e01b81526004016106ad9392919061345b565b8086101561182d57838187604051636d3ae3a760e01b81526004016106ad9392919061345b565b61183781876133e4565b9550600e87858151811061184d5761184d6133f7565b6020908102919091018101515182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155908355918201805460ff19169055500161161c565b50505050565b6001600f5460ff1660038111156118c1576118c1612ffd565b141580156118e657506002600f5460ff1660038111156118e3576118e3612ffd565b14155b1561190b576002546040516310004b0160e11b815260048101919091526024016106ad565b60006119178383612aa3565b600c549091506000908190815b818110156119bd576000600c8281548110611941576119416133f7565b6000918252602090912060029091020180549091506001600160a01b03808a169116036119b45760018101546001600160a01b03808816911603611989575050505050505050565b600190810180546001600160a01b038881166001600160a01b031983161790925516945092506119bd565b50600101611924565b50816119de5785604051632fd768d360e11b81526004016106ad9190613094565b856001600160a01b03167f25639ce407c98fba722dddf1f023c8242e3c77732cfe4660d1c04930bf4cbe728486604051611a19929190612fe3565b60405180910390a2505050505050565b600c5460005b81811015611aec576000600c8281548110611a4c57611a4c6133f7565b600091825260208083206002909202909101546001600160a01b0316808352600a9091526040909120549091506003600f5460ff166003811115611a9257611a92612ffd565b14158015611aa05750600081115b15611ad957611ad96001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001683836129da565b611ae282612ac1565b5050600101611a2f565b50611af9600c6000612cae565b600f805460ff191690556040805160008082526020820190925281611b40565b6040805180820190915260008082526020820152815260200190600190039081611b195790505b5090506107b3816114e3565b6001600160a01b0381166000908152600a602052604081205490819003611b7257919050565b611b7b82612ac1565b600c5460005b81811015611c9657600c8181548110611b9c57611b9c6133f7565b60009182526020909120600290910201546001600160a01b0390811690851603611c8e57600c611bcd6001846133e4565b81548110611bdd57611bdd6133f7565b9060005260206000209060020201600c8281548110611bfe57611bfe6133f7565b60009182526020909120825460029092020180546001600160a01b039283166001600160a01b0319918216178255600193840154939091018054939092169216919091179055600c805480611c5557611c55613471565b60008281526020902060026000199092019182020180546001600160a01b03199081168255600191909101805490911690559055611c96565b600101611b81565b506001600160a01b0383166000908152600d6020526040902060018101805460ff191690556003600f5460ff166003811115611cd457611cd4612ffd565b03611ce25760009250611d42565b611d166001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001685856129da565b6002600f5460ff166003811115611d2f57611d2f612ffd565b03611d4257600f805460ff191660011790555b5050919050565b6000600f5460ff166003811115611d6257611d62612ffd565b14158015611d8757506001600f5460ff166003811115611d8457611d84612ffd565b14155b15611dac57600f54604051630295f95f60e51b81526106ad9160ff1690600401613013565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e2e9190613442565b7f000000000000000000000000000000000000000000000000000000000000000014611f12577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166386fa50636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ef79190613442565b604051631248081b60e31b81526004016106ad9291906130a8565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f949190613442565b7f000000000000000000000000000000000000000000000000000000000000000014612039577f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ed3573d6000803e3d6000fd5b6000600f5460ff16600381111561205257612052612ffd565b03612145577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316146120cd57827f00000000000000000000000000000000000000000000000000000000000000006040516316a4ebc160e21b81526004016106ad929190612fe3565b600f805460ff1916600117905560025460055460405161ffff90911681526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907fe52a01bb4ffcc3baf2c5d17111d7d5b676a7485809eb10671092a89d2bb22ff69060200160405180910390a35b6001600160a01b0383166000908152600d602052604090208054158015906121725750600181015460ff16155b156121b257805482101561219d57805460405163454cd8e160e01b81526106ad9184916004016130a8565b6001818101805460ff19169091179055612207565b6001600160a01b0384166000908152600a60205260409020541580156121de57506121db610e62565b82105b1561220757816121ec610e62565b6040516310e45ff160e21b81526004016106ad9291906130a8565b6001600160a01b0384166000908152600a602052604081205490036122d85760006122328585612aa3565b604080518082019091526001600160a01b03808816825291821660208201908152600c8054600181018255600091909152915160029092027fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7810180549385166001600160a01b031994851617905590517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c89091018054919093169116179055506122e2565b6122e284846118a8565b6001600160a01b0384166000908152600a60205260408120805484929061230a90849061340d565b90915550506001600160a01b0384166000908152600b6020526040812042905561233261096d565b9050600061233e610fce565b90507f000000000000000000000000000000000000000000000000000000000000000061236b828461340d565b11156123b05781817f000000000000000000000000000000000000000000000000000000000000000060405163513b428b60e11b81526004016106ad9392919061345b565b600c547f00000000000000000000000000000000000000000000000000000000000000001015612415576040516315b5c3d560e21b81527f000000000000000000000000000000000000000000000000000000000000000060048201526024016106ad565b7f000000000000000000000000000000000000000000000000000000000000000082036124a2576002546040516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691907f48996614f3380f3dcd5a2d2e6eaddfd66207ff3457fee339e09b4735bc9ec95890600090a3600f805460ff191660021790555b856001600160a01b03167fbdaa686eb6f59012d211a74523da260341c516896e9e5be954163d6ecf26ffa2856040516124dd91815260200190565b60405180910390a261251a6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016873087612ae8565b6002600f5460ff16600381111561253357612533612ffd565b1480156125485750600f54610100900460ff16155b15612555576125556125e7565b505050505050565b6000600f5460ff16600381111561257657612576612ffd565b1461259b57600f5460405163ad88fc8f60e01b81526106ad9160ff1690600401613013565b61271061ffff821611156125cf57604051624ae8fd60e41b815261ffff8216600482015261271060248201526044016106ad565b6005805461ffff191661ffff92909216919091179055565b6002600f5460ff16600381111561260057612600612ffd565b1461262557600f546040516383696f6f60e01b81526106ad9160ff1690600401613013565b600f805460ff191660031790556002546040517f839cf22e1ba87ce2f5b9bbf46cf0175a09eed52febdfaac8852478e68203c76390600090a2600c546000816001600160401b0381111561267b5761267b612d34565b6040519080825280602002602001820160405280156126b457816020015b6126a1612ccf565b8152602001906001900390816126995790505b50905060005b8281101561274f576000600c82815481106126d7576126d76133f7565b6000918252602080832060408051608081018252600290940290910180546001600160a01b03908116858401818152600184015490921660608701529085528552600a83529320549082015284519192509084908490811061273b5761273b6133f7565b6020908102919091010152506001016126ba565b5060405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906127de907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090600401613487565b6020604051808303816000875af11580156127fd573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061282191906134a0565b50604051632f1fbfb360e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bc7efecc90612877906000906006906002908790600401613504565b600060405180830381600087803b15801561289157600080fd5b505af1158015612555573d6000803e3d6000fd5b600f80549115156101000261ff0019909216919091179055565b6000600f5460ff1660038111156128d8576128d8612ffd565b146128fd57600f54604051639a0293fd60e01b81526106ad9160ff1690600401613013565b60405160016226579360e01b031981526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063ffd9a86d9061297290889088907f000000000000000000000000000000000000000000000000000000000000000090899060040161357b565b600060405180830381600087803b15801561298c57600080fd5b505af11580156129a0573d6000803e3d6000fd5b5050865160005550506020948501516001558351600655938301516007556040830151600855606090920151600955600255600355600455565b6111da83846001600160a01b031663a9059cbb8585604051602401612a00929190613487565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050612b21565b612a3a611a29565b612a5387878760000151886020015189604001516128bf565b612a60856060015161255d565b612a69846114e3565b612a72836128a5565b80156114da576114da7f00000000000000000000000000000000000000000000000000000000000000008383611d49565b60006001600160a01b03821615612aba5781610e02565b5090919050565b6001600160a01b03166000908152600a60209081526040808320839055600b909152812055565b6040516001600160a01b0384811660248301528381166044830152606482018390526118a29186918216906323b872dd90608401612a00565b6000612b366001600160a01b03841683612b7b565b90508051600014158015612b5b575080806020019051810190612b5991906134a0565b155b156111da5782604051635274afe760e01b81526004016106ad9190613094565b6060610e028383600084600080856001600160a01b03168486604051612ba191906135b6565b60006040518083038185875af1925050503d8060008114612bde576040519150601f19603f3d011682016040523d82523d6000602084013e612be3565b606091505b5091509150612bf3868383612bfd565b9695505050505050565b606082612c1257612c0d82612c50565b610e02565b8151158015612c2957506001600160a01b0384163b155b15612c495783604051639996b31560e01b81526004016106ad9190613094565b5080610e02565b805115612c605780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b604080518082019091526000808252602082015290565b50805460008255906000526020600020908101906106bf9190612cef565b50805460008255600202906000526020600020908101906106bf9190612d08565b6040518060400160405280612ce2612c79565b8152602001600081525090565b5b80821115612d045760008155600101612cf0565b5090565b5b80821115612d045780546001600160a01b031990811682556001820180549091169055600201612d09565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715612d6c57612d6c612d34565b60405290565b604051608081016001600160401b0381118282101715612d6c57612d6c612d34565b604051601f8201601f191681016001600160401b0381118282101715612dbc57612dbc612d34565b604052919050565b80356001600160a01b0381168114612ddb57600080fd5b919050565b600082601f830112612df157600080fd5b81356001600160401b03811115612e0a57612e0a612d34565b612e1960208260051b01612d94565b8082825260208201915060208360061b860101925085831115612e3b57600080fd5b602085015b83811015612e855760408188031215612e5857600080fd5b612e60612d4a565b612e6982612dc4565b8152602082810135818301529084529290920191604001612e40565b5095945050505050565b600060208284031215612ea157600080fd5b81356001600160401b03811115612eb757600080fd5b612ec384828501612de0565b949350505050565b600060208284031215612edd57600080fd5b610e0282612dc4565b600081518084526020840193506020830160005b82811015612f215781516001600160a01b0316865260209586019590910190600101612efa565b5093949350505050565b600081518084526020840193506020830160005b82811015612f21578151865260209586019590910190600101612f3f565b606081526000612f706060830186612ee6565b8281036020840152612f828186612f2b565b83810360408501528451808252602080870193509091019060005b81811015612fbd5783511515835260209384019390920191600101612f9d565b5090979650505050505050565b600060208284031215612fdc57600080fd5b5035919050565b6001600160a01b0392831681529116602082015260400190565b634e487b7160e01b600052602160045260246000fd5b602081016004831061303557634e487b7160e01b600052602160045260246000fd5b91905290565b6000806040838503121561304e57600080fd5b8235915061305e60208401612dc4565b90509250929050565b803561ffff81168114612ddb57600080fd5b60006020828403121561308b57600080fd5b610e0282613067565b6001600160a01b0391909116815260200190565b918252602082015260400190565b805182526020810151602083015260408101516040830152606081015160608301525050565b60808101610fc882846130b6565b80151581146106bf57600080fd5b8035612ddb816130ea565b600080600080600060a0868803121561311b57600080fd5b61312486613067565b945060208601356001600160401b0381111561313f57600080fd5b61314b88828901612de0565b945050604086013561315c816130ea565b925061316a60608701612dc4565b949793965091946080013592915050565b60006040828403121561318d57600080fd5b613195612d4a565b823581526020928301359281019290925250919050565b6000608082840312156131be57600080fd5b6131c6612d72565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b6000806000806000610120868803121561320a57600080fd5b613214878761317b565b945061322387604088016131ac565b949794965050505060c08301359260e081013592610100909101359150565b60006020828403121561325457600080fd5b8135610e02816130ea565b60008060006060848603121561327457600080fd5b505081359360208301359350604090920135919050565b80516001600160a01b03908116835260209182015116910152565b60408101610fc8828461328b565b6060815260006132c76060830186612ee6565b82810360208401526132d98186612ee6565b90508281036040840152612bf38185612f2b565b60008060008060008060008789036101c081121561330a57600080fd5b6133148a8a61317b565b97506133238a60408b016131ac565b9650608060bf198201121561333757600080fd5b50613340612d72565b60c0890135815260e0890135602082015261010089013560408201526133696101208a01613067565b606082015294506101408801356001600160401b0381111561338a57600080fd5b6133968a828b01612de0565b9450506133a661016089016130f8565b92506133b56101808901612dc4565b9699959850939692959194919350506101a09091013590565b634e487b7160e01b600052601160045260246000fd5b81810381811115610fc857610fc86133ce565b634e487b7160e01b600052603260045260246000fd5b80820180821115610fc857610fc86133ce565b60008261343d57634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561345457600080fd5b5051919050565b9283526020830191909152604082015260600190565b634e487b7160e01b600052603160045260246000fd5b6001600160a01b03929092168252602082015260400190565b6000602082840312156134b257600080fd5b8151610e02816130ea565b600081518084526020840193506020830160005b82811015612f215781516134e687825161328b565b602090810151604088015260609096019591909101906001016134d1565b8454815260018501546020820152835460408201526001840154606082015260028401546080820152600384015460a0820152825460c0820152600183015460e0820152600283015461010082015261ffff6003840154166101208201526101606101408201526000612bf36101608301846134bd565b8451815260208086015190820152610100810161359b60408301866130b6565b6001600160a01b039390931660c082015260e0015292915050565b6000825160005b818110156135d757602081860181015185830152016135bd565b50600092019182525091905056fea164736f6c634300081a000aa164736f6c634300081a000a", "linkReferences": {}, "deployedLinkReferences": {} } diff --git a/src/web3client/abis/ServiceNodeRewards.json b/src/web3client/abis/ServiceNodeRewards.json new file mode 100644 index 0000000..f21f1ef --- /dev/null +++ b/src/web3client/abis/ServiceNodeRewards.json @@ -0,0 +1,2226 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "ServiceNodeRewards", + "sourceName": "contracts/ServiceNodeRewards.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "BLSPubkeyAlreadyExists", + "type": "error" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "BLSPubkeyDoesNotExist", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "BLSPubkeyDoesNotMatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "address", + "name": "contributor", + "type": "address" + } + ], + "name": "CallerNotContributor", + "type": "error" + }, + { + "inputs": [], + "name": "ClaimThresholdExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "ContractAlreadyStarted", + "type": "error" + }, + { + "inputs": [], + "name": "ContractNotStarted", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "required", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "provided", + "type": "uint256" + } + ], + "name": "ContributionTotalMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "DeleteSentinelNodeNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "Ed25519PubkeyAlreadyExists", + "type": "error" + }, + { + "inputs": [], + "name": "EnforcedPause", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "addedTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currenttime", + "type": "uint256" + } + ], + "name": "ExitTooEarly", + "type": "error" + }, + { + "inputs": [], + "name": "ExpectedPause", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "contributor", + "type": "address" + } + ], + "name": "FirstContributorMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "numSigners", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requiredSigners", + "type": "uint256" + } + ], + "name": "InsufficientBLSSignatures", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientContributors", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientNodes", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidBLSProofOfPossession", + "type": "error" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "aggPubkey", + "type": "tuple" + } + ], + "name": "InvalidBLSSignature", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "LeaveRequestNotInitiatedYet", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "endTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currTimestamp", + "type": "uint256" + } + ], + "name": "LeaveRequestTooEarly", + "type": "error" + }, + { + "inputs": [], + "name": "LiquidatorPenaltyTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "MaxClaimExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "MaxContributorsExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "MaxPubkeyAggregationsExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "NullAddress", + "type": "error" + }, + { + "inputs": [], + "name": "NullBLSPubkey", + "type": "error" + }, + { + "inputs": [], + "name": "NullEd25519Pubkey", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "PositiveNumberRequirement", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "expectedRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "providedRecipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "serviceNodeID", + "type": "uint256" + } + ], + "name": "RecipientAddressDoesNotMatch", + "type": "error" + }, + { + "inputs": [], + "name": "RecipientRewardsTooLow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "ServiceNodeDoesntExist", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currenttime", + "type": "uint256" + } + ], + "name": "SignatureExpired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "address", + "name": "contributor", + "type": "address" + } + ], + "name": "SmallContributorLeaveTooEarly", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newMax", + "type": "uint256" + } + ], + "name": "BLSNonSignerThresholdMaxUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "ClaimCycleUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "ClaimThresholdUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "liquidatorRatio", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "poolRatio", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "recipientRatio", + "type": "uint256" + } + ], + "name": "LiquidationRatiosUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "blsPubkey", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ed25519Pubkey", + "type": "uint256" + } + ], + "name": "NewSeededServiceNode", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature2", + "type": "uint256" + }, + { + "internalType": "uint16", + "name": "fee", + "type": "uint16" + } + ], + "indexed": false, + "internalType": "struct IServiceNodeRewards.ServiceNodeParams", + "name": "serviceNode", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "internalType": "struct IServiceNodeRewards.Staker", + "name": "staker", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "stakedAmount", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct IServiceNodeRewards.Contributor[]", + "name": "contributors", + "type": "tuple[]" + } + ], + "name": "NewServiceNodeV2", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "previousBalance", + "type": "uint256" + } + ], + "name": "RewardsBalanceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RewardsClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "returnedAmount", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeExit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "contributor", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeExitRequest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeLiquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newExpiry", + "type": "uint256" + } + ], + "name": "SignatureExpiryUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newRequirement", + "type": "uint256" + } + ], + "name": "StakingRequirementUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "LIST_SENTINEL", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_PERMITTED_PUBKEY_AGGREGATIONS_LOWER_BOUND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_SERVICE_NODE_EXIT_WAIT_TIME", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_TIME_BEFORE_REPEATED_LEAVE_REQUEST", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SMALL_CONTRIBUTOR_DIVISOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SMALL_CONTRIBUTOR_LEAVE_DELAY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "blsPubkey", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.BLSSignatureParams", + "name": "blsSignature", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature2", + "type": "uint256" + }, + { + "internalType": "uint16", + "name": "fee", + "type": "uint16" + } + ], + "internalType": "struct IServiceNodeRewards.ServiceNodeParams", + "name": "serviceNodeParams", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "internalType": "struct IServiceNodeRewards.Staker", + "name": "staker", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "stakedAmount", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.Contributor[]", + "name": "contributors", + "type": "tuple[]" + } + ], + "name": "addBLSPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "aggregatePubkey", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allServiceNodeIDs", + "outputs": [ + { + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point[]", + "name": "pubkeys", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blsNonSignerThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blsNonSignerThresholdMax", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimCycle", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "claimRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentClaimCycle", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentClaimTotal", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "designatedToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "ed25519Pubkey", + "type": "uint256" + } + ], + "name": "ed25519ToServiceNodeID", + "outputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "exitBLSPublicKeyAfterWaitTime", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "blsPubkey", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.BLSSignatureParams", + "name": "blsSignature", + "type": "tuple" + }, + { + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "exitBLSPublicKeyWithSignature", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "exitTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "foundationPool", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hashToG2Tag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "address", + "name": "foundationPool_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "stakingRequirement_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxContributors_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidatorRewardRatio_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "poolShareOfLiquidationRatio_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "recipientRatio_", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "initiateExitBLSPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isStarted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastHeightPubkeyWasAggregated", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "blsPubkey", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.BLSSignatureParams", + "name": "blsSignature", + "type": "tuple" + }, + { + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "liquidateBLSPublicKeyWithSignature", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidateTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liquidatorRewardRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxContributors", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxPermittedPubkeyAggregations", + "outputs": [ + { + "internalType": "uint256", + "name": "result", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nextServiceNodeID", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "numPubkeyAggregationsForHeight", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "poolShareOfLiquidationRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proofOfPossessionTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "recipientRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "recipients", + "outputs": [ + { + "internalType": "uint256", + "name": "rewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "claimed", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rederiveTotalNodesAndAggregatePubkey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "blsPubkey", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "ed25519Pubkey", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "addedTimestamp", + "type": "uint256" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "internalType": "struct IServiceNodeRewards.Staker", + "name": "staker", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "stakedAmount", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.Contributor[]", + "name": "contributors", + "type": "tuple[]" + } + ], + "internalType": "struct IServiceNodeRewards.SeedServiceNode[]", + "name": "nodes", + "type": "tuple[]" + } + ], + "name": "seedPublicKeyList", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "blsPubkey", + "type": "bytes" + } + ], + "name": "serviceNodeIDs", + "outputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "serviceNodes", + "outputs": [ + { + "components": [ + { + "internalType": "uint64", + "name": "next", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "prev", + "type": "uint64" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "blsPubkey", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "addedTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "leaveRequestTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "latestLeaveRequestTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deposit", + "type": "uint256" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "internalType": "struct IServiceNodeRewards.Staker", + "name": "staker", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "stakedAmount", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.Contributor[]", + "name": "contributors", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "ed25519Pubkey", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.ServiceNode", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newMax", + "type": "uint256" + } + ], + "name": "setBLSNonSignerThresholdMax", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "setClaimCycle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newMax", + "type": "uint256" + } + ], + "name": "setClaimThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "liquidator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pool", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "recipient", + "type": "uint256" + } + ], + "name": "setLiquidationRatios", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newExpiry", + "type": "uint256" + } + ], + "name": "setSignatureExpiry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newRequirement", + "type": "uint256" + } + ], + "name": "setStakingRequirement", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "signatureExpiry", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakingRequirement", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "start", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalNodes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "recipientRewards", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.BLSSignatureParams", + "name": "blsSignature", + "type": "tuple" + }, + { + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "updateRewardsBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "blsPubkey", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.BLSSignatureParams", + "name": "blsSignature", + "type": "tuple" + }, + { + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" + } + ], + "name": "validateProofOfPossession", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x6080604052348015600f57600080fd5b506157e98061001f6000396000f3fe608060405234801561001057600080fd5b50600436106102e05760003560e01c8063040f9853146102e5578063095f14041461030e5780630962ef79146103255780630bd1b4191461033a57806317f33a7d146103435780631f4c49301461036a5780631f744b541461037d578063245fc1831461038557806334fde37614610398578063372500ab146103a05780633f4ba83a146103a857806343039d90146103b0578063538d2709146103c3578063544736e6146103cc57806356d399e8146103e957806358e93308146103f25780635c975abb146103fb578063623b94221461040357806362f1fbc21461040c57806365ca819d146104165780636b348ab41461044a578063715018a61461045357806379ba50971461045b5780637a6d4065146104635780637c89d2f0146104765780638328b6101461049b5780638456cb59146104ae57806386e76685146104b657806386fa5063146104bf578063879d0f0c146104c85780638a2209e6146104d05780638a399481146104e55780638da5cb5b146104ee5780639156ac80146104f657806395055ffd146104ff5780639592d424146105125780639c80ebee1461051b5780639cebc4741461040c5780639e3b372d14610523578063a44285a51461052c578063ab0122ae14610535578063abf2c50314610548578063ae6c60631461055e578063b13aaebd14610567578063b687f85c14610570578063bc7efecc14610578578063be9a65551461058b578063c0ebedab14610593578063c4f56d9a146105bc578063d5a81254146105cf578063d84f0bf8146105d8578063d9054b09146105e1578063da1d5435146105f4578063dd644d74146105fd578063e30c397814610606578063e58598131461060e578063eb82031214610621578063edbf4ac214610656578063ee94e41214610669578063f2fde38b14610672578063f324c8eb14610685578063f88d658b14610698578063f907f5fc146106ab578063ffd9a86d146106be575b600080fd5b6102f86102f3366004614afd565b6106d1565b6040516103059190614b92565b60405180910390f35b610317600d5481565b604051908152602001610305565b610338610333366004614c50565b610802565b005b610317600f5481565b60015461035d90600160a01b90046001600160401b031681565b6040516103059190614c69565b610338610378366004614c7d565b61080f565b610338610aba565b610338610393366004614c50565b610b8a565b610317610bef565b610338610c26565b610338610c54565b6103386103be366004614c50565b610c66565b61031760075481565b6000546103d99060ff1681565b6040519015158152602001610305565b610317600b5481565b610317600e5481565b6103d9610cc4565b61031760155481565b61031762278d0081565b61035d610424366004614d82565b80516020818301810180516012825292820191909301209152546001600160401b031681565b610317600a5481565b610338610cd9565b610338610ceb565b610338610471366004614ed8565b610d27565b60005461048e9061010090046001600160a01b031681565b6040516103059190614f42565b6103386104a9366004614c50565b610ec5565b610338610f22565b61031760045481565b610317600c5481565b610317601481565b6104d8610f32565b6040516103059190614f56565b61031760035481565b61048e610f55565b61031760195481565b61033861050d366004614f64565b610f70565b61031760025481565b61035d600081565b61031760095481565b61031760175481565b610338610543366004614afd565b610ff9565b6105506110c2565b604051610305929190614fbd565b61031760065481565b61031760185481565b610317600481565b610338610586366004615187565b61124d565b610338611576565b61035d6105a1366004614c50565b601b602052600090815260409020546001600160401b031681565b6103386105ca366004614f64565b61158d565b61031760055481565b61031760085481565b6103386105ef366004614afd565b611742565b61031760165481565b610317601a5481565b61048e611754565b61033861061c366004615234565b61175f565b61064861062f366004615260565b6011602052600090815260409020805460019091015482565b60405161030592919061527d565b61033861066436600461528b565b611812565b610317610e1081565b610338610680366004615260565b611b80565b610338610693366004614c50565b611bf1565b6103386106a6366004614c50565b611c4f565b60015461048e906001600160a01b031681565b6103386106cc3660046152ed565b611cad565b6106d9614978565b6001600160401b03808316600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e0860152600881018054835181860281018601909452808452919461010087019491929184015b828210156107e9576000848152602090819020604080516080810182526003860290920180546001600160a01b0390811692840192835260018083015490911660608501529183526002015482840152908352909201910161078c565b5050505081526020016009820154815250509050919050565b61080c3382611cbf565b50565b610817611dfa565b60005460ff161561083b57604051630a35d5c360e31b815260040160405180910390fd5b8060005b81811015610aac573684848381811061085a5761085a61533d565b905060200281019061086c9190615353565b9050600061087d6080830183615373565b90501161089d57604051631a10033d60e11b815260040160405180910390fd5b600c546108ad6080830183615373565b905011156108ce5760405163226c2d8360e21b815260040160405180910390fd5b6000806108ed6108e3368590038501856153bb565b8460400135611e2c565b600b54600782015590925090506109076080840184615373565b60008181106109185761091861533d565b61092e9260206060909202019081019150615260565b6001820180546001600160a01b0319166001600160a01b03929092169190911790556060830135600482015560008061096a6080860186615373565b9050905060005b81811015610a1857366109876080880188615373565b838181106109975761099761533d565b90506060020190508060400135846109af91906153ed565b935060006109c06020830183615260565b6001600160a01b0316036109e75760405163e99d5ac560e01b815260040160405180910390fd5b60088501805460018101825560009182526020909120829160030201610a0d8282615420565b505050600101610971565b50600b548214610a4a57600b54826040516367d22bd960e01b8152600401610a4192919061527d565b60405180910390fd5b604080518635815260208088013590820152868201358183015290516001600160401b038616917f34be2fa283e1d5eb453459898f160448257aa1ef0f7dd2c3a3b55c45d1af768f919081900360600190a2600186019550505050505061083f565b50610ab5612160565b505050565b600060028190558052601060205260008051602061577c833981519152546001600160401b03165b6001600160401b0381161561080c576001600160401b0381166000908152601060205260408120600254909103610b285760028101546013556003810154601455610b70565b6040805180820182526013548152601454602080830191909152825180840190935260028401548352600384015490830152610b639161218c565b8051601355602001516014555b546002805460010190556001600160401b03169050610ae2565b610b92611dfa565b60008111610bb357604051630ef6cf4560e41b815260040160405180910390fd5b60188190556040518181527fa83e79dc52a438d552d0631ce4fbfe7dec7656d10398a2849f352a6ab4f0163b906020015b60405180910390a150565b60008060646002546002610c039190615460565b610c0d919061548d565b905060148111610c1e576014610c20565b805b91505090565b336000908152601160205260408120600181015490549091610c4883836154a1565b9050610ab53382611cbf565b610c5c611dfa565b610c64612238565b565b610c6e611dfa565b60008111610c8f57604051630ef6cf4560e41b815260040160405180910390fd5b60048190556040518181527f0ac8ee09138dfaf5e3ebe4cb4fd42dd1a0695535a530171223fb5066f52e0e3b90602001610be4565b600080610ccf612284565b5460ff1692915050565b610ce1611dfa565b610c6460006122a8565b3380610cf5611754565b6001600160a01b031614610d1e578060405163118cdaa760e01b8152600401610a419190614f42565b61080c816122a8565b610d2f6122cf565b60005460ff16610d525760405163348b55eb60e21b815260040160405180910390fd5b8051600354811115610d975780600254610d6c91906154a1565b600354600254610d7c91906154a1565b604051635eee4a3560e01b8152600401610a4192919061527d565b6001600160a01b038516610dbe5760405163e99d5ac560e01b815260040160405180910390fd5b6001600160a01b0385166000908152601160205260409020548411610df6576040516333938e6360e01b815260040160405180910390fd5b6007546040805160208101929092526001600160601b0319606088901b16908201526054810185905260009060740160405160208183030381529060405290506000610e4482600a546122f5565b9050610e5f84610e59368890038801886154b4565b836123b5565b50506001600160a01b0385166000818152601160205260409081902080549087905590519091907f95390641529563dbfb446535fa996c5ac3be00f90f5705b3abda59a4467b797f90610eb5908890859061527d565b60405180910390a2505050505050565b610ecd611dfa565b60008111610eee57604051630ef6cf4560e41b815260040160405180910390fd5b600b8190556040518181527e6b7a1ea14ff2794527a64af37d55a2040e351f8b4c1adcdc9aea80d64e042990602001610be4565b610f2a611dfa565b610c646124f7565b610f3a6149d2565b50604080518082019091526013548152601454602082015290565b600080610f6061253e565b546001600160a01b031692915050565b610f786122cf565b60005460ff16610f9b5760405163348b55eb60e21b815260040160405180910390fd5b8051600354811115610fb55780600254610d6c91906154a1565b6000610fc686868660085487612562565b506001600160401b038116600090815260106020526040902060070154909150610ff190829061280f565b505050505050565b6110016122cf565b60005460ff166110245760405163348b55eb60e21b815260040160405180910390fd5b6001600160401b038116600090815260106020526040812060050154908190036110635781604051630cf4827360e31b8152600401610a419190614c69565b600061107262278d00836153ed565b90508042101561109b57828142604051637a54a45360e11b8152600401610a41939291906154d0565b6001600160401b038316600090815260106020526040902060070154610ab590849061280f565b6060806002546001600160401b038111156110df576110df614cf2565b604051908082528060200260200182016040528015611108578160200160208202803683370190505b5091506002546001600160401b0381111561112557611125614cf2565b60405190808252806020026020018201604052801561115e57816020015b61114b6149d2565b8152602001906001900390816111435790505b506000808052601060205260008051602061577c833981519152549192506001600160401b03909116905b6001600160401b03821615611247576001600160401b038083166000908152601060205260409020855190918491879185169081106111ca576111ca61533d565b60200260200101906001600160401b031690816001600160401b031681525050806002016040518060400160405290816000820154815260200160018201548152505084836001600160401b0316815181106112285761122861533d565b6020908102919091010152546001600160401b03169150600101611189565b50509091565b6112556122cf565b60005460ff166112785760405163348b55eb60e21b815260040160405180910390fd5b8051600c5481111561129d5760405163226c2d8360e21b815260040160405180910390fd5b8015611311576000805b828110156112e2578381815181106112c1576112c161533d565b602002602001015160200151826112d891906153ed565b91506001016112a7565b50600b54811461130b57600b54816040516367d22bd960e01b8152600401610a4192919061527d565b506113a6565b60408051600180825281830190925290816020015b604080516080810182526000918101828152606082018390528152602081019190915281526020019060019003908161132657505060408051608081018252339181018281526060820192909252908152600b54602082015281519193509083906000906113965761139661533d565b6020026020010181905250815190505b600060126113b3876128b2565b6040516113c09190615515565b908152604051908190036020019020546001600160401b0316905080156113fc578060405163459c639360e01b8152600401610a419190614c69565b6000836000815181106114115761141161533d565b60200260200101516000015160000151905061143387878388600001516128db565b600080611444898860000151611e2c565b600b5460078201556001810180546001600160a01b0319166001600160a01b038716179055909250905060005b858110156114f8578160080187828151811061148f5761148f61533d565b60209081029190910181015182546001808201855560009485529383902082518051600390930290910180546001600160a01b03199081166001600160a01b03948516178255918501518187018054909316931692909217905591015160029091015501611471565b50611501612160565b816001600160401b03167f533c16cb4158fe7c77021b90e9290bbefa457dd392bd8abc1eab59747834fd5b848b8a8a6040516115409493929190615527565b60405180910390a261156b600060019054906101000a90046001600160a01b03163330600b546129c5565b505050505050505050565b61157e611dfa565b6000805460ff19166001179055565b6115956122cf565b60005460ff166115b85760405163348b55eb60e21b815260040160405180910390fd5b80516003548111156115d25780600254610d6c91906154a1565b6000806115e487878760095488612562565b91509150816001600160401b03167f0bfb12191b00293af29126b1c5489f8daeb4a4af82db2960b7f8353c3105cd7c8260400151836060015160405161162b9291906155c0565b60405180910390a26000600f54600d54600e5461164891906153ed565b61165291906153ed565b905060008260e001519050600082600d548361166e9190615460565b611678919061548d565b90506000600e548361168a9190615460565b156116c457836001600e54856116a09190615460565b6116aa91906154a1565b6116b4919061548d565b6116bf9060016153ed565b6116c7565b60005b90506116e786826116d885876154a1565b6116e291906154a1565b61280f565b600d541561170b5760005461170b9061010090046001600160a01b03163384612a2c565b600e541561173557600054600154611735916001600160a01b036101009091048116911683612a2c565b5050505050505050505050565b61174a6122cf565b61080c8133612a5d565b600080610f60612c2d565b611767611dfa565b6000811161178857604051630ef6cf4560e41b815260040160405180910390fd5b8061179383856153ed565b61179e906003615460565b11156117bd57604051630b06449d60e41b815260040160405180910390fd5b600d839055600e829055600f81905560408051848152602081018490529081018290527f0f69a2a87c90cdbb1a6a46bbb4870f2edfbd516244285ab6ffed6460ac2c6a839060600160405180910390a1505050565b600061181c612c51565b805490915060ff600160401b82041615906001600160401b03166000811580156118435750825b90506000826001600160401b0316600114801561185f5750303b155b90508115801561186d575080155b1561188b5760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b031916600117855583156118b457845460ff60401b1916600160401b1785555b60018610156118d657604051630ef6cf4560e41b815260040160405180910390fd5b856118e1888a6153ed565b6118ec906003615460565b111561190b57604051630b06449d60e41b815260040160405180910390fd5b6000805460ff19168155600281905560035561012c60045560408051808201909152601b81527a0424c535f5349475f545259414e44494e4352454d454e545f504f5602c1b602082015261195e90612c75565b60065560408051808201909152601e81527f424c535f5349475f545259414e44494e4352454d454e545f5245574152440000602082015261199e90612c75565b60075560408051808201909152601c81527b109314d7d4d251d7d51496505391125390d4915351539517d156125560221b60208201526119dd90612c75565b600881905550611a0460405180606001604052806021815260200161579c60219139612c75565b600955604080518082019091526019815278424c535f5349475f484153485f544f5f4649454c445f54414760381b6020820152611a4090612c75565b600a5561025860055566038d7ea4c6800060175561a8c060185560006019819055601a8190558054610100600160a81b0319166101006001600160a01b038f811691909102919091178255600180546001600160a01b031916918e16919091178155600b8c9055600c8b9055600d8a9055600e899055600f889055611ac591906155dd565b600180546001600160401b0392909216600160a01b02600160a01b600160e01b031990921691909117905560008052601060205260008051602061577c83398151915280546001600160801b0319169055611b1f33612ca9565b611b27612cba565b8315611b7257845460ff60401b191685556040517fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290611b6990600190614c69565b60405180910390a15b505050505050505050505050565b611b88611dfa565b6000611b92612c2d565b80546001600160a01b0319166001600160a01b0384169081178255909150611bb8610f55565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b611bf9611dfa565b60008111611c1a57604051630ef6cf4560e41b815260040160405180910390fd5b60178190556040518181527fe604909b918af52702fa14cd9b5a8870e5bd66da3163365671e583f705fd546390602001610be4565b611c57611dfa565b60008111611c7857604051630ef6cf4560e41b815260040160405180910390fd5b60058190556040518181527ffbbc3d0a51e101ce4a7fda20e6e4eb0e230bf7de27ee2e3683fc8539e376639590602001610be4565b611cb9848484846128db565b50505050565b6001600160a01b0382166000908152601160205260408120600181015490549091611cea83836154a1565b905080841115611d0d5760405163363cc6b360e21b815260040160405180910390fd5b600060185442611d1d919061548d565b9050601a54811115611d3457601a81905560006019555b8460196000828254611d4691906153ed565b90915550506017546019541115611d7057604051638c10944b60e01b815260040160405180910390fd5b6001600160a01b03861660009081526011602052604081206001018054879290611d9b9084906153ed565b90915550506040518581526001600160a01b038716907ffc30cddea38e2bf4d6ea7d3f9ed3b6ad7f176419f4963bd81318067a4aee73fe9060200160405180910390a2600054610ff19061010090046001600160a01b03168787612a2c565b33611e03610f55565b6001600160a01b031614610c64573360405163118cdaa760e01b8152600401610a419190614f42565b815160009081901580611e4157506020840151155b15611e5f5760405163139b722760e31b815260040160405180910390fd5b82600003611e8057604051634eb8f11f60e01b815260040160405180910390fd5b6000611e8b856128b2565b905060006001600160401b0316601282604051611ea89190615515565b908152604051908190036020019020546001600160401b031614611f0857601281604051611ed69190615515565b9081526040519081900360200181205463459c639360e01b8252610a41916001600160401b0390911690600401614c69565b6000848152601b60205260409020546001600160401b031615611f58576000848152601b602052604090819020549051630cb4c72160e21b8152610a41916001600160401b031690600401614c69565b60005460ff1615611fbd57436015541015611f77574360155560006016555b60168054906000611f87836155fc565b91905055506000611f96610bef565b9050806016541115611fbb5760405163689f6e7560e11b815260040160405180910390fd5b505b60018054600160a01b90046001600160401b0316906014611fdd83615615565b91906101000a8154816001600160401b0302191690836001600160401b031602179055509250600260008154612012906155fc565b909155506001600160401b03838116600081815260106020908152604080832060008051602061577c833981519152805482546001600160801b031916600160401b918290049098168082029890981783558154600160401b600160801b03191690870217815586855282852080546001600160401b031990811688179091558c5160028401558c8501516003840155426004840155600983018c90558b8652601b90945293829020805490931690941790915551919450919085906012906120dc908690615515565b90815260405190819003602001902080546001600160401b03929092166001600160401b03199092169190911790556002546001036121275786516013556020870151601455612155565b6040805180820190915260135481526014546020820152612148908861218c565b8051601355602001516014555b5050505b9250929050565b60006003600254612171919061548d565b905060045481116121825780612186565b6004545b60035550565b6121946149d2565b61219c6149ec565b835181526020808501518183015283516040808401919091529084015160608301526000908360808460066107d05a03fa9050806122305760405162461bcd60e51b815260206004820152602b60248201527f43616c6c20746f20707265636f6d70696c656420636f6e747261637420666f7260448201526a081859190819985a5b195960aa1b6064820152608401610a41565b505092915050565b612240612cca565b600061224a612284565b805460ff1916815590507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b604051610be49190614f42565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330090565b60006122b2612c2d565b80546001600160a01b031916815590506122cb82612cef565b5050565b6122d7610cc4565b15610c645760405163d93c066560e01b815260040160405180910390fd5b6122fd614a0a565b60006123098484612d4b565b9050600080600080612376856000015160016002811061232b5761232b61533d565b602002015186516000602002015187602001516001600281106123505761235061533d565b6020020151886020015160006002811061236c5761236c61533d565b6020020151612f1c565b60408051608081018252808201948552606081019590955292845282518084019093528252602082810191909152820152955050505050505b92915050565b6123bd6149d2565b835160005b818110156124435760008682815181106123de576123de61533d565b602002602001015190506124388460106000846001600160401b03166001600160401b031681526020019081526020016000206002016040518060400160405290816000820154815260200160018201548152505061218c565b9350506001016123c2565b50604080518082019091526013548152601454602082015261246d9061246884612fac565b61218c565b60408051608081018252602080880151828401908152885160608085019190915290835283518085018552908901518152928801518382015281019190915290925060006124ba84612fac565b90506124cf6124c7613036565b838388613057565b6124ee5783604051634fb09a2160e11b8152600401610a419190614f56565b50505050505050565b6124ff6122cf565b6000612509612284565b805460ff1916600117815590507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586122773390565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b600061256c614978565b6000612585612580368a90038a018a6153bb565b6128b2565b90506012816040516125979190615515565b908152604051908190036020019020546001600160401b0316925060008390036125d65787604051631b8ac14360e01b8152600401610a419190615641565b6125df87613158565b15612603578287426040516337030b8b60e01b8152600401610a41939291906154d0565b6001600160401b03808416600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e0860152600881018054835181860281018601909452808452919461010087019491929184015b82821015612713576000848152602090819020604080516080810182526003860290920180546001600160a01b039081169284019283526001808301549091166060850152918352600201548284015290835290920191016126b6565b5050509082525060099190910154602090910152606081015151909250883514158061274b5750816060015160200151886020013514155b1561276d57828860405163c43283b560e01b8152600401610a41929190615658565b608082015161277f90611c20906153ed565b4210156127a8576080820151604051633acb74ef60e11b8152610a4191859142906004016154d0565b6040805160208082018890528a35828401528a0135606082015260808082018a90528251808303909101815260a0909101909152600a546000906127ed9083906122f5565b905061280286610e59368b90038b018b6154b4565b5050509550959350505050565b6001600160401b0382166000908152601060209081526040918290206001810154835180850190945260028201548452600390910154918301919091526001600160a01b03169061285f84613170565b612867612160565b836001600160401b03167f24c4411329b949fad2eba6f79a8ba090a08eac1af4b56c3a2f5a282ed45e233e8385846040516128a49392919061567f565b60405180910390a250505050565b6060816040516020016128c59190614f56565b6040516020818303038152906040529050919050565b600060065485600001518660200151858560405160200161292c95949392919094855260208501939093526040840191909152606090811b6001600160601b03191690830152607482015260940190565b6040516020818303038152906040529050600061294b82600a546122f5565b60408051608081018252602080890151828401908152895160608085019190915290835283518085018552908a0151815292890151838201528101919091529091506129a8612998613036565b826129a28a612fac565b85613057565b6124ee5760405163cf006ab760e01b815260040160405180910390fd5b6040516001600160a01b038481166024830152838116604483015260648201839052611cb99186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050613479565b6040516001600160a01b03838116602483015260448201839052610ab591859182169063a9059cbb906064016129fa565b60005460ff16612a805760405163348b55eb60e21b815260040160405180910390fd5b6001600160401b03821660009081526010602052604081206008810154829190825b81811015612b13576000836008018281548110612ac157612ac161533d565b6000918252602090912060039091020180549091506001600160a01b03808916911603612b0a576007840154600282015460019750612b01906004615460565b10945050612b13565b50600101612aa2565b5083612b36578585604051635bf2837760e11b8152600401610a419291906156a3565b8160050154600003612b8d57828015612b60575062278d008260040154612b5d91906153ed565b42105b15612b82578585604051633826feb560e01b8152600401610a419291906156a3565b426005830155612bcc565b6000610e108360060154612ba191906153ed565b905080421015612bca57868142604051637a54a45360e11b8152600401610a41939291906154d0565b505b426006830155604080516001600160a01b0387168152600284015460208201526003840154918101919091526001600160401b038716907f8600c0145511b317ae0189933fa71eb57a32d74891804c7993ef74ec63a5e80490606001610eb5565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6000814630604051602001612c8c939291906156c5565b604051602081830303815290604052805190602001209050919050565b612cb16134d3565b61080c816134f8565b612cc26134d3565b610c6461352a565b612cd2610cc4565b610c6457604051638dfc202b60e01b815260040160405180910390fd5b6000612cf961253e565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b612d53614a0a565b600080600080600087516001612d6991906153ed565b6001600160401b03811115612d8057612d80614cf2565b6040519080825280601f01601f191660200182016040528015612daa576020820181803683370190505b50885190915060005b81811015612e0957898181518110612dcd57612dcd61533d565b602001015160f81c60f81b838281518110612dea57612dea61533d565b60200101906001600160f81b031916908160001a905350600101612db3565b5060005b8060f81b8360018551612e2091906154a1565b81518110612e3057612e3061533d565b60200101906001600160f81b031916908160001a9053506000612e53848b613547565b91995097509050600080612e678a8a61363f565b91509150600080612e7884846136b2565b9150915081600014158015612e8c57508015155b15612ec9578415612ea757612ea18282613821565b90925090505b90985096508787612eba8c8c8484613866565b15612ec9575050505050612ee1565b50505050508080612ed9906156f9565b915050612e0d565b505060408051608081018252808201958652606081019690965293855250825180840190935282526020808301919091528201529392505050565b600080600080612f2e8888888861387d565b612f3a57612f3a61570f565b6040805160c081018252898152602081018990529081018790526060810186905260016080820152600060a0820152612f7281613920565b8051602082015160408301516060840151608085015160a0860151959650612f9995613ad2565b929c919b50995090975095505050505050565b612fb46149d2565b8151158015612fc557506020820151155b15612fe3575050604080518082019091526000808252602082015290565b60405180604001604052808360000151815260200160008051602061575c83398151915284602001516130169190615725565b61302e9060008051602061575c8339815191526154a1565b905292915050565b61303e6149d2565b5060408051808201909152600181526002602082015290565b60408051600280825260608201909252600091829190816020015b61307a6149d2565b8152602001906001900390816130725750506040805160028082526060820190925291925060009190602082015b6130b0614a0a565b8152602001906001900390816130a857905050905086826000815181106130d9576130d961533d565b602002602001018190525084826001815181106130f8576130f861533d565b602002602001018190525085816000815181106131175761311761533d565b602002602001018190525083816001815181106131365761313661533d565b602002602001018190525061314b8282613b1c565b925050505b949350505050565b60006005548261316891906153ed565b421192915050565b600060025411613193576040516363cd35d560e11b815260040160405180910390fd5b6001600160401b0381166131ba57604051631d58f8cb60e11b815260040160405180910390fd5b6001600160401b03808216600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e086015260088101805483518186028101860190945280845294959491936101008601939290879084015b828210156132ce576000848152602090819020604080516080810182526003860290920180546001600160a01b03908116928401928352600180830154909116606085015291835260020154828401529083529092019101613271565b5050509082525060099190910154602091820152818101805183516001600160401b0390811660009081526010855260408082208054600160401b600160801b031916600160401b9585169590950294909417909355855193518216815282902080546001600160401b0319169390911692909217909155805180820190915260135481526014549181019190915260608201519192506133729161246890612fac565b805160135560200151601455606081015160009061338f906128b2565b6101208301516000908152601b60205260409081902080546001600160401b0319169055519091506012906133c5908390615515565b908152604080516020928190038301902080546001600160401b03191690556001600160401b03851660009081526010909252812080546001600160801b03191681556001810180546001600160a01b0319169055600281018290556003810182905560048101829055600581018290556006810182905560078101829055906134526008830182614a2f565b6009820160009055505060016002600082825461346f91906154a1565b9091555050505050565b600061348e6001600160a01b03841683613e22565b905080516000141580156134b35750808060200190518101906134b19190615739565b155b15610ab55782604051635274afe760e01b8152600401610a419190614f42565b6134db613e37565b610c6457604051631afcd79f60e31b815260040160405180910390fd5b6135006134d3565b6001600160a01b038116610d1e576000604051631e4fbdf760e01b8152600401610a419190614f42565b6135326134d3565b600061353c612284565b805460ff1916905550565b600080600080600080600061357d898960405160200161356991815260200190565b604051602081830303815290604052613e51565b935093509350935060405160308152602080820152602060408201528460608201528360808201526001609082015260008051602061575c83398151915260b082015260208160d0836005600019fa6135d557600080fd5b805197505060405160308152602080820152836050820152602060408201528260708201526001609082015260008051602061575c83398151915260b082015260208160d0836005600019fa61362a57600080fd5b51969996985060019081161496505050505050565b60008060008060008061365488888a8a613f9a565b909250905061366582828a8a613f9a565b90925090506136a382826000805160206157bd8339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d261400b565b90999098509650505050505050565b6000806000806000856000036136f4576136cb8761403f565b909350905080156136e55782600094509450505050612159565b60008394509450505050612159565b60008051602061575c833981519152878809925060008051602061575c833981519152868709915060008051602061575c83398151915282840892506137398361403f565b9093509050806137525760008094509450505050612159565b60008051602061575c833981519152838808915061376f826140d6565b9150600061377c8361403f565b92509050816137cd5761379e888560008051602061575c833981519152614130565b92506137a9836140d6565b92506137b48361403f565b92509050816137cd576000809550955050505050612159565b8060008051602061575c83398151915282830893506137fa8460008051602061575c833981519152614154565b9350600060008051602061575c833981519152858a09919a91995090975050505050505050565b6000808061383d8560008051602061575c8339815191526154a1565b905060006138598560008051602061575c8339815191526154a1565b9196919550909350505050565b60006138748585858561387d565b95945050505050565b600080600080600061389187878989613f9a565b90945092506138a289898181613f9a565b90925090506138b382828b8b613f9a565b90925090506138c4848484846141a5565b909450925061390284846000805160206157bd8339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d26141a5565b909450925083158015613913575082155b9998505050505050505050565b613928614a50565b613930614a50565b613938614a50565b613940614a50565b84516020860151604087015160608801516080890151613978946744e992b44a6909f1949093909290918b60055b60200201516141e7565b805160208201516040830151606084015160808501519497506139a4946002949392919089600561396e565b9150613a108360005b602002015184600160200201518560026020020151866003602002015187600460200201518860056020020151886000602002015189600160200201518a600260200201518b600360200201518c600460200201518d60055b602002015161426a565b9150613a1b826144f3565b9150613a26836144f3565b9050613a31816144f3565b9050613a3e8360006139ad565b9250613aa48360005b6020020151846001602002015185600260200201518660036020020151876004602002015188600560200201518760006020020151886001602002015189600260200201518a600360200201518b600460200201518c6005613a06565b9250613aaf856144f3565b9050613aba816144f3565b9050613ac5816144f3565b9050613874836000613a47565b600080600080600080613ae5888861460b565b9092509050613af68c8c8484613f9a565b9096509450613b078a8a8484613f9a565b969d959c509a50949850929650505050505050565b60008151835114613b2c57600080fd5b82516000613b3b826006615460565b90506000816001600160401b03811115613b5757613b57614cf2565b604051908082528060200260200182016040528015613b80578160200160208202803683370190505b50905060005b83811015613db157868181518110613ba057613ba061533d565b60200260200101516000015182826006613bba9190615460565b613bc59060006153ed565b81518110613bd557613bd561533d565b602002602001018181525050868181518110613bf357613bf361533d565b60200260200101516020015182826006613c0d9190615460565b613c189060016153ed565b81518110613c2857613c2861533d565b602002602001018181525050858181518110613c4657613c4661533d565b6020908102919091010151515182613c5f836006615460565b613c6a9060026153ed565b81518110613c7a57613c7a61533d565b602002602001018181525050858181518110613c9857613c9861533d565b60209081029190910181015151015182613cb3836006615460565b613cbe9060036153ed565b81518110613cce57613cce61533d565b602002602001018181525050858181518110613cec57613cec61533d565b602002602001015160200151600060028110613d0a57613d0a61533d565b602002015182613d1b836006615460565b613d269060046153ed565b81518110613d3657613d3661533d565b602002602001018181525050858181518110613d5457613d5461533d565b602002602001015160200151600160028110613d7257613d7261533d565b602002015182613d83836006615460565b613d8e9060056153ed565b81518110613d9e57613d9e61533d565b6020908102919091010152600101613b86565b50613dba614a6e565b60006020826020860260208601600060086107d05a03f1905080613e145760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964205369676e617475726560781b6044820152606401610a41565b505115159695505050505050565b6060613e3083836000614696565b9392505050565b6000613e41612c51565b54600160401b900460ff16919050565b60008060008060ff85511115613e6657600080fd5b600060405160005b6088811015613e8557600082820152602001613e6e565b506088602060005b8a51811015613eae578a820151848401526020928301929182019101613e8d565b505060898951019050608081830153600201602060005b8951811015613ee65789820151848401526020928301929182019101613ec5565b5050608b88518a5101019050875181830153508751875101608c018120915050604051818152600160208201536021602060005b8951811015613f3b5789820151848401526020928301929182019101613f1a565b505050865187516021018201538651602201812095508582188152600260208201538651602201812094508482188152600360208201538651602201812093508382188152600460208201539551602201909520939692955090935050565b600080613fd860008051602061575c83398151915285880960008051602061575c83398151915285880960008051602061575c833981519152614130565b60008051602061575c8339815191528086880960008051602061575c833981519152868a09089150915094509492505050565b60008060008051602061575c83398151915284870860008051602061575c8339815191528487089150915094509492505050565b600080600060405160208152602080820152602060408201528460608201527f0c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52608082015260008051602061575c83398151915260a082015260208160c08360056107d05a03fa90519350905060008051602061575c83398151915283800984149150806140d05760009250600091505b50915091565b60006001821615156140e960028461548d565b9150801561412a5760008051602061575c833981519152600261411b60008051602061575c83398151915260016153ed565b614125919061548d565b830891505b50919050565b6000818061414057614140615477565b61414a84846154a1565b8508949350505050565b60008060405160208152602080820152602060408201528460608201526002840360808201528360a082015260208160c08360056107d05a03fa9051925090508061419e57600080fd5b5092915050565b6000806141c1868560008051602061575c833981519152614130565b6141da868560008051602061575c833981519152614130565b9150915094509492505050565b6141ef614a50565b871561425f576001881615614230578051602082015160408301516060840151608085015160a086015161422d9594939291908d8d8d8d8d8d61426a565b90505b61423e878787878787614733565b949b5092995090975095509350915061425860028961548d565b97506141ef565b979650505050505050565b614272614a50565b8815801561427e575087155b156142c0578686868686868660005b60a089019290925260808801929092526060870192909252604086019290925260208581019390935290910201526144e3565b821580156142cc575081155b156142df578c8c8c8c8c8c86600061428d565b6142eb85858b8b613f9a565b90955093506142fc8b8b8585613f9a565b6060830152604082015261431287878b8b613f9a565b90975095506143238d8d8585613f9a565b60a08301526080820181905287148015614340575060a081015186145b156143855760408101518514801561435b5750606081015184145b156143765761436e8d8d8d8d8d8d614733565b86600061428d565b6001600081818080868161428d565b61439189898585613f9a565b90935091506143b1858583600260200201518460035b60200201516141a5565b909d509b506143cb878783600460200201518460056143a7565b909b5099506143dc8b8b8181613f9a565b90995097506143fc898983600460200201518460055b6020020151613f9a565b909550935061440d89898d8d613f9a565b909950975061441e89898585613f9a565b60a083015260808201526144348d8d8181613f9a565b909750955061444587878585613f9a565b909750955061445687878b8b6141a5565b9097509550614467858560026148a2565b9093509150614478878785856141a5565b90975095506144898b8b8989613f9a565b6020830152815261449c858589896141a5565b909b5099506144ad8d8d8d8d613f9a565b909b5099506144c7898983600260200201518460036143f2565b909d509b506144d88b8b8f8f6141a5565b606083015260408201525b9c9b505050505050505050505050565b6144fb614a50565b815161450f908360015b60200201516148d5565b60208301528152604082015161452790836003614505565b60608301526040820152608082015161454290836005614505565b60a083015260808201528051602082015161459f91907f2fb347984f7911f74c0bec3cf559b143b78cc310c2c3330c99e39557176f553d7f16c9e55061ebae204ba4cc8bd75a079432ae2a1d0b7c9dce1665d51c640fcba2613f9a565b60208301528152604081015160608201516145fc91907f063cf305489af5dcdc5ec698b6e2f9b9dbaae0eda9c95998dc54014671a0135a7f07c03cbcac41049a0704b5a7ec796f2b21807dc98fa25bd282d37f632623b0e3613f9a565b60608301526040820152919050565b6000808061464c60008051602061575c8339815191528087880960008051602061575c8339815191528788090860008051602061575c833981519152614154565b905060008051602061575c83398151915281860960008051602061575c83398151915282860961468a9060008051602061575c8339815191526154a1565b92509250509250929050565b6060814710156146bb573060405163cd78605960e01b8152600401610a419190614f42565b600080856001600160a01b031684866040516146d79190615515565b60006040518083038185875af1925050503d8060008114614714576040519150601f19603f3d011682016040523d82523d6000602084013e614719565b606091505b50915091506147298683836148fc565b9695505050505050565b6000806000806000806147488c8c60036148a2565b909650945061475986868e8e613f9a565b909650945061476a8a8a8a8a613f9a565b909850965061477b8c8c8c8c613f9a565b909450925061478c84848a8a613f9a565b909450925061479d86868181613f9a565b909c509a506147ae848460086148a2565b90925090506147bf8c8c84846141a5565b909c509a506147d088888181613f9a565b90925090506147e1848460046148a2565b90945092506147f284848e8e6141a5565b909450925061480384848888613f9a565b90945092506148148a8a60086148a2565b909650945061482586868c8c613f9a565b909650945061483686868484613f9a565b9096509450614847848488886141a5565b90945092506148588c8c60026148a2565b909650945061486986868a8a613f9a565b909650945061487a88888484613f9a565b909250905061488b828260086148a2565b809250819350505096509650965096509650969050565b60008060008051602061575c83398151915283860960008051602061575c83398151915284860991509150935093915050565b600080836148f18460008051602061575c8339815191526154a1565b915091509250929050565b6060826149115761490c8261494f565b613e30565b815115801561492857506001600160a01b0384163b155b156149485783604051639996b31560e01b8152600401610a419190614f42565b5080613e30565b80511561495f5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b604080516101408101825260008082526020820181905291810191909152606081016149a26149d2565b81526020016000815260200160008152602001600081526020016000815260200160608152602001600081525090565b604051806040016040528060008152602001600081525090565b60405180608001604052806004906020820280368337509192915050565b6040518060400160405280614a1d614a8c565b8152602001614a2a614a8c565b905290565b508054600082556003029060005260206000209081019061080c9190614aaa565b6040518060c001604052806006906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b60405180604001604052806002906020820280368337509192915050565b5b80821115614add5780546001600160a01b03199081168255600182018054909116905560006002820155600301614aab565b5090565b80356001600160401b0381168114614af857600080fd5b919050565b600060208284031215614b0f57600080fd5b613e3082614ae1565b80518252602090810151910152565b805180516001600160a01b03908116845260209182015116818401520151604082015260600190565b600081518084526020840193506020830160005b82811015614b8857614b77868351614b27565b955060209190910190600101614b64565b5093949350505050565b60208152614bac6020820183516001600160401b03169052565b60006020830151614bc860408401826001600160401b03169052565b5060408301516001600160a01b0381166060840152506060830151614bf06080840182614b18565b50608083015160c083015260a083015160e083015260c083015161010083015260e0830151610120830152610100830151610160610140840152614c38610180840182614b50565b90506101208401516101608401528091505092915050565b600060208284031215614c6257600080fd5b5035919050565b6001600160401b0391909116815260200190565b60008060208385031215614c9057600080fd5b82356001600160401b03811115614ca657600080fd5b8301601f81018513614cb757600080fd5b80356001600160401b03811115614ccd57600080fd5b8560208260051b8401011115614ce257600080fd5b6020919091019590945092505050565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715614d2a57614d2a614cf2565b60405290565b604051608081016001600160401b0381118282101715614d2a57614d2a614cf2565b604051601f8201601f191681016001600160401b0381118282101715614d7a57614d7a614cf2565b604052919050565b600060208284031215614d9457600080fd5b81356001600160401b03811115614daa57600080fd5b8201601f81018413614dbb57600080fd5b80356001600160401b03811115614dd457614dd4614cf2565b614de7601f8201601f1916602001614d52565b818152856020838501011115614dfc57600080fd5b81602084016020830137600091810160200191909152949350505050565b6001600160a01b038116811461080c57600080fd5b60006080828403121561412a57600080fd5b60006001600160401b03821115614e5a57614e5a614cf2565b5060051b60200190565b600082601f830112614e7557600080fd5b8135614e88614e8382614e41565b614d52565b8082825260208201915060208360051b860101925085831115614eaa57600080fd5b602085015b83811015614ece57614ec081614ae1565b835260209283019201614eaf565b5095945050505050565b60008060008060e08587031215614eee57600080fd5b8435614ef981614e1a565b935060208501359250614f0f8660408701614e2f565b915060c08501356001600160401b03811115614f2a57600080fd5b614f3687828801614e64565b91505092959194509250565b6001600160a01b0391909116815260200190565b604081016123af8284614b18565b600080600080848603610100811215614f7c57600080fd5b6040811215614f8a57600080fd5b5084935060408401359250614fa28660608601614e2f565b915060e08501356001600160401b03811115614f2a57600080fd5b6040808252835190820181905260009060208501906060840190835b818110156150005783516001600160401b0316835260209384019390920191600101614fd9565b50508381036020808601919091528551808352918101925085019060005b8181101561504757615031848451614b18565b604093909301926020929092019160010161501e565b50919695505050505050565b60006040828403121561506557600080fd5b61506d614d08565b823581526020928301359281019290925250919050565b60006080828403121561509657600080fd5b61509e614d30565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b600082601f8301126150da57600080fd5b81356150e8614e8382614e41565b8082825260208201915060206060840286010192508583111561510a57600080fd5b602085015b83811015614ece57808703606081121561512857600080fd5b615130614d08565b604082121561513e57600080fd5b615146614d08565b9150823561515381614e1a565b8252602083013561516381614e1a565b6020838101919091529181526040830135818301528452929092019160600161510f565b60008060008084860361016081121561519f57600080fd5b6151a98787615053565b94506151b88760408801615084565b9350608060bf19820112156151cc57600080fd5b506151d5614d30565b60c0860135815260e08601356020820152610100860135604082015261012086013561ffff8116811461520757600080fd5b606082015291506101408501356001600160401b0381111561522857600080fd5b614f36878288016150c9565b60008060006060848603121561524957600080fd5b505081359360208301359350604090920135919050565b60006020828403121561527257600080fd5b8135613e3081614e1a565b918252602082015260400190565b600080600080600080600060e0888a0312156152a657600080fd5b87356152b181614e1a565b965060208801356152c181614e1a565b96999698505050506040850135946060810135946080820135945060a0820135935060c0909101359150565b600080600080610100858703121561530457600080fd5b61530e8686615053565b935061531d8660408701615084565b925060c085013561532d81614e1a565b9396929550929360e00135925050565b634e487b7160e01b600052603260045260246000fd5b60008235609e1983360301811261536957600080fd5b9190910192915050565b6000808335601e1984360301811261538a57600080fd5b8301803591506001600160401b038211156153a457600080fd5b602001915060608102360382131561215957600080fd5b6000604082840312156153cd57600080fd5b613e308383615053565b634e487b7160e01b600052601160045260246000fd5b808201808211156123af576123af6153d7565b80546001600160a01b0319166001600160a01b0392909216919091179055565b813561542b81614e1a565b6154358183615400565b50602082013561544481614e1a565b6154518160018401615400565b50604082013560028201555050565b80820281158282048414176123af576123af6153d7565b634e487b7160e01b600052601260045260246000fd5b60008261549c5761549c615477565b500490565b818103818111156123af576123af6153d7565b6000608082840312156154c657600080fd5b613e308383615084565b6001600160401b039390931683526020830191909152604082015260600190565b60005b8381101561550c5781810151838201526020016154f4565b50506000910152565b600082516153698184602087016154f1565b6001600160a01b0385168152600061010082016155476020840187614b18565b8451606084015260208501516080840152604085015160a084015261ffff60608601511660c084015261010060e08401528084518083526101208501915060208601925060005b818110156155b2576155a1838551614b27565b60209490940193925060010161558e565b509098975050505050505050565b6001600160a01b038316815260608101613e306020830184614b18565b6001600160401b0381811683821601908111156123af576123af6153d7565b60006001820161560e5761560e6153d7565b5060010190565b60006001600160401b0382166002600160401b03198101615638576156386153d7565b60010192915050565b8135815260208083013590820152604081016123af565b6001600160401b038316815260608101613e30602083018480358252602090810135910152565b6001600160a01b038416815260208101839052608081016131506040830184614b18565b6001600160401b039290921682526001600160a01b0316602082015260400190565b600084516156d78184602089016154f1565b919091019283525060601b6001600160601b0319166020820152603401919050565b600060ff821660ff8103615638576156386153d7565b634e487b7160e01b600052600160045260246000fd5b60008261573457615734615477565b500690565b60006020828403121561574b57600080fd5b81518015158114613e3057600080fdfe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd476e0956cda88cad152e89927e53611735b61a5c762d1428573c6931b0a5efcb01424c535f5349475f545259414e44494e4352454d454e545f4c49515549444154452b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5a164736f6c634300081a000a", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106102e05760003560e01c8063040f9853146102e5578063095f14041461030e5780630962ef79146103255780630bd1b4191461033a57806317f33a7d146103435780631f4c49301461036a5780631f744b541461037d578063245fc1831461038557806334fde37614610398578063372500ab146103a05780633f4ba83a146103a857806343039d90146103b0578063538d2709146103c3578063544736e6146103cc57806356d399e8146103e957806358e93308146103f25780635c975abb146103fb578063623b94221461040357806362f1fbc21461040c57806365ca819d146104165780636b348ab41461044a578063715018a61461045357806379ba50971461045b5780637a6d4065146104635780637c89d2f0146104765780638328b6101461049b5780638456cb59146104ae57806386e76685146104b657806386fa5063146104bf578063879d0f0c146104c85780638a2209e6146104d05780638a399481146104e55780638da5cb5b146104ee5780639156ac80146104f657806395055ffd146104ff5780639592d424146105125780639c80ebee1461051b5780639cebc4741461040c5780639e3b372d14610523578063a44285a51461052c578063ab0122ae14610535578063abf2c50314610548578063ae6c60631461055e578063b13aaebd14610567578063b687f85c14610570578063bc7efecc14610578578063be9a65551461058b578063c0ebedab14610593578063c4f56d9a146105bc578063d5a81254146105cf578063d84f0bf8146105d8578063d9054b09146105e1578063da1d5435146105f4578063dd644d74146105fd578063e30c397814610606578063e58598131461060e578063eb82031214610621578063edbf4ac214610656578063ee94e41214610669578063f2fde38b14610672578063f324c8eb14610685578063f88d658b14610698578063f907f5fc146106ab578063ffd9a86d146106be575b600080fd5b6102f86102f3366004614afd565b6106d1565b6040516103059190614b92565b60405180910390f35b610317600d5481565b604051908152602001610305565b610338610333366004614c50565b610802565b005b610317600f5481565b60015461035d90600160a01b90046001600160401b031681565b6040516103059190614c69565b610338610378366004614c7d565b61080f565b610338610aba565b610338610393366004614c50565b610b8a565b610317610bef565b610338610c26565b610338610c54565b6103386103be366004614c50565b610c66565b61031760075481565b6000546103d99060ff1681565b6040519015158152602001610305565b610317600b5481565b610317600e5481565b6103d9610cc4565b61031760155481565b61031762278d0081565b61035d610424366004614d82565b80516020818301810180516012825292820191909301209152546001600160401b031681565b610317600a5481565b610338610cd9565b610338610ceb565b610338610471366004614ed8565b610d27565b60005461048e9061010090046001600160a01b031681565b6040516103059190614f42565b6103386104a9366004614c50565b610ec5565b610338610f22565b61031760045481565b610317600c5481565b610317601481565b6104d8610f32565b6040516103059190614f56565b61031760035481565b61048e610f55565b61031760195481565b61033861050d366004614f64565b610f70565b61031760025481565b61035d600081565b61031760095481565b61031760175481565b610338610543366004614afd565b610ff9565b6105506110c2565b604051610305929190614fbd565b61031760065481565b61031760185481565b610317600481565b610338610586366004615187565b61124d565b610338611576565b61035d6105a1366004614c50565b601b602052600090815260409020546001600160401b031681565b6103386105ca366004614f64565b61158d565b61031760055481565b61031760085481565b6103386105ef366004614afd565b611742565b61031760165481565b610317601a5481565b61048e611754565b61033861061c366004615234565b61175f565b61064861062f366004615260565b6011602052600090815260409020805460019091015482565b60405161030592919061527d565b61033861066436600461528b565b611812565b610317610e1081565b610338610680366004615260565b611b80565b610338610693366004614c50565b611bf1565b6103386106a6366004614c50565b611c4f565b60015461048e906001600160a01b031681565b6103386106cc3660046152ed565b611cad565b6106d9614978565b6001600160401b03808316600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e0860152600881018054835181860281018601909452808452919461010087019491929184015b828210156107e9576000848152602090819020604080516080810182526003860290920180546001600160a01b0390811692840192835260018083015490911660608501529183526002015482840152908352909201910161078c565b5050505081526020016009820154815250509050919050565b61080c3382611cbf565b50565b610817611dfa565b60005460ff161561083b57604051630a35d5c360e31b815260040160405180910390fd5b8060005b81811015610aac573684848381811061085a5761085a61533d565b905060200281019061086c9190615353565b9050600061087d6080830183615373565b90501161089d57604051631a10033d60e11b815260040160405180910390fd5b600c546108ad6080830183615373565b905011156108ce5760405163226c2d8360e21b815260040160405180910390fd5b6000806108ed6108e3368590038501856153bb565b8460400135611e2c565b600b54600782015590925090506109076080840184615373565b60008181106109185761091861533d565b61092e9260206060909202019081019150615260565b6001820180546001600160a01b0319166001600160a01b03929092169190911790556060830135600482015560008061096a6080860186615373565b9050905060005b81811015610a1857366109876080880188615373565b838181106109975761099761533d565b90506060020190508060400135846109af91906153ed565b935060006109c06020830183615260565b6001600160a01b0316036109e75760405163e99d5ac560e01b815260040160405180910390fd5b60088501805460018101825560009182526020909120829160030201610a0d8282615420565b505050600101610971565b50600b548214610a4a57600b54826040516367d22bd960e01b8152600401610a4192919061527d565b60405180910390fd5b604080518635815260208088013590820152868201358183015290516001600160401b038616917f34be2fa283e1d5eb453459898f160448257aa1ef0f7dd2c3a3b55c45d1af768f919081900360600190a2600186019550505050505061083f565b50610ab5612160565b505050565b600060028190558052601060205260008051602061577c833981519152546001600160401b03165b6001600160401b0381161561080c576001600160401b0381166000908152601060205260408120600254909103610b285760028101546013556003810154601455610b70565b6040805180820182526013548152601454602080830191909152825180840190935260028401548352600384015490830152610b639161218c565b8051601355602001516014555b546002805460010190556001600160401b03169050610ae2565b610b92611dfa565b60008111610bb357604051630ef6cf4560e41b815260040160405180910390fd5b60188190556040518181527fa83e79dc52a438d552d0631ce4fbfe7dec7656d10398a2849f352a6ab4f0163b906020015b60405180910390a150565b60008060646002546002610c039190615460565b610c0d919061548d565b905060148111610c1e576014610c20565b805b91505090565b336000908152601160205260408120600181015490549091610c4883836154a1565b9050610ab53382611cbf565b610c5c611dfa565b610c64612238565b565b610c6e611dfa565b60008111610c8f57604051630ef6cf4560e41b815260040160405180910390fd5b60048190556040518181527f0ac8ee09138dfaf5e3ebe4cb4fd42dd1a0695535a530171223fb5066f52e0e3b90602001610be4565b600080610ccf612284565b5460ff1692915050565b610ce1611dfa565b610c6460006122a8565b3380610cf5611754565b6001600160a01b031614610d1e578060405163118cdaa760e01b8152600401610a419190614f42565b61080c816122a8565b610d2f6122cf565b60005460ff16610d525760405163348b55eb60e21b815260040160405180910390fd5b8051600354811115610d975780600254610d6c91906154a1565b600354600254610d7c91906154a1565b604051635eee4a3560e01b8152600401610a4192919061527d565b6001600160a01b038516610dbe5760405163e99d5ac560e01b815260040160405180910390fd5b6001600160a01b0385166000908152601160205260409020548411610df6576040516333938e6360e01b815260040160405180910390fd5b6007546040805160208101929092526001600160601b0319606088901b16908201526054810185905260009060740160405160208183030381529060405290506000610e4482600a546122f5565b9050610e5f84610e59368890038801886154b4565b836123b5565b50506001600160a01b0385166000818152601160205260409081902080549087905590519091907f95390641529563dbfb446535fa996c5ac3be00f90f5705b3abda59a4467b797f90610eb5908890859061527d565b60405180910390a2505050505050565b610ecd611dfa565b60008111610eee57604051630ef6cf4560e41b815260040160405180910390fd5b600b8190556040518181527e6b7a1ea14ff2794527a64af37d55a2040e351f8b4c1adcdc9aea80d64e042990602001610be4565b610f2a611dfa565b610c646124f7565b610f3a6149d2565b50604080518082019091526013548152601454602082015290565b600080610f6061253e565b546001600160a01b031692915050565b610f786122cf565b60005460ff16610f9b5760405163348b55eb60e21b815260040160405180910390fd5b8051600354811115610fb55780600254610d6c91906154a1565b6000610fc686868660085487612562565b506001600160401b038116600090815260106020526040902060070154909150610ff190829061280f565b505050505050565b6110016122cf565b60005460ff166110245760405163348b55eb60e21b815260040160405180910390fd5b6001600160401b038116600090815260106020526040812060050154908190036110635781604051630cf4827360e31b8152600401610a419190614c69565b600061107262278d00836153ed565b90508042101561109b57828142604051637a54a45360e11b8152600401610a41939291906154d0565b6001600160401b038316600090815260106020526040902060070154610ab590849061280f565b6060806002546001600160401b038111156110df576110df614cf2565b604051908082528060200260200182016040528015611108578160200160208202803683370190505b5091506002546001600160401b0381111561112557611125614cf2565b60405190808252806020026020018201604052801561115e57816020015b61114b6149d2565b8152602001906001900390816111435790505b506000808052601060205260008051602061577c833981519152549192506001600160401b03909116905b6001600160401b03821615611247576001600160401b038083166000908152601060205260409020855190918491879185169081106111ca576111ca61533d565b60200260200101906001600160401b031690816001600160401b031681525050806002016040518060400160405290816000820154815260200160018201548152505084836001600160401b0316815181106112285761122861533d565b6020908102919091010152546001600160401b03169150600101611189565b50509091565b6112556122cf565b60005460ff166112785760405163348b55eb60e21b815260040160405180910390fd5b8051600c5481111561129d5760405163226c2d8360e21b815260040160405180910390fd5b8015611311576000805b828110156112e2578381815181106112c1576112c161533d565b602002602001015160200151826112d891906153ed565b91506001016112a7565b50600b54811461130b57600b54816040516367d22bd960e01b8152600401610a4192919061527d565b506113a6565b60408051600180825281830190925290816020015b604080516080810182526000918101828152606082018390528152602081019190915281526020019060019003908161132657505060408051608081018252339181018281526060820192909252908152600b54602082015281519193509083906000906113965761139661533d565b6020026020010181905250815190505b600060126113b3876128b2565b6040516113c09190615515565b908152604051908190036020019020546001600160401b0316905080156113fc578060405163459c639360e01b8152600401610a419190614c69565b6000836000815181106114115761141161533d565b60200260200101516000015160000151905061143387878388600001516128db565b600080611444898860000151611e2c565b600b5460078201556001810180546001600160a01b0319166001600160a01b038716179055909250905060005b858110156114f8578160080187828151811061148f5761148f61533d565b60209081029190910181015182546001808201855560009485529383902082518051600390930290910180546001600160a01b03199081166001600160a01b03948516178255918501518187018054909316931692909217905591015160029091015501611471565b50611501612160565b816001600160401b03167f533c16cb4158fe7c77021b90e9290bbefa457dd392bd8abc1eab59747834fd5b848b8a8a6040516115409493929190615527565b60405180910390a261156b600060019054906101000a90046001600160a01b03163330600b546129c5565b505050505050505050565b61157e611dfa565b6000805460ff19166001179055565b6115956122cf565b60005460ff166115b85760405163348b55eb60e21b815260040160405180910390fd5b80516003548111156115d25780600254610d6c91906154a1565b6000806115e487878760095488612562565b91509150816001600160401b03167f0bfb12191b00293af29126b1c5489f8daeb4a4af82db2960b7f8353c3105cd7c8260400151836060015160405161162b9291906155c0565b60405180910390a26000600f54600d54600e5461164891906153ed565b61165291906153ed565b905060008260e001519050600082600d548361166e9190615460565b611678919061548d565b90506000600e548361168a9190615460565b156116c457836001600e54856116a09190615460565b6116aa91906154a1565b6116b4919061548d565b6116bf9060016153ed565b6116c7565b60005b90506116e786826116d885876154a1565b6116e291906154a1565b61280f565b600d541561170b5760005461170b9061010090046001600160a01b03163384612a2c565b600e541561173557600054600154611735916001600160a01b036101009091048116911683612a2c565b5050505050505050505050565b61174a6122cf565b61080c8133612a5d565b600080610f60612c2d565b611767611dfa565b6000811161178857604051630ef6cf4560e41b815260040160405180910390fd5b8061179383856153ed565b61179e906003615460565b11156117bd57604051630b06449d60e41b815260040160405180910390fd5b600d839055600e829055600f81905560408051848152602081018490529081018290527f0f69a2a87c90cdbb1a6a46bbb4870f2edfbd516244285ab6ffed6460ac2c6a839060600160405180910390a1505050565b600061181c612c51565b805490915060ff600160401b82041615906001600160401b03166000811580156118435750825b90506000826001600160401b0316600114801561185f5750303b155b90508115801561186d575080155b1561188b5760405163f92ee8a960e01b815260040160405180910390fd5b84546001600160401b031916600117855583156118b457845460ff60401b1916600160401b1785555b60018610156118d657604051630ef6cf4560e41b815260040160405180910390fd5b856118e1888a6153ed565b6118ec906003615460565b111561190b57604051630b06449d60e41b815260040160405180910390fd5b6000805460ff19168155600281905560035561012c60045560408051808201909152601b81527a0424c535f5349475f545259414e44494e4352454d454e545f504f5602c1b602082015261195e90612c75565b60065560408051808201909152601e81527f424c535f5349475f545259414e44494e4352454d454e545f5245574152440000602082015261199e90612c75565b60075560408051808201909152601c81527b109314d7d4d251d7d51496505391125390d4915351539517d156125560221b60208201526119dd90612c75565b600881905550611a0460405180606001604052806021815260200161579c60219139612c75565b600955604080518082019091526019815278424c535f5349475f484153485f544f5f4649454c445f54414760381b6020820152611a4090612c75565b600a5561025860055566038d7ea4c6800060175561a8c060185560006019819055601a8190558054610100600160a81b0319166101006001600160a01b038f811691909102919091178255600180546001600160a01b031916918e16919091178155600b8c9055600c8b9055600d8a9055600e899055600f889055611ac591906155dd565b600180546001600160401b0392909216600160a01b02600160a01b600160e01b031990921691909117905560008052601060205260008051602061577c83398151915280546001600160801b0319169055611b1f33612ca9565b611b27612cba565b8315611b7257845460ff60401b191685556040517fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290611b6990600190614c69565b60405180910390a15b505050505050505050505050565b611b88611dfa565b6000611b92612c2d565b80546001600160a01b0319166001600160a01b0384169081178255909150611bb8610f55565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b611bf9611dfa565b60008111611c1a57604051630ef6cf4560e41b815260040160405180910390fd5b60178190556040518181527fe604909b918af52702fa14cd9b5a8870e5bd66da3163365671e583f705fd546390602001610be4565b611c57611dfa565b60008111611c7857604051630ef6cf4560e41b815260040160405180910390fd5b60058190556040518181527ffbbc3d0a51e101ce4a7fda20e6e4eb0e230bf7de27ee2e3683fc8539e376639590602001610be4565b611cb9848484846128db565b50505050565b6001600160a01b0382166000908152601160205260408120600181015490549091611cea83836154a1565b905080841115611d0d5760405163363cc6b360e21b815260040160405180910390fd5b600060185442611d1d919061548d565b9050601a54811115611d3457601a81905560006019555b8460196000828254611d4691906153ed565b90915550506017546019541115611d7057604051638c10944b60e01b815260040160405180910390fd5b6001600160a01b03861660009081526011602052604081206001018054879290611d9b9084906153ed565b90915550506040518581526001600160a01b038716907ffc30cddea38e2bf4d6ea7d3f9ed3b6ad7f176419f4963bd81318067a4aee73fe9060200160405180910390a2600054610ff19061010090046001600160a01b03168787612a2c565b33611e03610f55565b6001600160a01b031614610c64573360405163118cdaa760e01b8152600401610a419190614f42565b815160009081901580611e4157506020840151155b15611e5f5760405163139b722760e31b815260040160405180910390fd5b82600003611e8057604051634eb8f11f60e01b815260040160405180910390fd5b6000611e8b856128b2565b905060006001600160401b0316601282604051611ea89190615515565b908152604051908190036020019020546001600160401b031614611f0857601281604051611ed69190615515565b9081526040519081900360200181205463459c639360e01b8252610a41916001600160401b0390911690600401614c69565b6000848152601b60205260409020546001600160401b031615611f58576000848152601b602052604090819020549051630cb4c72160e21b8152610a41916001600160401b031690600401614c69565b60005460ff1615611fbd57436015541015611f77574360155560006016555b60168054906000611f87836155fc565b91905055506000611f96610bef565b9050806016541115611fbb5760405163689f6e7560e11b815260040160405180910390fd5b505b60018054600160a01b90046001600160401b0316906014611fdd83615615565b91906101000a8154816001600160401b0302191690836001600160401b031602179055509250600260008154612012906155fc565b909155506001600160401b03838116600081815260106020908152604080832060008051602061577c833981519152805482546001600160801b031916600160401b918290049098168082029890981783558154600160401b600160801b03191690870217815586855282852080546001600160401b031990811688179091558c5160028401558c8501516003840155426004840155600983018c90558b8652601b90945293829020805490931690941790915551919450919085906012906120dc908690615515565b90815260405190819003602001902080546001600160401b03929092166001600160401b03199092169190911790556002546001036121275786516013556020870151601455612155565b6040805180820190915260135481526014546020820152612148908861218c565b8051601355602001516014555b5050505b9250929050565b60006003600254612171919061548d565b905060045481116121825780612186565b6004545b60035550565b6121946149d2565b61219c6149ec565b835181526020808501518183015283516040808401919091529084015160608301526000908360808460066107d05a03fa9050806122305760405162461bcd60e51b815260206004820152602b60248201527f43616c6c20746f20707265636f6d70696c656420636f6e747261637420666f7260448201526a081859190819985a5b195960aa1b6064820152608401610a41565b505092915050565b612240612cca565b600061224a612284565b805460ff1916815590507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b604051610be49190614f42565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330090565b60006122b2612c2d565b80546001600160a01b031916815590506122cb82612cef565b5050565b6122d7610cc4565b15610c645760405163d93c066560e01b815260040160405180910390fd5b6122fd614a0a565b60006123098484612d4b565b9050600080600080612376856000015160016002811061232b5761232b61533d565b602002015186516000602002015187602001516001600281106123505761235061533d565b6020020151886020015160006002811061236c5761236c61533d565b6020020151612f1c565b60408051608081018252808201948552606081019590955292845282518084019093528252602082810191909152820152955050505050505b92915050565b6123bd6149d2565b835160005b818110156124435760008682815181106123de576123de61533d565b602002602001015190506124388460106000846001600160401b03166001600160401b031681526020019081526020016000206002016040518060400160405290816000820154815260200160018201548152505061218c565b9350506001016123c2565b50604080518082019091526013548152601454602082015261246d9061246884612fac565b61218c565b60408051608081018252602080880151828401908152885160608085019190915290835283518085018552908901518152928801518382015281019190915290925060006124ba84612fac565b90506124cf6124c7613036565b838388613057565b6124ee5783604051634fb09a2160e11b8152600401610a419190614f56565b50505050505050565b6124ff6122cf565b6000612509612284565b805460ff1916600117815590507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586122773390565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930090565b600061256c614978565b6000612585612580368a90038a018a6153bb565b6128b2565b90506012816040516125979190615515565b908152604051908190036020019020546001600160401b0316925060008390036125d65787604051631b8ac14360e01b8152600401610a419190615641565b6125df87613158565b15612603578287426040516337030b8b60e01b8152600401610a41939291906154d0565b6001600160401b03808416600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e0860152600881018054835181860281018601909452808452919461010087019491929184015b82821015612713576000848152602090819020604080516080810182526003860290920180546001600160a01b039081169284019283526001808301549091166060850152918352600201548284015290835290920191016126b6565b5050509082525060099190910154602090910152606081015151909250883514158061274b5750816060015160200151886020013514155b1561276d57828860405163c43283b560e01b8152600401610a41929190615658565b608082015161277f90611c20906153ed565b4210156127a8576080820151604051633acb74ef60e11b8152610a4191859142906004016154d0565b6040805160208082018890528a35828401528a0135606082015260808082018a90528251808303909101815260a0909101909152600a546000906127ed9083906122f5565b905061280286610e59368b90038b018b6154b4565b5050509550959350505050565b6001600160401b0382166000908152601060209081526040918290206001810154835180850190945260028201548452600390910154918301919091526001600160a01b03169061285f84613170565b612867612160565b836001600160401b03167f24c4411329b949fad2eba6f79a8ba090a08eac1af4b56c3a2f5a282ed45e233e8385846040516128a49392919061567f565b60405180910390a250505050565b6060816040516020016128c59190614f56565b6040516020818303038152906040529050919050565b600060065485600001518660200151858560405160200161292c95949392919094855260208501939093526040840191909152606090811b6001600160601b03191690830152607482015260940190565b6040516020818303038152906040529050600061294b82600a546122f5565b60408051608081018252602080890151828401908152895160608085019190915290835283518085018552908a0151815292890151838201528101919091529091506129a8612998613036565b826129a28a612fac565b85613057565b6124ee5760405163cf006ab760e01b815260040160405180910390fd5b6040516001600160a01b038481166024830152838116604483015260648201839052611cb99186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050613479565b6040516001600160a01b03838116602483015260448201839052610ab591859182169063a9059cbb906064016129fa565b60005460ff16612a805760405163348b55eb60e21b815260040160405180910390fd5b6001600160401b03821660009081526010602052604081206008810154829190825b81811015612b13576000836008018281548110612ac157612ac161533d565b6000918252602090912060039091020180549091506001600160a01b03808916911603612b0a576007840154600282015460019750612b01906004615460565b10945050612b13565b50600101612aa2565b5083612b36578585604051635bf2837760e11b8152600401610a419291906156a3565b8160050154600003612b8d57828015612b60575062278d008260040154612b5d91906153ed565b42105b15612b82578585604051633826feb560e01b8152600401610a419291906156a3565b426005830155612bcc565b6000610e108360060154612ba191906153ed565b905080421015612bca57868142604051637a54a45360e11b8152600401610a41939291906154d0565b505b426006830155604080516001600160a01b0387168152600284015460208201526003840154918101919091526001600160401b038716907f8600c0145511b317ae0189933fa71eb57a32d74891804c7993ef74ec63a5e80490606001610eb5565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0090565b6000814630604051602001612c8c939291906156c5565b604051602081830303815290604052805190602001209050919050565b612cb16134d3565b61080c816134f8565b612cc26134d3565b610c6461352a565b612cd2610cc4565b610c6457604051638dfc202b60e01b815260040160405180910390fd5b6000612cf961253e565b80546001600160a01b038481166001600160a01b031983168117845560405193945091169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b612d53614a0a565b600080600080600087516001612d6991906153ed565b6001600160401b03811115612d8057612d80614cf2565b6040519080825280601f01601f191660200182016040528015612daa576020820181803683370190505b50885190915060005b81811015612e0957898181518110612dcd57612dcd61533d565b602001015160f81c60f81b838281518110612dea57612dea61533d565b60200101906001600160f81b031916908160001a905350600101612db3565b5060005b8060f81b8360018551612e2091906154a1565b81518110612e3057612e3061533d565b60200101906001600160f81b031916908160001a9053506000612e53848b613547565b91995097509050600080612e678a8a61363f565b91509150600080612e7884846136b2565b9150915081600014158015612e8c57508015155b15612ec9578415612ea757612ea18282613821565b90925090505b90985096508787612eba8c8c8484613866565b15612ec9575050505050612ee1565b50505050508080612ed9906156f9565b915050612e0d565b505060408051608081018252808201958652606081019690965293855250825180840190935282526020808301919091528201529392505050565b600080600080612f2e8888888861387d565b612f3a57612f3a61570f565b6040805160c081018252898152602081018990529081018790526060810186905260016080820152600060a0820152612f7281613920565b8051602082015160408301516060840151608085015160a0860151959650612f9995613ad2565b929c919b50995090975095505050505050565b612fb46149d2565b8151158015612fc557506020820151155b15612fe3575050604080518082019091526000808252602082015290565b60405180604001604052808360000151815260200160008051602061575c83398151915284602001516130169190615725565b61302e9060008051602061575c8339815191526154a1565b905292915050565b61303e6149d2565b5060408051808201909152600181526002602082015290565b60408051600280825260608201909252600091829190816020015b61307a6149d2565b8152602001906001900390816130725750506040805160028082526060820190925291925060009190602082015b6130b0614a0a565b8152602001906001900390816130a857905050905086826000815181106130d9576130d961533d565b602002602001018190525084826001815181106130f8576130f861533d565b602002602001018190525085816000815181106131175761311761533d565b602002602001018190525083816001815181106131365761313661533d565b602002602001018190525061314b8282613b1c565b925050505b949350505050565b60006005548261316891906153ed565b421192915050565b600060025411613193576040516363cd35d560e11b815260040160405180910390fd5b6001600160401b0381166131ba57604051631d58f8cb60e11b815260040160405180910390fd5b6001600160401b03808216600090815260106020908152604080832081516101408101835281548087168252600160401b90049095168584015260018101546001600160a01b0316858301528151808301835260028201548152600382015481850152606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e086015260088101805483518186028101860190945280845294959491936101008601939290879084015b828210156132ce576000848152602090819020604080516080810182526003860290920180546001600160a01b03908116928401928352600180830154909116606085015291835260020154828401529083529092019101613271565b5050509082525060099190910154602091820152818101805183516001600160401b0390811660009081526010855260408082208054600160401b600160801b031916600160401b9585169590950294909417909355855193518216815282902080546001600160401b0319169390911692909217909155805180820190915260135481526014549181019190915260608201519192506133729161246890612fac565b805160135560200151601455606081015160009061338f906128b2565b6101208301516000908152601b60205260409081902080546001600160401b0319169055519091506012906133c5908390615515565b908152604080516020928190038301902080546001600160401b03191690556001600160401b03851660009081526010909252812080546001600160801b03191681556001810180546001600160a01b0319169055600281018290556003810182905560048101829055600581018290556006810182905560078101829055906134526008830182614a2f565b6009820160009055505060016002600082825461346f91906154a1565b9091555050505050565b600061348e6001600160a01b03841683613e22565b905080516000141580156134b35750808060200190518101906134b19190615739565b155b15610ab55782604051635274afe760e01b8152600401610a419190614f42565b6134db613e37565b610c6457604051631afcd79f60e31b815260040160405180910390fd5b6135006134d3565b6001600160a01b038116610d1e576000604051631e4fbdf760e01b8152600401610a419190614f42565b6135326134d3565b600061353c612284565b805460ff1916905550565b600080600080600080600061357d898960405160200161356991815260200190565b604051602081830303815290604052613e51565b935093509350935060405160308152602080820152602060408201528460608201528360808201526001609082015260008051602061575c83398151915260b082015260208160d0836005600019fa6135d557600080fd5b805197505060405160308152602080820152836050820152602060408201528260708201526001609082015260008051602061575c83398151915260b082015260208160d0836005600019fa61362a57600080fd5b51969996985060019081161496505050505050565b60008060008060008061365488888a8a613f9a565b909250905061366582828a8a613f9a565b90925090506136a382826000805160206157bd8339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d261400b565b90999098509650505050505050565b6000806000806000856000036136f4576136cb8761403f565b909350905080156136e55782600094509450505050612159565b60008394509450505050612159565b60008051602061575c833981519152878809925060008051602061575c833981519152868709915060008051602061575c83398151915282840892506137398361403f565b9093509050806137525760008094509450505050612159565b60008051602061575c833981519152838808915061376f826140d6565b9150600061377c8361403f565b92509050816137cd5761379e888560008051602061575c833981519152614130565b92506137a9836140d6565b92506137b48361403f565b92509050816137cd576000809550955050505050612159565b8060008051602061575c83398151915282830893506137fa8460008051602061575c833981519152614154565b9350600060008051602061575c833981519152858a09919a91995090975050505050505050565b6000808061383d8560008051602061575c8339815191526154a1565b905060006138598560008051602061575c8339815191526154a1565b9196919550909350505050565b60006138748585858561387d565b95945050505050565b600080600080600061389187878989613f9a565b90945092506138a289898181613f9a565b90925090506138b382828b8b613f9a565b90925090506138c4848484846141a5565b909450925061390284846000805160206157bd8339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d26141a5565b909450925083158015613913575082155b9998505050505050505050565b613928614a50565b613930614a50565b613938614a50565b613940614a50565b84516020860151604087015160608801516080890151613978946744e992b44a6909f1949093909290918b60055b60200201516141e7565b805160208201516040830151606084015160808501519497506139a4946002949392919089600561396e565b9150613a108360005b602002015184600160200201518560026020020151866003602002015187600460200201518860056020020151886000602002015189600160200201518a600260200201518b600360200201518c600460200201518d60055b602002015161426a565b9150613a1b826144f3565b9150613a26836144f3565b9050613a31816144f3565b9050613a3e8360006139ad565b9250613aa48360005b6020020151846001602002015185600260200201518660036020020151876004602002015188600560200201518760006020020151886001602002015189600260200201518a600360200201518b600460200201518c6005613a06565b9250613aaf856144f3565b9050613aba816144f3565b9050613ac5816144f3565b9050613874836000613a47565b600080600080600080613ae5888861460b565b9092509050613af68c8c8484613f9a565b9096509450613b078a8a8484613f9a565b969d959c509a50949850929650505050505050565b60008151835114613b2c57600080fd5b82516000613b3b826006615460565b90506000816001600160401b03811115613b5757613b57614cf2565b604051908082528060200260200182016040528015613b80578160200160208202803683370190505b50905060005b83811015613db157868181518110613ba057613ba061533d565b60200260200101516000015182826006613bba9190615460565b613bc59060006153ed565b81518110613bd557613bd561533d565b602002602001018181525050868181518110613bf357613bf361533d565b60200260200101516020015182826006613c0d9190615460565b613c189060016153ed565b81518110613c2857613c2861533d565b602002602001018181525050858181518110613c4657613c4661533d565b6020908102919091010151515182613c5f836006615460565b613c6a9060026153ed565b81518110613c7a57613c7a61533d565b602002602001018181525050858181518110613c9857613c9861533d565b60209081029190910181015151015182613cb3836006615460565b613cbe9060036153ed565b81518110613cce57613cce61533d565b602002602001018181525050858181518110613cec57613cec61533d565b602002602001015160200151600060028110613d0a57613d0a61533d565b602002015182613d1b836006615460565b613d269060046153ed565b81518110613d3657613d3661533d565b602002602001018181525050858181518110613d5457613d5461533d565b602002602001015160200151600160028110613d7257613d7261533d565b602002015182613d83836006615460565b613d8e9060056153ed565b81518110613d9e57613d9e61533d565b6020908102919091010152600101613b86565b50613dba614a6e565b60006020826020860260208601600060086107d05a03f1905080613e145760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964205369676e617475726560781b6044820152606401610a41565b505115159695505050505050565b6060613e3083836000614696565b9392505050565b6000613e41612c51565b54600160401b900460ff16919050565b60008060008060ff85511115613e6657600080fd5b600060405160005b6088811015613e8557600082820152602001613e6e565b506088602060005b8a51811015613eae578a820151848401526020928301929182019101613e8d565b505060898951019050608081830153600201602060005b8951811015613ee65789820151848401526020928301929182019101613ec5565b5050608b88518a5101019050875181830153508751875101608c018120915050604051818152600160208201536021602060005b8951811015613f3b5789820151848401526020928301929182019101613f1a565b505050865187516021018201538651602201812095508582188152600260208201538651602201812094508482188152600360208201538651602201812093508382188152600460208201539551602201909520939692955090935050565b600080613fd860008051602061575c83398151915285880960008051602061575c83398151915285880960008051602061575c833981519152614130565b60008051602061575c8339815191528086880960008051602061575c833981519152868a09089150915094509492505050565b60008060008051602061575c83398151915284870860008051602061575c8339815191528487089150915094509492505050565b600080600060405160208152602080820152602060408201528460608201527f0c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52608082015260008051602061575c83398151915260a082015260208160c08360056107d05a03fa90519350905060008051602061575c83398151915283800984149150806140d05760009250600091505b50915091565b60006001821615156140e960028461548d565b9150801561412a5760008051602061575c833981519152600261411b60008051602061575c83398151915260016153ed565b614125919061548d565b830891505b50919050565b6000818061414057614140615477565b61414a84846154a1565b8508949350505050565b60008060405160208152602080820152602060408201528460608201526002840360808201528360a082015260208160c08360056107d05a03fa9051925090508061419e57600080fd5b5092915050565b6000806141c1868560008051602061575c833981519152614130565b6141da868560008051602061575c833981519152614130565b9150915094509492505050565b6141ef614a50565b871561425f576001881615614230578051602082015160408301516060840151608085015160a086015161422d9594939291908d8d8d8d8d8d61426a565b90505b61423e878787878787614733565b949b5092995090975095509350915061425860028961548d565b97506141ef565b979650505050505050565b614272614a50565b8815801561427e575087155b156142c0578686868686868660005b60a089019290925260808801929092526060870192909252604086019290925260208581019390935290910201526144e3565b821580156142cc575081155b156142df578c8c8c8c8c8c86600061428d565b6142eb85858b8b613f9a565b90955093506142fc8b8b8585613f9a565b6060830152604082015261431287878b8b613f9a565b90975095506143238d8d8585613f9a565b60a08301526080820181905287148015614340575060a081015186145b156143855760408101518514801561435b5750606081015184145b156143765761436e8d8d8d8d8d8d614733565b86600061428d565b6001600081818080868161428d565b61439189898585613f9a565b90935091506143b1858583600260200201518460035b60200201516141a5565b909d509b506143cb878783600460200201518460056143a7565b909b5099506143dc8b8b8181613f9a565b90995097506143fc898983600460200201518460055b6020020151613f9a565b909550935061440d89898d8d613f9a565b909950975061441e89898585613f9a565b60a083015260808201526144348d8d8181613f9a565b909750955061444587878585613f9a565b909750955061445687878b8b6141a5565b9097509550614467858560026148a2565b9093509150614478878785856141a5565b90975095506144898b8b8989613f9a565b6020830152815261449c858589896141a5565b909b5099506144ad8d8d8d8d613f9a565b909b5099506144c7898983600260200201518460036143f2565b909d509b506144d88b8b8f8f6141a5565b606083015260408201525b9c9b505050505050505050505050565b6144fb614a50565b815161450f908360015b60200201516148d5565b60208301528152604082015161452790836003614505565b60608301526040820152608082015161454290836005614505565b60a083015260808201528051602082015161459f91907f2fb347984f7911f74c0bec3cf559b143b78cc310c2c3330c99e39557176f553d7f16c9e55061ebae204ba4cc8bd75a079432ae2a1d0b7c9dce1665d51c640fcba2613f9a565b60208301528152604081015160608201516145fc91907f063cf305489af5dcdc5ec698b6e2f9b9dbaae0eda9c95998dc54014671a0135a7f07c03cbcac41049a0704b5a7ec796f2b21807dc98fa25bd282d37f632623b0e3613f9a565b60608301526040820152919050565b6000808061464c60008051602061575c8339815191528087880960008051602061575c8339815191528788090860008051602061575c833981519152614154565b905060008051602061575c83398151915281860960008051602061575c83398151915282860961468a9060008051602061575c8339815191526154a1565b92509250509250929050565b6060814710156146bb573060405163cd78605960e01b8152600401610a419190614f42565b600080856001600160a01b031684866040516146d79190615515565b60006040518083038185875af1925050503d8060008114614714576040519150601f19603f3d011682016040523d82523d6000602084013e614719565b606091505b50915091506147298683836148fc565b9695505050505050565b6000806000806000806147488c8c60036148a2565b909650945061475986868e8e613f9a565b909650945061476a8a8a8a8a613f9a565b909850965061477b8c8c8c8c613f9a565b909450925061478c84848a8a613f9a565b909450925061479d86868181613f9a565b909c509a506147ae848460086148a2565b90925090506147bf8c8c84846141a5565b909c509a506147d088888181613f9a565b90925090506147e1848460046148a2565b90945092506147f284848e8e6141a5565b909450925061480384848888613f9a565b90945092506148148a8a60086148a2565b909650945061482586868c8c613f9a565b909650945061483686868484613f9a565b9096509450614847848488886141a5565b90945092506148588c8c60026148a2565b909650945061486986868a8a613f9a565b909650945061487a88888484613f9a565b909250905061488b828260086148a2565b809250819350505096509650965096509650969050565b60008060008051602061575c83398151915283860960008051602061575c83398151915284860991509150935093915050565b600080836148f18460008051602061575c8339815191526154a1565b915091509250929050565b6060826149115761490c8261494f565b613e30565b815115801561492857506001600160a01b0384163b155b156149485783604051639996b31560e01b8152600401610a419190614f42565b5080613e30565b80511561495f5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b604080516101408101825260008082526020820181905291810191909152606081016149a26149d2565b81526020016000815260200160008152602001600081526020016000815260200160608152602001600081525090565b604051806040016040528060008152602001600081525090565b60405180608001604052806004906020820280368337509192915050565b6040518060400160405280614a1d614a8c565b8152602001614a2a614a8c565b905290565b508054600082556003029060005260206000209081019061080c9190614aaa565b6040518060c001604052806006906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b60405180604001604052806002906020820280368337509192915050565b5b80821115614add5780546001600160a01b03199081168255600182018054909116905560006002820155600301614aab565b5090565b80356001600160401b0381168114614af857600080fd5b919050565b600060208284031215614b0f57600080fd5b613e3082614ae1565b80518252602090810151910152565b805180516001600160a01b03908116845260209182015116818401520151604082015260600190565b600081518084526020840193506020830160005b82811015614b8857614b77868351614b27565b955060209190910190600101614b64565b5093949350505050565b60208152614bac6020820183516001600160401b03169052565b60006020830151614bc860408401826001600160401b03169052565b5060408301516001600160a01b0381166060840152506060830151614bf06080840182614b18565b50608083015160c083015260a083015160e083015260c083015161010083015260e0830151610120830152610100830151610160610140840152614c38610180840182614b50565b90506101208401516101608401528091505092915050565b600060208284031215614c6257600080fd5b5035919050565b6001600160401b0391909116815260200190565b60008060208385031215614c9057600080fd5b82356001600160401b03811115614ca657600080fd5b8301601f81018513614cb757600080fd5b80356001600160401b03811115614ccd57600080fd5b8560208260051b8401011115614ce257600080fd5b6020919091019590945092505050565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715614d2a57614d2a614cf2565b60405290565b604051608081016001600160401b0381118282101715614d2a57614d2a614cf2565b604051601f8201601f191681016001600160401b0381118282101715614d7a57614d7a614cf2565b604052919050565b600060208284031215614d9457600080fd5b81356001600160401b03811115614daa57600080fd5b8201601f81018413614dbb57600080fd5b80356001600160401b03811115614dd457614dd4614cf2565b614de7601f8201601f1916602001614d52565b818152856020838501011115614dfc57600080fd5b81602084016020830137600091810160200191909152949350505050565b6001600160a01b038116811461080c57600080fd5b60006080828403121561412a57600080fd5b60006001600160401b03821115614e5a57614e5a614cf2565b5060051b60200190565b600082601f830112614e7557600080fd5b8135614e88614e8382614e41565b614d52565b8082825260208201915060208360051b860101925085831115614eaa57600080fd5b602085015b83811015614ece57614ec081614ae1565b835260209283019201614eaf565b5095945050505050565b60008060008060e08587031215614eee57600080fd5b8435614ef981614e1a565b935060208501359250614f0f8660408701614e2f565b915060c08501356001600160401b03811115614f2a57600080fd5b614f3687828801614e64565b91505092959194509250565b6001600160a01b0391909116815260200190565b604081016123af8284614b18565b600080600080848603610100811215614f7c57600080fd5b6040811215614f8a57600080fd5b5084935060408401359250614fa28660608601614e2f565b915060e08501356001600160401b03811115614f2a57600080fd5b6040808252835190820181905260009060208501906060840190835b818110156150005783516001600160401b0316835260209384019390920191600101614fd9565b50508381036020808601919091528551808352918101925085019060005b8181101561504757615031848451614b18565b604093909301926020929092019160010161501e565b50919695505050505050565b60006040828403121561506557600080fd5b61506d614d08565b823581526020928301359281019290925250919050565b60006080828403121561509657600080fd5b61509e614d30565b8235815260208084013590820152604080840135908201526060928301359281019290925250919050565b600082601f8301126150da57600080fd5b81356150e8614e8382614e41565b8082825260208201915060206060840286010192508583111561510a57600080fd5b602085015b83811015614ece57808703606081121561512857600080fd5b615130614d08565b604082121561513e57600080fd5b615146614d08565b9150823561515381614e1a565b8252602083013561516381614e1a565b6020838101919091529181526040830135818301528452929092019160600161510f565b60008060008084860361016081121561519f57600080fd5b6151a98787615053565b94506151b88760408801615084565b9350608060bf19820112156151cc57600080fd5b506151d5614d30565b60c0860135815260e08601356020820152610100860135604082015261012086013561ffff8116811461520757600080fd5b606082015291506101408501356001600160401b0381111561522857600080fd5b614f36878288016150c9565b60008060006060848603121561524957600080fd5b505081359360208301359350604090920135919050565b60006020828403121561527257600080fd5b8135613e3081614e1a565b918252602082015260400190565b600080600080600080600060e0888a0312156152a657600080fd5b87356152b181614e1a565b965060208801356152c181614e1a565b96999698505050506040850135946060810135946080820135945060a0820135935060c0909101359150565b600080600080610100858703121561530457600080fd5b61530e8686615053565b935061531d8660408701615084565b925060c085013561532d81614e1a565b9396929550929360e00135925050565b634e487b7160e01b600052603260045260246000fd5b60008235609e1983360301811261536957600080fd5b9190910192915050565b6000808335601e1984360301811261538a57600080fd5b8301803591506001600160401b038211156153a457600080fd5b602001915060608102360382131561215957600080fd5b6000604082840312156153cd57600080fd5b613e308383615053565b634e487b7160e01b600052601160045260246000fd5b808201808211156123af576123af6153d7565b80546001600160a01b0319166001600160a01b0392909216919091179055565b813561542b81614e1a565b6154358183615400565b50602082013561544481614e1a565b6154518160018401615400565b50604082013560028201555050565b80820281158282048414176123af576123af6153d7565b634e487b7160e01b600052601260045260246000fd5b60008261549c5761549c615477565b500490565b818103818111156123af576123af6153d7565b6000608082840312156154c657600080fd5b613e308383615084565b6001600160401b039390931683526020830191909152604082015260600190565b60005b8381101561550c5781810151838201526020016154f4565b50506000910152565b600082516153698184602087016154f1565b6001600160a01b0385168152600061010082016155476020840187614b18565b8451606084015260208501516080840152604085015160a084015261ffff60608601511660c084015261010060e08401528084518083526101208501915060208601925060005b818110156155b2576155a1838551614b27565b60209490940193925060010161558e565b509098975050505050505050565b6001600160a01b038316815260608101613e306020830184614b18565b6001600160401b0381811683821601908111156123af576123af6153d7565b60006001820161560e5761560e6153d7565b5060010190565b60006001600160401b0382166002600160401b03198101615638576156386153d7565b60010192915050565b8135815260208083013590820152604081016123af565b6001600160401b038316815260608101613e30602083018480358252602090810135910152565b6001600160a01b038416815260208101839052608081016131506040830184614b18565b6001600160401b039290921682526001600160a01b0316602082015260400190565b600084516156d78184602089016154f1565b919091019283525060601b6001600160601b0319166020820152603401919050565b600060ff821660ff8103615638576156386153d7565b634e487b7160e01b600052600160045260246000fd5b60008261573457615734615477565b500690565b60006020828403121561574b57600080fd5b81518015158114613e3057600080fdfe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd476e0956cda88cad152e89927e53611735b61a5c762d1428573c6931b0a5efcb01424c535f5349475f545259414e44494e4352454d454e545f4c49515549444154452b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5a164736f6c634300081a000a", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/src/web3client/abis/Token.json b/src/web3client/abis/Token.json new file mode 100644 index 0000000..dd75ff5 --- /dev/null +++ b/src/web3client/abis/Token.json @@ -0,0 +1,545 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "SESH", + "sourceName": "contracts/SESH.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "totalSupply_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiverGenesisAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ECDSAInvalidSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "ECDSAInvalidSignatureLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "ECDSAInvalidSignatureS", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "ERC2612ExpiredSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC2612InvalidSigner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentNonce", + "type": "uint256" + } + ], + "name": "InvalidAccountNonce", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidShortString", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "str", + "type": "string" + } + ], + "name": "StringTooLong", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x61016060405234801561001157600080fd5b5060405161155838038061155883398101604081905261003091610429565b6040518060400160405280600d81526020016c29b2b9b9b4b7b7102a37b5b2b760991b81525080604051806040016040528060018152602001603160f81b8152506040518060400160405280600d81526020016c29b2b9b9b4b7b7102a37b5b2b760991b815250604051806040016040528060048152602001630a68aa6960e31b81525081600390816100c39190610505565b5060046100d08282610505565b506100e091508390506005610254565b610120526100ef816006610254565b61014052815160208084019190912060e052815190820120610100524660a05261017c60e05161010051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905090565b60805250503060c05250806001600160a01b0381166101f05760405162461bcd60e51b815260206004820152602560248201527f5368617265643a205a65726f2d61646472657373206973206e6f74207065726d6044820152641a5d1d195960da1b60648201526084015b60405180910390fd5b82806000036102415760405162461bcd60e51b815260206004820152601b60248201527f5368617265643a2075696e7420696e70757420697320656d707479000000000060448201526064016101e7565b61024b8385610287565b50505050610656565b600060208351101561027057610269836102c1565b9050610281565b8161027b8482610505565b5060ff90505b92915050565b6001600160a01b0382166102b15760405163ec442f0560e01b8152600060048201526024016101e7565b6102bd600083836102ff565b5050565b600080829050601f815111156102ec578260405163305a27a960e01b81526004016101e791906105c3565b80516102f782610611565b179392505050565b6001600160a01b03831661032a57806002600082825461031f9190610635565b9091555061039c9050565b6001600160a01b0383166000908152602081905260409020548181101561037d5760405163391434e360e21b81526001600160a01b038516600482015260248101829052604481018390526064016101e7565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b0382166103b8576002805482900390556103d7565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161041c91815260200190565b60405180910390a3505050565b6000806040838503121561043c57600080fd5b825160208401519092506001600160a01b038116811461045b57600080fd5b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061049057607f821691505b6020821081036104b057634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561050057806000526020600020601f840160051c810160208510156104dd5750805b601f840160051c820191505b818110156104fd57600081556001016104e9565b50505b505050565b81516001600160401b0381111561051e5761051e610466565b6105328161052c845461047c565b846104b6565b6020601f821160018114610566576000831561054e5750848201515b600019600385901b1c1916600184901b1784556104fd565b600084815260208120601f198516915b828110156105965787850151825560209485019460019092019101610576565b50848210156105b45786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b602081526000825180602084015260005b818110156105f157602081860181015160408684010152016105d4565b506000604082850101526040601f19601f83011684010191505092915050565b805160208083015191908110156104b05760001960209190910360031b1b16919050565b8082018082111561028157634e487b7160e01b600052601160045260246000fd5b60805160a05160c05160e051610100516101205161014051610ea86106b060003960006106b80152600061068b015260006106330152600061060b0152600061056601526000610590015260006105ba0152610ea86000f3fe608060405234801561001057600080fd5b50600436106100af5760003560e01c806306fdde03146100b4578063095ea7b3146100d257806318160ddd146100f557806323b872dd14610107578063313ce5671461011a5780633644e5151461012957806370a08231146101315780637ecebe001461015a57806384b0196e1461016d57806395d89b4114610188578063a9059cbb14610190578063d505accf146101a3578063dd62ed3e146101b8575b600080fd5b6100bc6101cb565b6040516100c99190610bff565b60405180910390f35b6100e56100e0366004610c35565b61025d565b60405190151581526020016100c9565b6002545b6040519081526020016100c9565b6100e5610115366004610c5f565b610277565b604051600981526020016100c9565b6100f961029b565b6100f961013f366004610c9c565b6001600160a01b031660009081526020819052604090205490565b6100f9610168366004610c9c565b6102aa565b6101756102c8565b6040516100c99796959493929190610cb7565b6100bc61030e565b6100e561019e366004610c35565b61031d565b6101b66101b1366004610d4f565b61032b565b005b6100f96101c6366004610dc2565b61046a565b6060600380546101da90610df5565b80601f016020809104026020016040519081016040528092919081815260200182805461020690610df5565b80156102535780601f1061022857610100808354040283529160200191610253565b820191906000526020600020905b81548152906001019060200180831161023657829003601f168201915b5050505050905090565b60003361026b818585610495565b60019150505b92915050565b6000336102858582856104a7565b6102908585856104fa565b506001949350505050565b60006102a5610559565b905090565b6001600160a01b038116600090815260076020526040812054610271565b6000606080600080600060606102dc610684565b6102e46106b1565b60408051600080825260208201909252600f60f81b9b939a50919850469750309650945092509050565b6060600480546101da90610df5565b60003361026b8185856104fa565b834211156103545760405163313c898160e11b8152600481018590526024015b60405180910390fd5b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98888886103a18c6001600160a01b0316600090815260076020526040902080546001810190915590565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e00160405160208183030381529060405280519060200120905060006103fc826106de565b9050600061040c8287878761070b565b9050896001600160a01b0316816001600160a01b031614610453576040516325c0072360e11b81526001600160a01b0380831660048301528b16602482015260440161034b565b61045e8a8a8a610495565b50505050505050505050565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6104a28383836001610739565b505050565b60006104b3848461046a565b905060001981146104f457818110156104e557828183604051637dc7a0d960e11b815260040161034b93929190610e2f565b6104f484848484036000610739565b50505050565b6001600160a01b038316610524576000604051634b637e8f60e11b815260040161034b9190610e50565b6001600160a01b03821661054e57600060405163ec442f0560e01b815260040161034b9190610e50565b6104a283838361080e565b6000306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156105b257507f000000000000000000000000000000000000000000000000000000000000000046145b156105dc57507f000000000000000000000000000000000000000000000000000000000000000090565b6102a5604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905090565b60606102a57f00000000000000000000000000000000000000000000000000000000000000006005610925565b60606102a57f00000000000000000000000000000000000000000000000000000000000000006006610925565b60006102716106eb610559565b8360405161190160f01b8152600281019290925260228201526042902090565b60008060008061071d888888886109d0565b92509250925061072d8282610a95565b50909695505050505050565b6001600160a01b03841661076357600060405163e602df0560e01b815260040161034b9190610e50565b6001600160a01b03831661078d576000604051634a1406b160e11b815260040161034b9190610e50565b6001600160a01b03808516600090815260016020908152604080832093871683529290522082905580156104f457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161080091815260200190565b60405180910390a350505050565b6001600160a01b03831661083957806002600082825461082e9190610e64565b909155506108989050565b6001600160a01b038316600090815260208190526040902054818110156108795783818360405163391434e360e21b815260040161034b93929190610e2f565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b0382166108b4576002805482900390556108d3565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161091891815260200190565b60405180910390a3505050565b606060ff831461093f5761093883610b52565b9050610271565b81805461094b90610df5565b80601f016020809104026020016040519081016040528092919081815260200182805461097790610df5565b80156109c45780601f10610999576101008083540402835291602001916109c4565b820191906000526020600020905b8154815290600101906020018083116109a757829003601f168201915b50505050509050610271565b600080806fa2a8918ca85bafe22016d0b997e4df60600160ff1b03841115610a015750600091506003905082610a8b565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015610a55573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610a8157506000925060019150829050610a8b565b9250600091508190505b9450945094915050565b6000826003811115610aa957610aa9610e85565b03610ab2575050565b6001826003811115610ac657610ac6610e85565b03610ae45760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115610af857610af8610e85565b03610b195760405163fce698f760e01b81526004810182905260240161034b565b6003826003811115610b2d57610b2d610e85565b03610b4e576040516335e2f38360e21b81526004810182905260240161034b565b5050565b60606000610b5f83610b91565b604080516020808252818301909252919250600091906020820181803683375050509182525060208101929092525090565b600060ff8216601f81111561027157604051632cd44ac360e21b815260040160405180910390fd5b6000815180845260005b81811015610bdf57602081850181015186830182015201610bc3565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000610c126020830184610bb9565b9392505050565b80356001600160a01b0381168114610c3057600080fd5b919050565b60008060408385031215610c4857600080fd5b610c5183610c19565b946020939093013593505050565b600080600060608486031215610c7457600080fd5b610c7d84610c19565b9250610c8b60208501610c19565b929592945050506040919091013590565b600060208284031215610cae57600080fd5b610c1282610c19565b60ff60f81b8816815260e060208201526000610cd660e0830189610bb9565b8281036040840152610ce88189610bb9565b606084018890526001600160a01b038716608085015260a0840186905283810360c08501528451808252602080870193509091019060005b81811015610d3e578351835260209384019390920191600101610d20565b50909b9a5050505050505050505050565b600080600080600080600060e0888a031215610d6a57600080fd5b610d7388610c19565b9650610d8160208901610c19565b95506040880135945060608801359350608088013560ff81168114610da557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215610dd557600080fd5b610dde83610c19565b9150610dec60208401610c19565b90509250929050565b600181811c90821680610e0957607f821691505b602082108103610e2957634e487b7160e01b600052602260045260246000fd5b50919050565b6001600160a01b039390931683526020830191909152604082015260600190565b6001600160a01b0391909116815260200190565b8082018082111561027157634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052602160045260246000fdfea164736f6c634300081a000a", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100af5760003560e01c806306fdde03146100b4578063095ea7b3146100d257806318160ddd146100f557806323b872dd14610107578063313ce5671461011a5780633644e5151461012957806370a08231146101315780637ecebe001461015a57806384b0196e1461016d57806395d89b4114610188578063a9059cbb14610190578063d505accf146101a3578063dd62ed3e146101b8575b600080fd5b6100bc6101cb565b6040516100c99190610bff565b60405180910390f35b6100e56100e0366004610c35565b61025d565b60405190151581526020016100c9565b6002545b6040519081526020016100c9565b6100e5610115366004610c5f565b610277565b604051600981526020016100c9565b6100f961029b565b6100f961013f366004610c9c565b6001600160a01b031660009081526020819052604090205490565b6100f9610168366004610c9c565b6102aa565b6101756102c8565b6040516100c99796959493929190610cb7565b6100bc61030e565b6100e561019e366004610c35565b61031d565b6101b66101b1366004610d4f565b61032b565b005b6100f96101c6366004610dc2565b61046a565b6060600380546101da90610df5565b80601f016020809104026020016040519081016040528092919081815260200182805461020690610df5565b80156102535780601f1061022857610100808354040283529160200191610253565b820191906000526020600020905b81548152906001019060200180831161023657829003601f168201915b5050505050905090565b60003361026b818585610495565b60019150505b92915050565b6000336102858582856104a7565b6102908585856104fa565b506001949350505050565b60006102a5610559565b905090565b6001600160a01b038116600090815260076020526040812054610271565b6000606080600080600060606102dc610684565b6102e46106b1565b60408051600080825260208201909252600f60f81b9b939a50919850469750309650945092509050565b6060600480546101da90610df5565b60003361026b8185856104fa565b834211156103545760405163313c898160e11b8152600481018590526024015b60405180910390fd5b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98888886103a18c6001600160a01b0316600090815260076020526040902080546001810190915590565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e00160405160208183030381529060405280519060200120905060006103fc826106de565b9050600061040c8287878761070b565b9050896001600160a01b0316816001600160a01b031614610453576040516325c0072360e11b81526001600160a01b0380831660048301528b16602482015260440161034b565b61045e8a8a8a610495565b50505050505050505050565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6104a28383836001610739565b505050565b60006104b3848461046a565b905060001981146104f457818110156104e557828183604051637dc7a0d960e11b815260040161034b93929190610e2f565b6104f484848484036000610739565b50505050565b6001600160a01b038316610524576000604051634b637e8f60e11b815260040161034b9190610e50565b6001600160a01b03821661054e57600060405163ec442f0560e01b815260040161034b9190610e50565b6104a283838361080e565b6000306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161480156105b257507f000000000000000000000000000000000000000000000000000000000000000046145b156105dc57507f000000000000000000000000000000000000000000000000000000000000000090565b6102a5604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905090565b60606102a57f00000000000000000000000000000000000000000000000000000000000000006005610925565b60606102a57f00000000000000000000000000000000000000000000000000000000000000006006610925565b60006102716106eb610559565b8360405161190160f01b8152600281019290925260228201526042902090565b60008060008061071d888888886109d0565b92509250925061072d8282610a95565b50909695505050505050565b6001600160a01b03841661076357600060405163e602df0560e01b815260040161034b9190610e50565b6001600160a01b03831661078d576000604051634a1406b160e11b815260040161034b9190610e50565b6001600160a01b03808516600090815260016020908152604080832093871683529290522082905580156104f457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161080091815260200190565b60405180910390a350505050565b6001600160a01b03831661083957806002600082825461082e9190610e64565b909155506108989050565b6001600160a01b038316600090815260208190526040902054818110156108795783818360405163391434e360e21b815260040161034b93929190610e2f565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b0382166108b4576002805482900390556108d3565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161091891815260200190565b60405180910390a3505050565b606060ff831461093f5761093883610b52565b9050610271565b81805461094b90610df5565b80601f016020809104026020016040519081016040528092919081815260200182805461097790610df5565b80156109c45780601f10610999576101008083540402835291602001916109c4565b820191906000526020600020905b8154815290600101906020018083116109a757829003601f168201915b50505050509050610271565b600080806fa2a8918ca85bafe22016d0b997e4df60600160ff1b03841115610a015750600091506003905082610a8b565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015610a55573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610a8157506000925060019150829050610a8b565b9250600091508190505b9450945094915050565b6000826003811115610aa957610aa9610e85565b03610ab2575050565b6001826003811115610ac657610ac6610e85565b03610ae45760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115610af857610af8610e85565b03610b195760405163fce698f760e01b81526004810182905260240161034b565b6003826003811115610b2d57610b2d610e85565b03610b4e576040516335e2f38360e21b81526004810182905260240161034b565b5050565b60606000610b5f83610b91565b604080516020808252818301909252919250600091906020820181803683375050509182525060208101929092525090565b600060ff8216601f81111561027157604051632cd44ac360e21b815260040160405180910390fd5b6000815180845260005b81811015610bdf57602081850181015186830182015201610bc3565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000610c126020830184610bb9565b9392505050565b80356001600160a01b0381168114610c3057600080fd5b919050565b60008060408385031215610c4857600080fd5b610c5183610c19565b946020939093013593505050565b600080600060608486031215610c7457600080fd5b610c7d84610c19565b9250610c8b60208501610c19565b929592945050506040919091013590565b600060208284031215610cae57600080fd5b610c1282610c19565b60ff60f81b8816815260e060208201526000610cd660e0830189610bb9565b8281036040840152610ce88189610bb9565b606084018890526001600160a01b038716608085015260a0840186905283810360c08501528451808252602080870193509091019060005b81811015610d3e578351835260209384019390920191600101610d20565b50909b9a5050505050505050505050565b600080600080600080600060e0888a031215610d6a57600080fd5b610d7388610c19565b9650610d8160208901610c19565b95506040880135945060608801359350608088013560ff81168114610da557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215610dd557600080fd5b610dde83610c19565b9150610dec60208401610c19565b90509250929050565b600181811c90821680610e0957607f821691505b602082108103610e2957634e487b7160e01b600052602260045260246000fd5b50919050565b6001600160a01b039390931683526020830191909152604082015260600190565b6001600160a01b0391909116815260200190565b8082018082111561027157634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052602160045260246000fdfea164736f6c634300081a000a", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/src/web3client/abis/TokenVestingStaking.json b/src/web3client/abis/TokenVestingStaking.json new file mode 100644 index 0000000..272f92d --- /dev/null +++ b/src/web3client/abis/TokenVestingStaking.json @@ -0,0 +1,537 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "TokenVestingStaking", + "sourceName": "contracts/utils/TokenVestingStaking.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "beneficiary_", + "type": "address" + }, + { + "internalType": "address", + "name": "revoker_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "start_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "end_", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "transferableBeneficiary_", + "type": "bool" + }, + { + "internalType": "contract IServiceNodeRewards", + "name": "rewardsContract_", + "type": "address" + }, + { + "internalType": "contract IServiceNodeContributionFactory", + "name": "snContribFactory_", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "sesh_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldBeneficiary", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newBeneficiary", + "type": "address" + } + ], + "name": "BeneficiaryTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldRevoker", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newRevoker", + "type": "address" + } + ], + "name": "RevokerTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "TokenVestingRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokensReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokensRevokedReleased", + "type": "event" + }, + { + "inputs": [], + "name": "SESH", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "blsPubkey", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.BLSSignatureParams", + "name": "blsSignature", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature2", + "type": "uint256" + }, + { + "internalType": "uint16", + "name": "fee", + "type": "uint16" + } + ], + "internalType": "struct IServiceNodeRewards.ServiceNodeParams", + "name": "serviceNodeParams", + "type": "tuple" + }, + { + "internalType": "address", + "name": "snBeneficiary", + "type": "address" + } + ], + "name": "addBLSPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "beneficiary", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "claimRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "snContribAddr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "snContribBeneficiary", + "type": "address" + } + ], + "name": "contributeFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "end", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "initiateExitBLSPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "release", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "revoke", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "revoked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "revoker", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardsContract", + "outputs": [ + { + "internalType": "contract IServiceNodeRewards", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "snContribFactory", + "outputs": [ + { + "internalType": "contract IServiceNodeContributionFactory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "start", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beneficiary_", + "type": "address" + } + ], + "name": "transferBeneficiary", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "revoker_", + "type": "address" + } + ], + "name": "transferRevoker", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "transferableBeneficiary", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "snContribAddr", + "type": "address" + }, + { + "internalType": "address", + "name": "snContribBeneficiary", + "type": "address" + } + ], + "name": "updateBeneficiary", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "factoryAddr", + "type": "address" + } + ], + "name": "updateContributionFactory", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "snContribAddr", + "type": "address" + } + ], + "name": "withdrawContribution", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x61012060405234801561001157600080fd5b50604051611ddc380380611ddc833981016040819052610030916101d1565b876001600160a01b0381166100605760405162461bcd60e51b815260040161005790610273565b60405180910390fd5b836001600160a01b0381166100875760405162461bcd60e51b815260040161005790610273565b826001600160a01b0381166100ae5760405162461bcd60e51b815260040161005790610273565b878911156100fe5760405162461bcd60e51b815260206004820152601a60248201527f56657374696e673a2073746172745f20616674657220656e645f0000000000006044820152606401610057565b8842106101585760405162461bcd60e51b815260206004820152602260248201527f56657374696e673a207374617274206265666f72652063757272656e742074696044820152616d6560f01b6064820152608401610057565b5050600080546001600160a01b03199081166001600160a01b039b8c1617909155600180548216998b16999099179098555060a09590955260c09390935290151560805284166101005260028054909316908416179091551660e0526102b8565b6001600160a01b03811681146101ce57600080fd5b50565b600080600080600080600080610100898b0312156101ee57600080fd5b88516101f9816101b9565b60208a015190985061020a816101b9565b60408a015160608b015160808c01519299509097509550801515811461022f57600080fd5b60a08a0151909450610240816101b9565b60c08a0151909350610251816101b9565b60e08a0151909250610262816101b9565b809150509295985092959890939650565b60208082526025908201527f5368617265643a205a65726f2d61646472657373206973206e6f74207065726d6040820152641a5d1d195960da1b606082015260800190565b60805160a05160c05160e05161010051611a6261037a6000396000818161015f015281816103f5015281816107ec01528181610db201528181610f1201528181610f9701526112a00152600081816102cf015281816109560152610ee501526000818161030901528181610bbb01526112db01526000818161029a015281816103a1015281816107ac015281816108cc01528181610aa701528181610d490152818161107a01526112440152600081816101dd01526104b00152611a626000f3fe608060405234801561001057600080fd5b50600436106101075760003560e01c80630962ef791461010c57806314bbe21c146101215780631916558714610134578063205a306114610147578063220cce971461015a57806335dec5d114610197578063372500ab146101aa57806338af3eed146101b257806343cac780146101c557806352208a5f146101d857806363d256ce1461020f5780637249d9d91461022357806374a8f103146102365780638a0b4e991461024957806395e3fd371461025c578063a35c47091461026f578063ac8d6c0c14610282578063be9a655514610295578063c3aca558146102ca578063d9054b09146102f1578063efbe1c1c14610304575b600080fd5b61011f61011a366004611657565b61032b565b005b61011f61012f366004611685565b61045d565b61011f610142366004611685565b610593565b61011f610155366004611685565b6106cb565b6101817f000000000000000000000000000000000000000000000000000000000000000081565b60405161018e91906116a2565b60405180910390f35b600154610181906001600160a01b031681565b61011f61073f565b600054610181906001600160a01b031681565b61011f6101d33660046116b6565b61085f565b6101ff7f000000000000000000000000000000000000000000000000000000000000000081565b604051901515815260200161018e565b6001546101ff90600160a01b900460ff1681565b61011f6102313660046116f8565b610a3a565b61011f610244366004611685565b610b7e565b600254610181906001600160a01b031681565b61011f61026a366004611743565b610cdc565b61011f61027d366004611685565b61100d565b61011f610290366004611685565b61111c565b6102bc7f000000000000000000000000000000000000000000000000000000000000000081565b60405190815260200161018e565b6101817f000000000000000000000000000000000000000000000000000000000000000081565b61011f6102ff3660046117a5565b6111d7565b6102bc7f000000000000000000000000000000000000000000000000000000000000000081565b600154600160a01b900460ff1615610375576001546001600160a01b031633146103705760405162461bcd60e51b8152600401610367906117ce565b60405180910390fd5b61039f565b6000546001600160a01b0316331461039f5760405162461bcd60e51b815260040161036790611805565b7f00000000000000000000000000000000000000000000000000000000000000004210156103df5760405162461bcd60e51b815260040161036790611848565b604051630962ef7960e01b8152600481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690630962ef79906024015b600060405180830381600087803b15801561044257600080fd5b505af1158015610456573d6000803e3d6000fd5b5050505050565b6000546001600160a01b031633146104875760405162461bcd60e51b815260040161036790611805565b806001600160a01b0381166104ae5760405162461bcd60e51b815260040161036790611876565b7f00000000000000000000000000000000000000000000000000000000000000006105295760405162461bcd60e51b815260206004820152602560248201527f56657374696e673a2062656e6566696369617279206e6f74207472616e7366656044820152647261626c6560d81b6064820152608401610367565b6000546040517f57005c5083fa0952870a7906715a2f6f9ef2d01b4a423e4b3ce59c6129b1a76391610568916001600160a01b039091169085906118bb565b60405180910390a150600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146105bd5760405162461bcd60e51b815260040161036790611805565b600154600160a01b900460ff16156106105760405162461bcd60e51b815260206004820152601660248201527515995cdd1a5b99ce881d1bdad95b881c995d9bdad95960521b6044820152606401610367565b600061061b826112d7565b90506000811161066a5760405162461bcd60e51b815260206004820152601a60248201527956657374696e673a206e6f20746f6b656e73206172652064756560301b6044820152606401610367565b816001600160a01b03167fc7798891864187665ac6dd119286e44ec13f014527aeeb2b8eb3fd413df93179826040516106a591815260200190565b60405180910390a26000546106c7906001600160a01b0384811691168361137b565b5050565b6001546001600160a01b031633146106f55760405162461bcd60e51b8152600401610367906117ce565b806001600160a01b03811661071c5760405162461bcd60e51b815260040161036790611876565b50600280546001600160a01b0319166001600160a01b0392909216919091179055565b600154600160a01b900460ff1615610780576001546001600160a01b0316331461077b5760405162461bcd60e51b8152600401610367906117ce565b6107aa565b6000546001600160a01b031633146107aa5760405162461bcd60e51b815260040161036790611805565b7f00000000000000000000000000000000000000000000000000000000000000004210156107ea5760405162461bcd60e51b815260040161036790611848565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663372500ab6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561084557600080fd5b505af1158015610859573d6000803e3d6000fd5b50505050565b600154600160a01b900460ff16156108a0576001546001600160a01b0316331461089b5760405162461bcd60e51b8152600401610367906117ce565b6108ca565b6000546001600160a01b031633146108ca5760405162461bcd60e51b815260040161036790611805565b7f000000000000000000000000000000000000000000000000000000000000000042101561090a5760405162461bcd60e51b815260040161036790611848565b806001600160a01b0381166109315760405162461bcd60e51b815260040161036790611876565b600061093c856113d8565b60405163095ea7b360e01b81529091506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b39061098d90889088906004016118d5565b6020604051808303816000875af11580156109ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109d091906118ee565b50604051632816ee7360e01b8152600481018590526001600160a01b038481166024830152821690632816ee7390604401600060405180830381600087803b158015610a1b57600080fd5b505af1158015610a2f573d6000803e3d6000fd5b505050505050505050565b600154600160a01b900460ff1615610a7b576001546001600160a01b03163314610a765760405162461bcd60e51b8152600401610367906117ce565b610aa5565b6000546001600160a01b03163314610aa55760405162461bcd60e51b815260040161036790611805565b7f0000000000000000000000000000000000000000000000000000000000000000421015610ae55760405162461bcd60e51b815260040161036790611848565b806001600160a01b038116610b0c5760405162461bcd60e51b815260040161036790611876565b6000610b17846113d8565b604051630557fe9560e11b81529091506001600160a01b03821690630aaffd2a90610b469086906004016116a2565b600060405180830381600087803b158015610b6057600080fd5b505af1158015610b74573d6000803e3d6000fd5b5050505050505050565b6001546001600160a01b03163314610ba85760405162461bcd60e51b8152600401610367906117ce565b600154600160a01b900460ff16610c6c577f0000000000000000000000000000000000000000000000000000000000000000421115610c245760405162461bcd60e51b815260206004820152601860248201527715995cdd1a5b99ce881d995cdd1a5b99c8195e1c1a5c995960421b6044820152606401610367565b6001805460ff60a01b1916600160a01b1790556040516001600160a01b038216907f39983c6d4d174a7aee564f449d4a5c3c7ac9649d72b7793c56901183996f8af690600090a25b6000610c77826112d7565b905080156106c757816001600160a01b03167fa415e62e43678c8b550c180a7ef9a2031e826b306d4052104d288f3c83ac964b82604051610cba91815260200190565b60405180910390a26001546106c7906001600160a01b0384811691168361137b565b600154600160a01b900460ff1615610d1d576001546001600160a01b03163314610d185760405162461bcd60e51b8152600401610367906117ce565b610d47565b6000546001600160a01b03163314610d475760405162461bcd60e51b815260040161036790611805565b7f0000000000000000000000000000000000000000000000000000000000000000421015610d875760405162461bcd60e51b815260040161036790611848565b806001600160a01b038116610dae5760405162461bcd60e51b815260040161036790611876565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e329190611910565b60408051600180825281830190925291925060009190816020015b6040805160808101825260009181018281526060820183905281526020810191909152815260200190600190039081610e4d57505060408051608081018252309181019182526001600160a01b0387166060820152908152602081018490528151919250908290600090610ec357610ec3611929565b602090810291909101015260405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b390610f3c907f00000000000000000000000000000000000000000000000000000000000000009086906004016118d5565b6020604051808303816000875af1158015610f5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f7f91906118ee565b50604051632f1fbfb360e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bc7efecc90610fd2908a908a908a90879060040161199e565b600060405180830381600087803b158015610fec57600080fd5b505af1158015611000573d6000803e3d6000fd5b5050505050505050505050565b600154600160a01b900460ff161561104e576001546001600160a01b031633146110495760405162461bcd60e51b8152600401610367906117ce565b611078565b6000546001600160a01b031633146110785760405162461bcd60e51b815260040161036790611805565b7f00000000000000000000000000000000000000000000000000000000000000004210156110b85760405162461bcd60e51b815260040161036790611848565b60006110c3826113d8565b9050806001600160a01b0316630d616d206040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561110057600080fd5b505af1158015611114573d6000803e3d6000fd5b505050505050565b6001546001600160a01b031633146111465760405162461bcd60e51b8152600401610367906117ce565b806001600160a01b03811661116d5760405162461bcd60e51b815260040161036790611876565b6001546040517fd0fc5fb9c7c77f1a24739dedc1219c212e20f8711ce4551d2e97909e0f3f59ba916111ac916001600160a01b039091169085906118bb565b60405180910390a150600180546001600160a01b0319166001600160a01b0392909216919091179055565b600154600160a01b900460ff1615611218576001546001600160a01b031633146112135760405162461bcd60e51b8152600401610367906117ce565b611242565b6000546001600160a01b031633146112425760405162461bcd60e51b815260040161036790611805565b7f00000000000000000000000000000000000000000000000000000000000000004210156112825760405162461bcd60e51b815260040161036790611848565b60405163d9054b0960e01b81526001600160401b03821660048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063d9054b0990602401610428565b60007f00000000000000000000000000000000000000000000000000000000000000004210611372576040516370a0823160e01b81526001600160a01b038316906370a082319061132c9030906004016116a2565b602060405180830381865afa158015611349573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061136d9190611910565b611375565b60005b92915050565b6113d383846001600160a01b031663a9059cbb85856040516024016113a19291906118d5565b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506114cc565b505050565b60025460405163f7bc39bf60e01b815260009182916001600160a01b039091169063f7bc39bf9061140d9086906004016116a2565b602060405180830381865afa15801561142a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061144e91906118ee565b9050829150806114c65760405162461bcd60e51b815260206004820152603d60248201527f436f6e74726163742061646472657373206973206e6f7420612076616c69642060448201527f6d756c74692d636f6e7472696275746f7220534e20636f6e74726163740000006064820152608401610367565b50919050565b60006114e16001600160a01b03841683611526565b9050805160001415801561150657508080602001905181019061150491906118ee565b155b156113d35782604051635274afe760e01b815260040161036791906116a2565b60606115348383600061153b565b9392505050565b606081471015611560573060405163cd78605960e01b815260040161036791906116a2565b600080856001600160a01b0316848660405161157c9190611a26565b60006040518083038185875af1925050503d80600081146115b9576040519150601f19603f3d011682016040523d82523d6000602084013e6115be565b606091505b50915091506115ce8683836115d8565b9695505050505050565b6060826115ed576115e88261162b565b611534565b815115801561160457506001600160a01b0384163b155b156116245783604051639996b31560e01b815260040161036791906116a2565b5080611534565b80511561163b5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b60006020828403121561166957600080fd5b5035919050565b6001600160a01b038116811461165457600080fd5b60006020828403121561169757600080fd5b813561153481611670565b6001600160a01b0391909116815260200190565b6000806000606084860312156116cb57600080fd5b83356116d681611670565b92506020840135915060408401356116ed81611670565b809150509250925092565b6000806040838503121561170b57600080fd5b823561171681611670565b9150602083013561172681611670565b809150509250929050565b6000608082840312156114c657600080fd5b60008060008084860361016081121561175b57600080fd5b604081121561176957600080fd5b5084935061177a8660408701611731565b92506117898660c08701611731565b915061014085013561179a81611670565b939692955090935050565b6000602082840312156117b757600080fd5b81356001600160401b038116811461153457600080fd5b6020808252601f908201527f56657374696e673a2043616c6c6572206d757374206265207265766f6b657200604082015260600190565b60208082526023908201527f56657374696e673a2043616c6c6572206d7573742062652062656e656669636960408201526261727960e81b606082015260800190565b60208082526014908201527315995cdd1a5b99ce881b9bdd081cdd185c9d195960621b604082015260600190565b60208082526025908201527f5368617265643a205a65726f2d61646472657373206973206e6f74207065726d6040820152641a5d1d195960da1b606082015260800190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b03929092168252602082015260400190565b60006020828403121561190057600080fd5b8151801515811461153457600080fd5b60006020828403121561192257600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fd5b600081518084526020840193506020830160005b82811015611994578151805180516001600160a01b039081168952602091820151168189015290810151604088015260609096019590910190600101611953565b5093949350505050565b84358152602080860135818301528435604080840191909152858201356060808501919091528187013560808501528087013560a0850152853560c08501529185013560e084015284013561010083015260009084013561ffff8116808214611a0657600080fd5b8061012085015250506101606101408301526115ce61016083018461193f565b6000825160005b81811015611a475760208186018101518583015201611a2d565b50600092019182525091905056fea164736f6c634300081a000a", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101075760003560e01c80630962ef791461010c57806314bbe21c146101215780631916558714610134578063205a306114610147578063220cce971461015a57806335dec5d114610197578063372500ab146101aa57806338af3eed146101b257806343cac780146101c557806352208a5f146101d857806363d256ce1461020f5780637249d9d91461022357806374a8f103146102365780638a0b4e991461024957806395e3fd371461025c578063a35c47091461026f578063ac8d6c0c14610282578063be9a655514610295578063c3aca558146102ca578063d9054b09146102f1578063efbe1c1c14610304575b600080fd5b61011f61011a366004611657565b61032b565b005b61011f61012f366004611685565b61045d565b61011f610142366004611685565b610593565b61011f610155366004611685565b6106cb565b6101817f000000000000000000000000000000000000000000000000000000000000000081565b60405161018e91906116a2565b60405180910390f35b600154610181906001600160a01b031681565b61011f61073f565b600054610181906001600160a01b031681565b61011f6101d33660046116b6565b61085f565b6101ff7f000000000000000000000000000000000000000000000000000000000000000081565b604051901515815260200161018e565b6001546101ff90600160a01b900460ff1681565b61011f6102313660046116f8565b610a3a565b61011f610244366004611685565b610b7e565b600254610181906001600160a01b031681565b61011f61026a366004611743565b610cdc565b61011f61027d366004611685565b61100d565b61011f610290366004611685565b61111c565b6102bc7f000000000000000000000000000000000000000000000000000000000000000081565b60405190815260200161018e565b6101817f000000000000000000000000000000000000000000000000000000000000000081565b61011f6102ff3660046117a5565b6111d7565b6102bc7f000000000000000000000000000000000000000000000000000000000000000081565b600154600160a01b900460ff1615610375576001546001600160a01b031633146103705760405162461bcd60e51b8152600401610367906117ce565b60405180910390fd5b61039f565b6000546001600160a01b0316331461039f5760405162461bcd60e51b815260040161036790611805565b7f00000000000000000000000000000000000000000000000000000000000000004210156103df5760405162461bcd60e51b815260040161036790611848565b604051630962ef7960e01b8152600481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690630962ef79906024015b600060405180830381600087803b15801561044257600080fd5b505af1158015610456573d6000803e3d6000fd5b5050505050565b6000546001600160a01b031633146104875760405162461bcd60e51b815260040161036790611805565b806001600160a01b0381166104ae5760405162461bcd60e51b815260040161036790611876565b7f00000000000000000000000000000000000000000000000000000000000000006105295760405162461bcd60e51b815260206004820152602560248201527f56657374696e673a2062656e6566696369617279206e6f74207472616e7366656044820152647261626c6560d81b6064820152608401610367565b6000546040517f57005c5083fa0952870a7906715a2f6f9ef2d01b4a423e4b3ce59c6129b1a76391610568916001600160a01b039091169085906118bb565b60405180910390a150600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146105bd5760405162461bcd60e51b815260040161036790611805565b600154600160a01b900460ff16156106105760405162461bcd60e51b815260206004820152601660248201527515995cdd1a5b99ce881d1bdad95b881c995d9bdad95960521b6044820152606401610367565b600061061b826112d7565b90506000811161066a5760405162461bcd60e51b815260206004820152601a60248201527956657374696e673a206e6f20746f6b656e73206172652064756560301b6044820152606401610367565b816001600160a01b03167fc7798891864187665ac6dd119286e44ec13f014527aeeb2b8eb3fd413df93179826040516106a591815260200190565b60405180910390a26000546106c7906001600160a01b0384811691168361137b565b5050565b6001546001600160a01b031633146106f55760405162461bcd60e51b8152600401610367906117ce565b806001600160a01b03811661071c5760405162461bcd60e51b815260040161036790611876565b50600280546001600160a01b0319166001600160a01b0392909216919091179055565b600154600160a01b900460ff1615610780576001546001600160a01b0316331461077b5760405162461bcd60e51b8152600401610367906117ce565b6107aa565b6000546001600160a01b031633146107aa5760405162461bcd60e51b815260040161036790611805565b7f00000000000000000000000000000000000000000000000000000000000000004210156107ea5760405162461bcd60e51b815260040161036790611848565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663372500ab6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561084557600080fd5b505af1158015610859573d6000803e3d6000fd5b50505050565b600154600160a01b900460ff16156108a0576001546001600160a01b0316331461089b5760405162461bcd60e51b8152600401610367906117ce565b6108ca565b6000546001600160a01b031633146108ca5760405162461bcd60e51b815260040161036790611805565b7f000000000000000000000000000000000000000000000000000000000000000042101561090a5760405162461bcd60e51b815260040161036790611848565b806001600160a01b0381166109315760405162461bcd60e51b815260040161036790611876565b600061093c856113d8565b60405163095ea7b360e01b81529091506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b39061098d90889088906004016118d5565b6020604051808303816000875af11580156109ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109d091906118ee565b50604051632816ee7360e01b8152600481018590526001600160a01b038481166024830152821690632816ee7390604401600060405180830381600087803b158015610a1b57600080fd5b505af1158015610a2f573d6000803e3d6000fd5b505050505050505050565b600154600160a01b900460ff1615610a7b576001546001600160a01b03163314610a765760405162461bcd60e51b8152600401610367906117ce565b610aa5565b6000546001600160a01b03163314610aa55760405162461bcd60e51b815260040161036790611805565b7f0000000000000000000000000000000000000000000000000000000000000000421015610ae55760405162461bcd60e51b815260040161036790611848565b806001600160a01b038116610b0c5760405162461bcd60e51b815260040161036790611876565b6000610b17846113d8565b604051630557fe9560e11b81529091506001600160a01b03821690630aaffd2a90610b469086906004016116a2565b600060405180830381600087803b158015610b6057600080fd5b505af1158015610b74573d6000803e3d6000fd5b5050505050505050565b6001546001600160a01b03163314610ba85760405162461bcd60e51b8152600401610367906117ce565b600154600160a01b900460ff16610c6c577f0000000000000000000000000000000000000000000000000000000000000000421115610c245760405162461bcd60e51b815260206004820152601860248201527715995cdd1a5b99ce881d995cdd1a5b99c8195e1c1a5c995960421b6044820152606401610367565b6001805460ff60a01b1916600160a01b1790556040516001600160a01b038216907f39983c6d4d174a7aee564f449d4a5c3c7ac9649d72b7793c56901183996f8af690600090a25b6000610c77826112d7565b905080156106c757816001600160a01b03167fa415e62e43678c8b550c180a7ef9a2031e826b306d4052104d288f3c83ac964b82604051610cba91815260200190565b60405180910390a26001546106c7906001600160a01b0384811691168361137b565b600154600160a01b900460ff1615610d1d576001546001600160a01b03163314610d185760405162461bcd60e51b8152600401610367906117ce565b610d47565b6000546001600160a01b03163314610d475760405162461bcd60e51b815260040161036790611805565b7f0000000000000000000000000000000000000000000000000000000000000000421015610d875760405162461bcd60e51b815260040161036790611848565b806001600160a01b038116610dae5760405162461bcd60e51b815260040161036790611876565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356d399e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e329190611910565b60408051600180825281830190925291925060009190816020015b6040805160808101825260009181018281526060820183905281526020810191909152815260200190600190039081610e4d57505060408051608081018252309181019182526001600160a01b0387166060820152908152602081018490528151919250908290600090610ec357610ec3611929565b602090810291909101015260405163095ea7b360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b390610f3c907f00000000000000000000000000000000000000000000000000000000000000009086906004016118d5565b6020604051808303816000875af1158015610f5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f7f91906118ee565b50604051632f1fbfb360e21b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bc7efecc90610fd2908a908a908a90879060040161199e565b600060405180830381600087803b158015610fec57600080fd5b505af1158015611000573d6000803e3d6000fd5b5050505050505050505050565b600154600160a01b900460ff161561104e576001546001600160a01b031633146110495760405162461bcd60e51b8152600401610367906117ce565b611078565b6000546001600160a01b031633146110785760405162461bcd60e51b815260040161036790611805565b7f00000000000000000000000000000000000000000000000000000000000000004210156110b85760405162461bcd60e51b815260040161036790611848565b60006110c3826113d8565b9050806001600160a01b0316630d616d206040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561110057600080fd5b505af1158015611114573d6000803e3d6000fd5b505050505050565b6001546001600160a01b031633146111465760405162461bcd60e51b8152600401610367906117ce565b806001600160a01b03811661116d5760405162461bcd60e51b815260040161036790611876565b6001546040517fd0fc5fb9c7c77f1a24739dedc1219c212e20f8711ce4551d2e97909e0f3f59ba916111ac916001600160a01b039091169085906118bb565b60405180910390a150600180546001600160a01b0319166001600160a01b0392909216919091179055565b600154600160a01b900460ff1615611218576001546001600160a01b031633146112135760405162461bcd60e51b8152600401610367906117ce565b611242565b6000546001600160a01b031633146112425760405162461bcd60e51b815260040161036790611805565b7f00000000000000000000000000000000000000000000000000000000000000004210156112825760405162461bcd60e51b815260040161036790611848565b60405163d9054b0960e01b81526001600160401b03821660048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063d9054b0990602401610428565b60007f00000000000000000000000000000000000000000000000000000000000000004210611372576040516370a0823160e01b81526001600160a01b038316906370a082319061132c9030906004016116a2565b602060405180830381865afa158015611349573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061136d9190611910565b611375565b60005b92915050565b6113d383846001600160a01b031663a9059cbb85856040516024016113a19291906118d5565b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506114cc565b505050565b60025460405163f7bc39bf60e01b815260009182916001600160a01b039091169063f7bc39bf9061140d9086906004016116a2565b602060405180830381865afa15801561142a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061144e91906118ee565b9050829150806114c65760405162461bcd60e51b815260206004820152603d60248201527f436f6e74726163742061646472657373206973206e6f7420612076616c69642060448201527f6d756c74692d636f6e7472696275746f7220534e20636f6e74726163740000006064820152608401610367565b50919050565b60006114e16001600160a01b03841683611526565b9050805160001415801561150657508080602001905181019061150491906118ee565b155b156113d35782604051635274afe760e01b815260040161036791906116a2565b60606115348383600061153b565b9392505050565b606081471015611560573060405163cd78605960e01b815260040161036791906116a2565b600080856001600160a01b0316848660405161157c9190611a26565b60006040518083038185875af1925050503d80600081146115b9576040519150601f19603f3d011682016040523d82523d6000602084013e6115be565b606091505b50915091506115ce8683836115d8565b9695505050505050565b6060826115ed576115e88261162b565b611534565b815115801561160457506001600160a01b0384163b155b156116245783604051639996b31560e01b815260040161036791906116a2565b5080611534565b80511561163b5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b60006020828403121561166957600080fd5b5035919050565b6001600160a01b038116811461165457600080fd5b60006020828403121561169757600080fd5b813561153481611670565b6001600160a01b0391909116815260200190565b6000806000606084860312156116cb57600080fd5b83356116d681611670565b92506020840135915060408401356116ed81611670565b809150509250925092565b6000806040838503121561170b57600080fd5b823561171681611670565b9150602083013561172681611670565b809150509250929050565b6000608082840312156114c657600080fd5b60008060008084860361016081121561175b57600080fd5b604081121561176957600080fd5b5084935061177a8660408701611731565b92506117898660c08701611731565b915061014085013561179a81611670565b939692955090935050565b6000602082840312156117b757600080fd5b81356001600160401b038116811461153457600080fd5b6020808252601f908201527f56657374696e673a2043616c6c6572206d757374206265207265766f6b657200604082015260600190565b60208082526023908201527f56657374696e673a2043616c6c6572206d7573742062652062656e656669636960408201526261727960e81b606082015260800190565b60208082526014908201527315995cdd1a5b99ce881b9bdd081cdd185c9d195960621b604082015260600190565b60208082526025908201527f5368617265643a205a65726f2d61646472657373206973206e6f74207065726d6040820152641a5d1d195960da1b606082015260800190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b03929092168252602082015260400190565b60006020828403121561190057600080fd5b8151801515811461153457600080fd5b60006020828403121561192257600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fd5b600081518084526020840193506020830160005b82811015611994578151805180516001600160a01b039081168952602091820151168189015290810151604088015260609096019590910190600101611953565b5093949350505050565b84358152602080860135818301528435604080840191909152858201356060808501919091528187013560808501528087013560a0850152853560c08501529185013560e084015284013561010083015260009084013561ffff8116808214611a0657600080fd5b8061012085015250506101606101408301526115ce61016083018461193f565b6000825160005b81811015611a475760208186018101518583015201611a2d565b50600092019182525091905056fea164736f6c634300081a000a", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/src/web3client/client.py b/src/web3client/client.py new file mode 100644 index 0000000..d19b8d8 --- /dev/null +++ b/src/web3client/client.py @@ -0,0 +1,86 @@ +import logging + +import eth_utils +from web3 import Web3, HTTPProvider +from web3.contract.contract import ContractFunction +from ..web3client.abi_manager import ABIManager + + +class Web3Client: + def __init__( + self, + provider_urls: list[str], + caller_address: str | None, + private_key: str | None, + logger: logging, + abi_manager: ABIManager = None + ): + """ + Initialize the web3 client. + + :param provider_urls: List of URLs for Ethereum nodes to connect to. + :param caller_address: Address of the caller. + :param private_key: Private key of the caller. + """ + if abi_manager is None: + abi_manager = ABIManager() + + self.web3 = Web3(HTTPProvider(endpoint_uri=provider_urls[0])) + self.provider_url = provider_urls[0] + self.abi_manager = abi_manager + self.chain_id = self.web3.eth.chain_id + + self.private_key = private_key + if private_key is None: + logger.warning("private_key is None, contract writes will be disabled") + + if caller_address is not None: + if not eth_utils.is_checksum_address(caller_address): + raise ValueError("Caller address is not a checksum address") + check_address = self.web3.eth.account.from_key(private_key).address + if check_address != caller_address: + raise ValueError("Private key address does not match the caller address") + self.caller = eth_utils.to_checksum_address(caller_address) + else: + logger.warning("caller_address is None, contract writes will be disabled") + self.caller = None + + def get_nonce(self): + return self.web3.eth.get_transaction_count(self.caller) + + def contract_write(self, contract_function: ContractFunction, args: tuple): + if self.caller is None: + logging.warning("No contract caller, write is disabled") + return None + try: + # Call your function + address_to, amount = args + call_function = contract_function(address_to, amount).build_transaction( + {"chainId": self.chain_id, "from": self.caller, "nonce": self.get_nonce()} + ) + + # Sign transaction + signed_tx = self.web3.eth.account.sign_transaction( + call_function, private_key=self.private_key + ) + + raw_transaction = None + try: + raw_transaction = signed_tx.raw_transaction + except Exception as e: + raw_transaction = signed_tx.rawTransaction + logging.warning("raw_transaction is deprecated, using rawTransaction instead") + + if raw_transaction is None: + raise ValueError("raw_transaction is None") + + # Send transaction + send_tx = self.web3.eth.send_raw_transaction(raw_transaction) + + # Wait for transaction receipt + tx_hash = self.web3.eth.wait_for_transaction_receipt(send_tx).get("transactionHash") + + return tx_hash.hex() + except Exception as e: + logging.error("Error: {}".format(e)) + return None diff --git a/src/web3client/contract_factory.py b/src/web3client/contract_factory.py new file mode 100644 index 0000000..4394548 --- /dev/null +++ b/src/web3client/contract_factory.py @@ -0,0 +1,103 @@ +import subprocess +from solcx import compile_source, install_solc +import pathlib + +SOLC_VERSION = "0.8.26" +install_solc(SOLC_VERSION) + +base_path = pathlib.Path(__file__).parent.parent.parent.joinpath("session-token-contracts") + +subprocess.run(["pnpm", "install"], cwd=base_path) + +compiled_sol = compile_source( + """ +import "ServiceNodeRewards.sol"; +import "RewardRatePool.sol"; +import "SESH.sol"; +import "ServiceNodeContribution.sol"; +import "ServiceNodeContributionFactory.sol"; +import "utils/TokenVestingStaking.sol"; +import "utils/TokenVestingNoStaking.sol"; +import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; +import "@openzeppelin/contracts/interfaces/IERC1967.sol"; +""", + base_path=base_path, + include_path=base_path.joinpath("contracts"), + solc_version=SOLC_VERSION, + import_remappings={ + "@openzeppelin/contracts": "node_modules/@openzeppelin/contracts", + "@openzeppelin/contracts-upgradeable": "node_modules/@openzeppelin/contracts-upgradeable", + }, +) + +abis = {} + + +class ContractFactory: + def __init__(self, w3): + """ + Creates factories for the contracts in the session-token-contracts repo. + + Factories can be used to instantiate contracts with the same ABI and bytecode by calling them with the address + of the contract. + + Example: + factory = ContractFactory(w3) + sesh_contract = factory.SESH(address) + """ + self.w3 = w3 + + def get(self, name: str): + if name not in abis: + key = f"{name}.sol:{name}" + + if name == "Ownable2StepUpgradeable": + key = "node_modules/@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:Ownable2StepUpgradeable" + elif name == "PausableUpgradeable": + key = "node_modules/@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:PausableUpgradeable" + elif name == "IERC1967": + key = "node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol:IERC1967" + elif name == "TokenVestingStaking": + key = "utils/TokenVestingStaking.sol:TokenVestingStaking" + elif name == "TokenVestingNoStaking": + key = "utils/TokenVestingNoStaking.sol:TokenVestingNoStaking" + + abis[name] = self.w3.eth.contract(abi=compiled_sol[key]["abi"]) + return abis[name] + + @property + def SESH(self): + return self.get("SESH") + + @property + def RewardRatePool(self): + return self.get("RewardRatePool") + + @property + def ServiceNodeRewards(self): + return self.get("ServiceNodeRewards") + + @property + def ServiceNodeContributionFactory(self): + return self.get("ServiceNodeContributionFactory") + + @property + def ServiceNodeContribution(self): + return self.get("ServiceNodeContribution") + + @property + def TokenVestingStaking(self): + return self.get("TokenVestingStaking") + + @property + def TokenVestingNoStaking(self): + return self.get("TokenVestingNoStaking") + + @property + def Ownable2StepUpgradeable(self): + return self.get("Ownable2StepUpgradeable") + + @property + def PausableUpgradeable(self): + return self.get("PausableUpgradeable") diff --git a/src/web3client/contracts/__init__.py b/src/web3client/contracts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/web3client/contracts/contract.py b/src/web3client/contracts/contract.py new file mode 100644 index 0000000..f738218 --- /dev/null +++ b/src/web3client/contracts/contract.py @@ -0,0 +1,26 @@ +from web3 import Web3 +from ..client import Web3Client + + +class ContractInterface: + def __init__( + self, + web3_client: Web3Client, + contract_address: str, + contract_abi_name: str, + ): + """ + Initialize the connection to a contract. + + :param web3_client: The web3 client to use for interacting with the contract. + :param contract_address: Address of the deployed contract. + :param contract_abi_name: Path to the ABI file for the contract. + """ + # Initialize address nonce + + self.web3_client = web3_client + self.contract_address = Web3.to_checksum_address(contract_address) + abi = self.web3_client.abi_manager.get_abi(contract_abi_name) + # TODO: This call takes a while (~5ms) so we should see if we can cache it and reuse it for other instances with the same abi but different addresses + self.contract = web3_client.web3.eth.contract(address=self.contract_address, abi=abi) + self.address_map = {} diff --git a/contracts/reward_rate_pool.py b/src/web3client/contracts/reward_rate_pool.py similarity index 53% rename from contracts/reward_rate_pool.py rename to src/web3client/contracts/reward_rate_pool.py index 0899702..ed66c02 100644 --- a/contracts/reward_rate_pool.py +++ b/src/web3client/contracts/reward_rate_pool.py @@ -1,17 +1,12 @@ -from web3 import Web3 -from abi_manager import ABIManager +from ..client import Web3Client +from ..contracts.contract import ContractInterface -class RewardRatePoolInterface: - def __init__(self, provider_url, contract_address): - """ - Initialize the connection to the Ethereum provider and set up the contract. - :param provider_url: URL of the Ethereum node to connect to. - :param contract_address: Address of the RewardRatePool smart contract. - """ - self.web3 = Web3(Web3.HTTPProvider(provider_url)) - manager = ABIManager() - abi = manager.load_abi('RewardRatePool') - self.contract = self.web3.eth.contract(address=Web3.to_checksum_address(contract_address), abi=abi) + +class RewardRatePoolInterface(ContractInterface): + abi_name = "RewardRatePool" + + def __init__(self, web3_client: Web3Client, contract_address: str): + super().__init__(web3_client, contract_address, RewardRatePoolInterface.abi_name) def calculate_total_deposited(self): """ @@ -40,14 +35,3 @@ def reward_rate(self, timestamp): :param timestamp: The timestamp for which to calculate the reward rate. """ return self.contract.functions.rewardRate(timestamp).call() - -# Example usage: -# provider_url = 'http://127.0.0.1:8545' -# contract_address = '0x...' - -# reward_rate_pool = RewardRatePoolInterface(provider_url, contract_address) -# total_deposited = reward_rate_pool.calculate_total_deposited() -# print("Total Deposited:", total_deposited) -# reward_rate = reward_rate_pool.reward_rate(Web3.toInt(text="latest")) -# print("Reward Rate at Latest:", reward_rate) - diff --git a/src/web3client/contracts/service_node_contribution.py b/src/web3client/contracts/service_node_contribution.py new file mode 100644 index 0000000..7fc2da5 --- /dev/null +++ b/src/web3client/contracts/service_node_contribution.py @@ -0,0 +1,79 @@ +import time +from dataclasses import dataclass + +from web3 import Web3 +from ..client import Web3Client +from ..contracts.contract import ContractInterface + + +class ServiceNodeContributionInterface(ContractInterface): + abi_name = "ServiceNodeContribution" + + def __init__(self, web3_client: Web3Client, contract_address: str): + super().__init__(web3_client, contract_address, ServiceNodeContributionInterface.abi_name) + + def get_contributor_contribution(self, contributor_address): + """ + Get the contribution amount of a specific contributor. + :param contributor_address: Address of the contributor. + :return: Contribution amount of the specified contributor. + """ + return self.contract.functions.contributions( + Web3.to_checksum_address(contributor_address) + ).call() + + def status(self): + """ + Check if the service node is finalized. + :return: True if the service node is finalized, otherwise False. + """ + return self.contract.functions.status().call() + + def is_cancelled(self): + """ + Check if the service node has been cancelled. + :return: True if the service node has been cancelled, otherwise False. + """ + return self.contract.functions.cancelled().call() + + def total_contribution(self): + """ + Get the total amount of contributions received. + :return: Total contributions amount. + """ + return self.contract.functions.totalContribution().call() + + def contributor_count(self): + """ + Get the number of contributors. + :return: Number of contributors. + """ + return len(self.contract.functions.contributorAddresses().call()) + + def minimum_contribution(self): + """ + Get the minimum contribution required. + :return: Minimum contribution amount. + """ + return self.contract.functions.minimumContribution().call() + + def get_bls_pubkey(self): + """ + Get the BLS public key. + :return: BLS public key, in hex. + """ + pks = self.contract.functions.blsPubkey().call() + return "0x{:0128x}".format((pks[0] << 256) + pks[1]) + + @staticmethod + def add_details_fetch_to_batch_added_batches(): + return 7 + + def add_details_fetch_to_batch(self, batch): + batch.add(self.contract.functions.serviceNodeParams()) + batch.add(self.contract.functions.operator()) + batch.add(self.contract.functions.blsPubkey()) + batch.add(self.contract.functions.getContributions()) + batch.add(self.contract.functions.status()) + batch.add(self.contract.functions.manualFinalize()) + batch.add(self.contract.functions.getReserved()) diff --git a/src/web3client/contracts/service_node_contribution_factory.py b/src/web3client/contracts/service_node_contribution_factory.py new file mode 100644 index 0000000..497b399 --- /dev/null +++ b/src/web3client/contracts/service_node_contribution_factory.py @@ -0,0 +1,22 @@ +from ..client import Web3Client +from ..contracts.contract import ContractInterface +from ..event_scanner import EventScanner + + +class ServiceNodeContributionFactory(ContractInterface): + abi_name = "ServiceNodeContributionFactory" + + def __init__(self, web3_client: Web3Client, contract_address: str, scanner_safety_blocks: int, scan_start_chunk_size: int): + super().__init__(web3_client, contract_address, ServiceNodeContributionFactory.abi_name) + self.event_scanner = EventScanner( + provider_url=web3_client.provider_url, + events=[ + self.contract.events.NewServiceNodeContributionContract, + ], + filters={"address": self.contract_address}, + # How many maximum blocks at the time we request from JSON-RPC + # and we are unlikely to exceed the response size limit of the JSON-RPC server + max_chunk_scan_size=10_000_000, + safety_blocks=scanner_safety_blocks, + optimal_chunk_size=scan_start_chunk_size + ) diff --git a/contracts/service_node_rewards.py b/src/web3client/contracts/service_node_rewards.py similarity index 50% rename from contracts/service_node_rewards.py rename to src/web3client/contracts/service_node_rewards.py index 0a26f3a..a747cfa 100644 --- a/contracts/service_node_rewards.py +++ b/src/web3client/contracts/service_node_rewards.py @@ -1,32 +1,43 @@ -from web3 import Web3 -from abi_manager import ABIManager +from ..client import Web3Client +from ..contracts.contract import ContractInterface +from ..event_scanner import EventScanner + class ServiceNodeRewardsRecipient: def __init__(self): self.rewards = 0 self.claimed = 0 + class ServiceNodeRewardsMapEntry: def __init__(self): - self.value = ServiceNodeRewardsRecipient() - self.height = 0; - -class ServiceNodeRewardsInterface: - def __init__(self, provider_url: str, contract_address: str): - """ - Initialize the connection to the ServiceNodeContributionFactory contract. - - :param provider_url: URL of the Ethereum node to connect to. - :param contract_address: Address of the deployed ServiceNodeContributionFactory contract. - """ - self.web3 = Web3(Web3.HTTPProvider(provider_url)) - self.contract_address = Web3.to_checksum_address(contract_address) - manager = ABIManager() - abi = manager.load_abi('ServiceNodeRewards') - self.contract = self.web3.eth.contract(address=self.contract_address, abi=abi) - self.address_map = {} - - def allServiceNodeIDs(self): + self.value = ServiceNodeRewardsRecipient() + self.height = 0 + + +class ServiceNodeRewardsInterface(ContractInterface): + abi_name = "ServiceNodeRewards" + + def __init__(self, web3_client: Web3Client, contract_address: str, scanner_safety_blocks: int, scan_start_chunk_size: int): + super().__init__(web3_client, contract_address, ServiceNodeRewardsInterface.abi_name) + self.event_scanner = EventScanner( + provider_url=web3_client.provider_url, + events=[ + self.contract.events.NewServiceNodeV2, + self.contract.events.ServiceNodeExitRequest, + self.contract.events.ServiceNodeExit, + self.contract.events.ServiceNodeLiquidated, + self.contract.events.RewardsClaimed, + ], + filters={"address": self.contract_address}, + # How many maximum blocks at the time we request from JSON-RPC + # and we are unlikely to exceed the response size limit of the JSON-RPC server + max_chunk_scan_size=10_000_000, + safety_blocks=scanner_safety_blocks, + optimal_chunk_size=scan_start_chunk_size + ) + + def get_all_service_node_contract_ids(self): """ Calls the allServiceNodeIds function to get the `id` and `bls_key` lists @@ -55,7 +66,7 @@ def recipients(self, eth_address: bytes) -> ServiceNodeRewardsRecipient: # Retrieve the recipient entry and check if enough time has elapsed to # update the entry, otherwise return the cached entry - entry = self.address_map[eth_address] + entry = self.address_map[eth_address] result = entry.value if entry.height >= height: return result @@ -63,24 +74,25 @@ def recipients(self, eth_address: bytes) -> ServiceNodeRewardsRecipient: # NOTE: Assuming a block time of 0.25s, we want a 30s block buffer. # TODO: This value is copied from oxen-core of the same name # `SAFE_BLOCKS` - SAFE_BLOCKS = 30 / 0.25; + SAFE_BLOCKS = 30 / 0.25 blocks_since_last_update = height - entry.height if blocks_since_last_update < SAFE_BLOCKS: return result # Enough blocks has elapsed, query the rewards from the contract - call_result = self.contract.functions.recipients(eth_address).call(block_identifier=height) + call_result = self.contract.functions.recipients(eth_address).call(block_identifier=height) result.rewards = call_result[0] result.claimed = call_result[1] - assert result.claimed <= result.rewards, "Contract returned that wallet '{}' claimed {} more than the rewards {} allocated to it!".format(eth_address.decode('utf-8'), - result.rewards, - result.claimed) - + assert ( + result.claimed <= result.rewards + ), "Contract returned that wallet '{}' claimed {} more than the rewards {} allocated to it!".format( + eth_address.decode("utf-8"), result.rewards, result.claimed + ) # Assign the updated entry back into the cache - entry.height = height - entry.value = result + entry.height = height + entry.value = result self.address_map[eth_address] = entry return result diff --git a/src/web3client/contracts/token.py b/src/web3client/contracts/token.py new file mode 100644 index 0000000..1313cf3 --- /dev/null +++ b/src/web3client/contracts/token.py @@ -0,0 +1,31 @@ +from ..client import Web3Client +from ..contracts.contract import ContractInterface + + +class TokenInterface(ContractInterface): + abi_name = "Token" + + decimals = 9 + + def __init__(self, web3_client: Web3Client, contract_address: str): + super().__init__(web3_client, contract_address, TokenInterface.abi_name) + + def transfer(self, amount: int, address_to: str): + """ + Calls the transfer function on the ERC20 token contract + + :return: receipt + """ + return self.web3_client.contract_write( + self.contract.functions.transfer, (address_to, amount) + ) + + def balance_of(self, address: str): + """ + Calls the balanceOf function on the ERC20 token contract + + :return: balance + """ + return self.contract.functions.balanceOf(address).call() + + diff --git a/src/web3client/contracts/token_tests.py b/src/web3client/contracts/token_tests.py new file mode 100644 index 0000000..c1e22a3 --- /dev/null +++ b/src/web3client/contracts/token_tests.py @@ -0,0 +1,65 @@ +from ..contracts.token import TokenInterface + +def test_token_decimals(): + assert TokenInterface.decimals == 9 + +def test_float_to_atomic(): + assert TokenInterface.to_atomic(0) == 0 + assert TokenInterface.to_atomic(0.0) == 0 + + assert TokenInterface.to_atomic(1) == 1_000000000 + assert TokenInterface.to_atomic(1.0) == 1_000000000 + + assert TokenInterface.to_atomic(10) == 10_000000000 + assert TokenInterface.to_atomic(10.0) == 10_000000000 + + assert TokenInterface.to_atomic(999) == 999_000000000 + assert TokenInterface.to_atomic(999.0) == 999_000000000 + + assert TokenInterface.to_atomic(0.1) == 100000000 + assert TokenInterface.to_atomic(0.000000001) == 1 + assert TokenInterface.to_atomic(0.9) == 900000000 + assert TokenInterface.to_atomic(0.99999) == 999990000 + assert TokenInterface.to_atomic(1.99999) == 1_999990000 + assert TokenInterface.to_atomic(0.999999999) == 999999999 + assert TokenInterface.to_atomic(1.999999999) == 1_999999999 + assert TokenInterface.to_atomic(0.333333333) == 333333333 + assert TokenInterface.to_atomic(1.333333333) == 1_333333333 + assert TokenInterface.to_atomic(1.000000001) == 1_000000001 + + assert TokenInterface.to_atomic(99.99) == 99_990000000 + assert TokenInterface.to_atomic(99.98) == 99_980000000 + assert TokenInterface.to_atomic(100) == 100_000000000 + assert TokenInterface.to_atomic(101) == 101_000000000 + assert TokenInterface.to_atomic(1000) == 1000_000000000 + assert TokenInterface.to_atomic(10.22) == 10_220000000 + assert TokenInterface.to_atomic(45.42) == 45_420000000 + + +def test_float_from_atomic(): + assert TokenInterface.from_atomic(0) == 0 + + assert TokenInterface.from_atomic(1_000000000) == 1 + assert TokenInterface.from_atomic(10_000000000) == 10 + assert TokenInterface.from_atomic(999_000000000) == 999 + + assert TokenInterface.from_atomic(100000000) == 0.1 + assert TokenInterface.from_atomic(1) == 0.000000001 + assert TokenInterface.from_atomic(900000000) == 0.9 + assert TokenInterface.from_atomic(999990000) == 0.99999 + assert TokenInterface.from_atomic(1_999990000) == 1.99999 + assert TokenInterface.from_atomic(999999999) == 0.999999999 + assert TokenInterface.from_atomic(1_999999999) == 1.999999999 + assert TokenInterface.from_atomic(333333333) == 0.333333333 + assert TokenInterface.from_atomic(1_333333333) == 1.333333333 + assert TokenInterface.from_atomic(1_000000001) == 1.000000001 + + assert TokenInterface.from_atomic(99_990000000) == 99.99 + assert TokenInterface.from_atomic(99_980000000) == 99.98 + assert TokenInterface.from_atomic(100_000000000) == 100 + assert TokenInterface.from_atomic(101_000000000) == 101 + assert TokenInterface.from_atomic(1000_000000000) == 1000 + assert TokenInterface.from_atomic(10_220000000) == 10.22 + assert TokenInterface.from_atomic(45_420000000) == 45.42 + + diff --git a/src/web3client/contracts_ws/__init__.py b/src/web3client/contracts_ws/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/web3client/contracts_ws/contract_utils.py b/src/web3client/contracts_ws/contract_utils.py new file mode 100644 index 0000000..1dc738d --- /dev/null +++ b/src/web3client/contracts_ws/contract_utils.py @@ -0,0 +1,56 @@ +from collections.abc import Callable + +from web3._utils.events import get_event_data +from web3.contract.async_contract import AsyncContractEvent +from web3.types import EventData +from web3.utils.subscriptions import EthSubscriptionContext + +from src.staking.write import DBWriterStaking +from src.web3client.event_queue_manager import EventQueueManager +from src.web3client.event_scanner import ProcessedEvent + + +def queue_past_events_for_scanning( + events: list[AsyncContractEvent], + event_queue: EventQueueManager, + handler_past: Callable = None, + event_abis: dict[str, any] = None, + start_block: int = None, +): + assert isinstance(event_queue, EventQueueManager), "event_queue must be an instance of EventQueueManager" + for event in events: + event_queue.add_event( + event=event, + handler=handler_past, + start_block=start_block, + ) + + if event.name not in event_abis: + event_abis[event.name] = event._get_event_abi() + +def parse_event(event_abis, event: EthSubscriptionContext): + result = event.result + name = event.subscription.label + + if "_" in name: + name = name.split("_")[0] + + abi = event_abis[name] + data = get_event_data(event.async_w3.codec, abi, result) + return data + + +def create_processed_event(event: EventData, main_arg: str | None = None): + return ProcessedEvent( + name=event.event, + args=event.args, + log_index=event.logIndex, + block=event.blockNumber, + main_arg=main_arg, + tx=event.transactionHash.hex() + ) + + +def write_event_to_db(db_writer: DBWriterStaking, event: EventData, main_arg: str | None = None): + event = create_processed_event(event, main_arg=main_arg) + db_writer.write_arbitrum_event_to_db(event) diff --git a/src/web3client/contracts_ws/contract_ws.py b/src/web3client/contracts_ws/contract_ws.py new file mode 100644 index 0000000..22eaac2 --- /dev/null +++ b/src/web3client/contracts_ws/contract_ws.py @@ -0,0 +1,33 @@ +import logging + +from web3 import AsyncWeb3 +from web3.types import EventData +from web3.utils.subscriptions import EthSubscriptionContext + +from src.staking.write import DBWriterStaking +from src.web3client.contract_factory import ContractFactory +from src.web3client.contracts_ws.contract_utils import parse_event, write_event_to_db +from src.web3client.event_queue_manager import EventQueueManager + + +class ContractWS: + def __init__(self, name: str, w3: AsyncWeb3, db_writer: DBWriterStaking, log: logging, + event_queue: EventQueueManager | None = None): + self.w3 = w3 + self.log = log + self.factory = ContractFactory(w3).get(name) + self.db_writer = db_writer + self.event_abis = {} + self.event_queue = event_queue + + def _parse_event(self, event: EthSubscriptionContext): + return parse_event(self.event_abis, event) + + async def _handle_event(self, event: EventData, main_arg: str | None = None): + self.log.debug(f"New {event.event}: {event.args}") + if main_arg is None: + main_arg = event.address + write_event_to_db(self.db_writer, event, main_arg=main_arg) + + async def _handle_event_sub(self, event: EthSubscriptionContext): + await self._handle_event(self._parse_event(event)) diff --git a/src/web3client/contracts_ws/ierc_1967.py b/src/web3client/contracts_ws/ierc_1967.py new file mode 100644 index 0000000..5a65a83 --- /dev/null +++ b/src/web3client/contracts_ws/ierc_1967.py @@ -0,0 +1,39 @@ +import logging + +from eth_typing import ChecksumAddress +from web3 import AsyncWeb3 +from web3.contract.async_contract import AsyncContractEvent + +from src.staking.write import DBWriterStaking +from src.web3client.contracts_ws.contract_ws import ContractWS +from src.web3client.contracts_ws.contract_utils import queue_past_events_for_scanning +from src.web3client.event_queue_manager import EventQueueManager + + +class IERC1967(ContractWS): + name = "IERC1967" + + def __init__(self, w3: AsyncWeb3, db_writer: DBWriterStaking, log: logging, + event_queue: EventQueueManager | None = None): + super().__init__(self.name, w3, db_writer, log, event_queue) + + def get_events(self, address: ChecksumAddress | list[ChecksumAddress]) -> list[AsyncContractEvent]: + events = self.factory(address[0] if isinstance(address, list) else address).events + return [ + events.Upgraded, + events.AdminChanged, + events.BeaconUpgraded, + ] + + def queue_past_events_for_scanning(self, address: ChecksumAddress | list[ChecksumAddress]): + event_list = self.get_events(address) + + for event in event_list: + event.address = address + + return queue_past_events_for_scanning( + events=event_list, + event_queue=self.event_queue, + event_abis=self.event_abis, + handler_past=self._handle_event, + ) diff --git a/src/web3client/contracts_ws/ownable_2_step_upgradeable.py b/src/web3client/contracts_ws/ownable_2_step_upgradeable.py new file mode 100644 index 0000000..999c2cd --- /dev/null +++ b/src/web3client/contracts_ws/ownable_2_step_upgradeable.py @@ -0,0 +1,39 @@ +import logging + +from eth_typing import ChecksumAddress +from web3 import AsyncWeb3 +from web3.contract.async_contract import AsyncContractEvent + +from src.staking.write import DBWriterStaking +from src.web3client.contracts_ws.contract_ws import ContractWS +from src.web3client.contracts_ws.contract_utils import queue_past_events_for_scanning +from src.web3client.event_queue_manager import EventQueueManager + + +class Ownable2StepUpgradeable(ContractWS): + name = "Ownable2StepUpgradeable" + + def __init__(self, w3: AsyncWeb3, db_writer: DBWriterStaking, log: logging, + event_queue: EventQueueManager | None = None): + super().__init__(self.name, w3, db_writer, log, event_queue) + + def get_events(self, address: ChecksumAddress | list[ChecksumAddress]) -> list[AsyncContractEvent]: + events = self.factory(address[0] if isinstance(address, list) else address).events + return [ + events.OwnershipTransferStarted, + events.OwnershipTransferred, + events.Initialized, + ] + + def queue_past_events_for_scanning(self, address: ChecksumAddress | list[ChecksumAddress]): + event_list = self.get_events(address) + + for event in event_list: + event.address = address + + return queue_past_events_for_scanning( + events=event_list, + event_queue=self.event_queue, + event_abis=self.event_abis, + handler_past=self._handle_event, + ) diff --git a/src/web3client/contracts_ws/pausable_upgradeable.py b/src/web3client/contracts_ws/pausable_upgradeable.py new file mode 100644 index 0000000..1e039e2 --- /dev/null +++ b/src/web3client/contracts_ws/pausable_upgradeable.py @@ -0,0 +1,38 @@ +import logging + +from eth_typing import ChecksumAddress +from web3 import AsyncWeb3 +from web3.contract.async_contract import AsyncContractEvent + +from src.staking.write import DBWriterStaking +from src.web3client.contracts_ws.contract_ws import ContractWS +from src.web3client.contracts_ws.contract_utils import queue_past_events_for_scanning +from src.web3client.event_queue_manager import EventQueueManager + + +class PausableUpgradeable(ContractWS): + name = "PausableUpgradeable" + + def __init__(self, w3: AsyncWeb3, db_writer: DBWriterStaking, log: logging, + event_queue: EventQueueManager | None = None): + super().__init__(self.name, w3, db_writer, log, event_queue) + + def get_events(self, address: ChecksumAddress | list[ChecksumAddress]) -> list[AsyncContractEvent]: + events = self.factory(address[0] if isinstance(address, list) else address).events + return [ + events.Paused, + events.Unpaused, + ] + + def queue_past_events_for_scanning(self, address: ChecksumAddress | list[ChecksumAddress]): + event_list = self.get_events(address) + + for event in event_list: + event.address = address + + return queue_past_events_for_scanning( + events=event_list, + event_queue=self.event_queue, + event_abis=self.event_abis, + handler_past=self._handle_event, + ) diff --git a/src/web3client/contracts_ws/reward_rate_pool.py b/src/web3client/contracts_ws/reward_rate_pool.py new file mode 100644 index 0000000..026eb74 --- /dev/null +++ b/src/web3client/contracts_ws/reward_rate_pool.py @@ -0,0 +1,48 @@ +import logging + +from eth_typing import ChecksumAddress +from web3 import AsyncWeb3 +from web3.contract.async_contract import AsyncContractEvent +from web3.types import EventData +from web3.utils.subscriptions import EthSubscriptionContext + +from src.staking.write import DBWriterStaking +from src.web3client.contracts_ws.contract_ws import ContractWS +from src.web3client.contracts_ws.contract_utils import queue_past_events_for_scanning +from src.web3client.event_queue_manager import EventQueueManager + + +class RewardRatePool(ContractWS): + name = "RewardRatePool" + + def __init__(self, w3: AsyncWeb3, db_writer: DBWriterStaking, log: logging, + event_queue: EventQueueManager | None = None): + super().__init__(self.name, w3, db_writer, log, event_queue) + + @staticmethod + def get_main_arg(event: EventData): + match event.event: + case "FundsReleased": + return event.args.amount + case _: + return None + + async def handle_event(self, event: EventData): + main_arg = RewardRatePool.get_main_arg(event) + assert main_arg is not None + return await self._handle_event(event, main_arg=main_arg) + + async def handle_event_sub(self, event: EthSubscriptionContext): + return await self.handle_event(self._parse_event(event)) + + def get_events(self, address: ChecksumAddress) -> list[AsyncContractEvent]: + events = self.factory(address).events + return [events.FundsReleased] + + def queue_past_events_for_scanning(self, address: ChecksumAddress): + return queue_past_events_for_scanning( + events=self.get_events(address), + event_queue=self.event_queue, + event_abis=self.event_abis, + handler_past=self.handle_event, + ) diff --git a/src/web3client/contracts_ws/service_node_contribution.py b/src/web3client/contracts_ws/service_node_contribution.py new file mode 100644 index 0000000..29344ed --- /dev/null +++ b/src/web3client/contracts_ws/service_node_contribution.py @@ -0,0 +1,235 @@ +import logging +from dataclasses import dataclass + +from eth_typing import ChecksumAddress +from web3 import AsyncWeb3 +from web3.contract.async_contract import AsyncContractEvent +from web3.types import EventData +from web3.utils.subscriptions import EthSubscriptionContext + +from src.staking.read import DBReaderStaking +from src.staking.write import DBWriterStaking +from src.util.parse import parse_ed25519_pubkey, parse_bls_pubkey +from src.web3client.contracts_ws.contract_ws import ContractWS +from src.web3client.contracts_ws.contract_utils import queue_past_events_for_scanning, create_processed_event +from src.web3client.event_queue_manager import EventQueueManager + + +@dataclass +class ContributionContractContributor: + address: str = None + amount: int = 0 + beneficiary: str = None + reserved: int = 0 + + +class ContributionContract: + def __init__(self, address, log, db_writer: DBWriterStaking, db_reader: DBReaderStaking = None): + self.log = log + self.db_writer = db_writer + self.address = address + + contributors = db_reader.get_contribution_contract_contributors(address) if db_reader else [] + + self._contributors = {} + if len(contributors) > 0: + for contributor in contributors: + self._contributors[contributor.address] = contributor + self.log.debug(f"Initialized {self.address} with Contributors: {self._contributors}") + + def update_status(self, status: int): + self.log.debug(f"Updating status of {self.address} to {status}") + self.db_writer.write_update_contribution_contract_status(self.address, status) + + def update_manual_finalize(self, new_value: bool): + self.log.debug(f"Updating manual finalize of {self.address} to {new_value}") + self.db_writer.write_update_contribution_contract_manual_finalize(self.address, new_value) + + def update_fee(self, new_fee: int): + self.log.debug(f"Updating fee of {self.address} to {new_fee}") + self.db_writer.write_update_contribution_contract_fee(self.address, new_fee) + + def update_pubkeys(self, new_bls_pubkey: dict, new_ed25519_pubkey: int): + self.log.debug(f"Updating pubkeys of {self.address} to {new_bls_pubkey}, {new_ed25519_pubkey}") + if new_bls_pubkey is None: + self.log.warning(f"No new BLS pubkey found for {self.address}") + return + if new_ed25519_pubkey is None: + self.log.warning(f"No new Ed25519 pubkey found for {self.address}") + return + + pubkey_bls = parse_bls_pubkey(new_bls_pubkey) + service_node_pubkey = parse_ed25519_pubkey(new_ed25519_pubkey) + self.db_writer.write_update_contribution_contract_pubkeys(self.address, pubkey_bls, service_node_pubkey) + + def _upsert_contributor(self, address: str, beneficiary: str | None): + self._contributors.setdefault(address, ContributionContractContributor(address=address, beneficiary=beneficiary)) + + def update_contributor_new_contribution(self, address: str, beneficiary: str, amount: int): + self.log.debug(f"Updating contributor add {address} with beneficiary {beneficiary} and amount {amount}") + self._upsert_contributor(address, beneficiary) + self._contributors[address].address = address + self._contributors[address].beneficiary = beneficiary + self._contributors[address].amount += amount + self.db_writer.write_update_contribution_contract_contributor(self.address, self._contributors[address]) + + def update_contributor_withdraw_contribution(self, address: str, amount: int): + self.log.debug(f"Updating contributor remove {address} with amount {amount}") + if address in self._contributors: + self._contributors[address].amount -= amount + if self._contributors[address].amount == 0: + del self._contributors[address] + self.db_writer.write_delete_contribution_contract_contributor(self.address, address) + else: + self.db_writer.write_update_contribution_contract_contributor(self.address, self._contributors[address]) + else: + self.log.warning(f"No contributor found for address {address} to withdraw {amount}") + + def update_contributor_beneficiary(self, address: str, beneficiary: str): + self.log.debug(f"Updating contributor beneficiary {address} to {beneficiary}") + self._upsert_contributor(address, beneficiary) + self._contributors[address].beneficiary = beneficiary + self.db_writer.write_update_contribution_contract_contributor(self.address, self._contributors[address]) + + def update_reserved_contributors(self, reserved_contributors: list[dict[str, str]]): + self.log.debug(f"Updating reserved contributors {reserved_contributors}") + for reserved_contributor in reserved_contributors: + address = reserved_contributor.get("addr") + reserved_amount = reserved_contributor.get("amount") + self._upsert_contributor(address, None) + self._contributors[address].reserved = reserved_amount + self.db_writer.write_update_contribution_contract_contributor(self.address, self._contributors[address]) + + def update_reset(self): + self.log.debug(f"Updating reset for {self.address}") + self._contributors = {} + self.db_writer.write_delete_all_contribution_contract_contributors(self.address) + self.update_status(0) + + def process_event(self, raw_event: EventData): + event = create_processed_event(raw_event, main_arg=self.address) + self.log.debug(f"Processing sn event: {event}") + match event.name: + case "OpenForPublicContribution": + return self.update_status(1) + + case "Filled": + return self.update_status(2) + + case "Finalized": + return self.update_status(3) + + case "NewContribution": + return self.update_contributor_new_contribution(event.args.get("contributor"), event.args.get("beneficiary"), event.args.get("amount")) + + case "WithdrawContribution": + return self.update_contributor_withdraw_contribution(event.args.get("contributor"), + event.args.get("amount")) + + case "UpdateStakerBeneficiary": + return self.update_contributor_beneficiary(event.args.get("staker"), event.args.get("newBeneficiary")) + + case "UpdateManualFinalize": + return self.update_manual_finalize(event.args.get("newValue")) + + case "UpdateFee": + return self.update_fee(event.args.get("newFee")) + + case "UpdatePubkeys": + return self.update_pubkeys(event.args.get("newBLSPubkey"), event.args.get("newEd25519Pubkey")) + + case "UpdateReservedContributors": + return self.update_reserved_contributors(event.args.get("newReservedContributors")) + + case "Reset": + return self.update_reset() + + case _: + return self.log.warning(f"Unknown event to process: {event}") + + +tracked_contracts: dict[str, ContributionContract] = {} + + +class ServiceNodeContribution(ContractWS): + name = "ServiceNodeContribution" + event_names = [ + "Finalized", + "NewContribution", + "OpenForPublicContribution", + "Filled", + "WithdrawContribution", + "UpdateStakerBeneficiary", + "UpdateManualFinalize", + "UpdateFee", + "UpdatePubkeys", + "UpdateReservedContributors", + "Reset", + ] + + def __init__(self, w3: AsyncWeb3, db_writer: DBWriterStaking, db_reader: DBReaderStaking, log: logging, + event_queue: EventQueueManager | None = None): + super().__init__(self.name, w3, db_writer, log, event_queue) + self.db_reader = db_reader + + async def handle_event(self, event: EventData): + await self._handle_event(event, main_arg=event.address) + tracked_contracts.setdefault( + event.address, + ContributionContract(address=event.address, log=self.log, db_writer=self.db_writer, + db_reader=self.db_reader) + ) + tracked_contracts[event.address].process_event(event) + + async def handle_event_sub(self, event: EthSubscriptionContext): + return await self.handle_event(self._parse_event(event)) + + + def get_events(self, address: ChecksumAddress) -> list[AsyncContractEvent]: + events = self.factory(address[0] if isinstance(address, list) else address).events + return [events[name] for name in self.event_names] + + def queue_past_events_for_scanning(self, address: ChecksumAddress | list[ChecksumAddress], start_block: int = 0): + event_list = self.get_events(address) + + for event in event_list: + event.address = address + + return queue_past_events_for_scanning( + events=event_list, + event_queue=self.event_queue, + event_abis=self.event_abis, + handler_past=self.handle_event, + start_block=start_block, + ) + + batch_items = 7 + # TODO: use this to get details for old contracts + async def batch_get_details(self, addresses: list[ChecksumAddress]): + chunk_size = 100 + chunks = [addresses[i: i + chunk_size] for i in range(0, len(addresses), chunk_size)] + responses = [] + for chunk in chunks: + assert len(chunk) <= chunk_size, "Expected chunk size <= {} got {}".format(chunk_size, len(chunk)) + async with self.w3.batch_requests() as batch: + for address in chunk: + contract = self.factory(address) + batch.add(contract.functions.serviceNodeParams()) + batch.add(contract.functions.operator()) + batch.add(contract.functions.blsPubkey()) + batch.add(contract.functions.getContributions()) + batch.add(contract.functions.status()) + batch.add(contract.functions.manualFinalize()) + batch.add(contract.functions.getReserved()) + + res = await batch.async_execute() + if len(res) == 1: + self.log.warning(res[0]) + assert len(res) == len( + chunk) * ServiceNodeContribution.batch_items, f"Expected {len(chunk) * ServiceNodeContribution.batch_items} responses, got {len(res)}" + responses.extend(res) + + assert len(responses) == len( + addresses) * ServiceNodeContribution.batch_items, f"Expected {len(addresses) * ServiceNodeContribution.batch_items} responses, got {len(responses)}" + + return responses diff --git a/src/web3client/contracts_ws/service_node_contribution_factory.py b/src/web3client/contracts_ws/service_node_contribution_factory.py new file mode 100644 index 0000000..70d1b3d --- /dev/null +++ b/src/web3client/contracts_ws/service_node_contribution_factory.py @@ -0,0 +1,88 @@ +import logging + +from eth_typing import ChecksumAddress +from web3 import AsyncWeb3 +from web3.contract.async_contract import AsyncContractEvent +from web3.types import EventData +from web3.utils.subscriptions import EthSubscriptionContext + +from src.staking.read import DBReaderStaking +from src.staking.write import DBWriterStaking +from src.util.parse import parse_ed25519_pubkey +from src.web3client.contracts_ws.contract_ws import ContractWS +from src.web3client.contracts_ws.service_node_contribution import ServiceNodeContribution +from src.web3client.contracts_ws.contract_utils import queue_past_events_for_scanning +from src.web3client.event_queue_manager import EventQueueManager + + +class ServiceNodeContributionFactory(ContractWS): + name = "ServiceNodeContributionFactory" + event_names = ["NewServiceNodeContributionContract"] + + def __init__(self, w3: AsyncWeb3, db_writer: DBWriterStaking, db_reader: DBReaderStaking, log: logging, + event_queue: EventQueueManager | None = None, start_block: int = 0, topic_map = None, event_addresses = None): + super().__init__(self.name, w3, db_writer, log, event_queue) + self.service_node_contribution_contract = ServiceNodeContribution(w3=w3, db_writer=db_writer, + db_reader=db_reader, log=log, + event_queue=self.event_queue) + self.bootstrap_contribution_contract_addresses = [] + self.start_block = start_block + self.topic_map = topic_map + self.event_addresses = event_addresses + + def add_existing_contribution_contracts(self, addresses: list[str]): + self.bootstrap_contribution_contract_addresses.extend(addresses) + + def bootstrap_contribution_contracts(self): + if len(self.bootstrap_contribution_contract_addresses) == 0: + self.log.warning("No bootstrap contribution contract addresses found") + return + self.log.info(f"Bootstrapping {len(self.bootstrap_contribution_contract_addresses)} contribution contracts from block {self.start_block}") + self.service_node_contribution_contract.queue_past_events_for_scanning( + address=self.bootstrap_contribution_contract_addresses, start_block=self.start_block) + events = self.service_node_contribution_contract.get_events(self.bootstrap_contribution_contract_addresses[0]) + for address in self.bootstrap_contribution_contract_addresses: + self.event_addresses.add(address) + for event in events: + existing_topic = self.topic_map.get(event().topic) + if existing_topic is None: + print(f"Adding topic f{event.name}: {event().topic}") + self.topic_map[event().topic] = (self.service_node_contribution_contract.event_abis[event.name], self.service_node_contribution_contract.handle_event) + + + async def handle_event(self, event: EventData): + address = event.args.get("contributorContract") + await self._handle_event(event, main_arg=address) + self.db_writer.write_new_contribution_contract(address, event.args.get("operator"), parse_ed25519_pubkey(event.args.get("serviceNodePubkey"))) + events = self.service_node_contribution_contract.get_events(address) + self.event_addresses.add(address) + for event in events: + topic = event().topic + existing_topic = self.topic_map.get(topic) + if existing_topic is None: + self.log.debug(f"Adding new topic {topic}") + self.topic_map[topic] = (self.service_node_contribution_contract.event_abis[event.name], self.service_node_contribution_contract.handle_event) + + + async def handle_event_bootstrap(self, event: EventData): + address = event.args.get("contributorContract") + await self._handle_event(event, main_arg=address) + self.db_writer.write_new_contribution_contract(address, event.args.get("operator"), parse_ed25519_pubkey(event.args.get("serviceNodePubkey"))) + self.bootstrap_contribution_contract_addresses.append(address) + + + async def handle_event_sub(self, event: EthSubscriptionContext): + return await self.handle_event(self._parse_event(event)) + + def get_events(self, address: ChecksumAddress) -> list[AsyncContractEvent]: + events = self.factory(address).events + return [events[name] for name in self.event_names] + + def queue_past_events_for_scanning(self, address: ChecksumAddress): + return queue_past_events_for_scanning( + events=self.get_events(address), + event_queue=self.event_queue, + event_abis=self.event_abis, + handler_past=self.handle_event_bootstrap, + start_block=self.start_block, + ) diff --git a/src/web3client/contracts_ws/service_node_rewards.py b/src/web3client/contracts_ws/service_node_rewards.py new file mode 100644 index 0000000..347f589 --- /dev/null +++ b/src/web3client/contracts_ws/service_node_rewards.py @@ -0,0 +1,111 @@ +import logging + +from eth_typing import ChecksumAddress +from web3 import AsyncWeb3 +from web3.contract.async_contract import AsyncContractEvent +from web3.types import EventData +from web3.utils.subscriptions import EthSubscriptionContext + +from src.staking.read import DBReaderStaking +from src.staking.write import DBWriterStaking +from src.web3client.contracts_ws.contract_ws import ContractWS +from src.web3client.contracts_ws.contract_utils import queue_past_events_for_scanning +from src.web3client.event_queue_manager import EventQueueManager + +def handle_claim(event: EventData, db_writer: DBWriterStaking, db_reader: DBReaderStaking, log: logging): + address = event.args.recipientAddress + amount = event.args.amount + + try: + reward_info = db_reader.get_rewards_info_for_address(address) + except Exception as e: + return + + assert reward_info is not None, f"Rewards info not found for address {address}" + + claimed_stakes = reward_info.claimed_stakes + claimed_rewards = reward_info.claimed_rewards + + remaining = amount + stakes_avail = reward_info.lifetime_unlocked_stakes - reward_info.lifetime_liquidated_stakes - claimed_stakes + if remaining > stakes_avail: + remaining -= stakes_avail + claimed_stakes += stakes_avail + else: + claimed_stakes += remaining + remaining = 0 + + rewards_avail = reward_info.lifetime_rewards - claimed_rewards + if remaining > rewards_avail: + remaining -= rewards_avail + claimed_rewards += rewards_avail + else: + claimed_rewards += remaining + remaining = 0 + + # assert remaining == 0, f"Remaining rewards {remaining} is not equal to 0" + + db_writer.write_update_rewards_claim_amounts(address, claimed_stakes, claimed_rewards) + + +class ServiceNodeRewards(ContractWS): + name = "ServiceNodeRewards" + + def __init__(self, w3: AsyncWeb3, db_writer: DBWriterStaking, db_reader: DBReaderStaking, log: logging, + event_queue: EventQueueManager | None = None): + super().__init__(self.name, w3, db_writer, log, event_queue) + self.db_reader = db_reader + + @staticmethod + def get_main_arg(event: EventData): + match event.event: + case "RewardsClaimed": + return event.args.recipientAddress + case "StakingRequirementUpdated": + return event.args.newRequirement + case "ClaimThresholdUpdated": + return event.args.newThreshold + case "ClaimCycleUpdated": + return event.args.newValue + case "LiquidationRatiosUpdated": + return event.args.liquidatorRatio + case "BLSNonSignerThresholdMaxUpdated": + return event.args.newMax + case _: + return event.args.serviceNodeID + + async def handle_event(self, event: EventData): + main_arg = ServiceNodeRewards.get_main_arg(event) + assert main_arg is not None + + if event.event == "RewardsClaimed": + handle_claim(event, self.db_writer, self.db_reader, self.log) + + return await self._handle_event(event, main_arg=main_arg) + + async def handle_event_sub(self, event: EthSubscriptionContext): + return await self.handle_event(self._parse_event(event)) + + def get_events(self, address: ChecksumAddress) -> list[AsyncContractEvent]: + events = self.factory(address).events + return [ + events.NewSeededServiceNode, + events.NewServiceNodeV2, + events.ServiceNodeExitRequest, + events.ServiceNodeExit, + events.ServiceNodeLiquidated, + events.RewardsClaimed, + events.StakingRequirementUpdated, + events.ClaimThresholdUpdated, + events.ClaimCycleUpdated, + events.LiquidationRatiosUpdated, + events.BLSNonSignerThresholdMaxUpdated, + ] + + def queue_past_events_for_scanning(self, address: ChecksumAddress): + return queue_past_events_for_scanning( + events=self.get_events(address), + event_queue=self.event_queue, + event_abis=self.event_abis, + handler_past=self.handle_event, + ) diff --git a/src/web3client/contracts_ws/token.py b/src/web3client/contracts_ws/token.py new file mode 100644 index 0000000..66bc2a2 --- /dev/null +++ b/src/web3client/contracts_ws/token.py @@ -0,0 +1,68 @@ +import logging + +from eth_typing import ChecksumAddress +from web3 import AsyncWeb3 +from web3.contract.async_contract import AsyncContractEvent +from web3.types import EventData +from web3.utils.subscriptions import EthSubscriptionContext + +from src.staking.write import DBWriterStaking +from src.web3client.contracts_ws.contract_ws import ContractWS +from src.web3client.contracts_ws.contract_utils import queue_past_events_for_scanning +from src.web3client.event_queue_manager import EventQueueManager + + +class Token(ContractWS): + name = "SESH" + decimals = 9 + + def __init__(self, w3: AsyncWeb3, db_writer: DBWriterStaking, log: logging, + event_queue: EventQueueManager | None = None): + super().__init__(self.name, w3, db_writer, log, event_queue) + + @staticmethod + def to_atomic(amount: float | int) -> int: + """ + Converts a float or int to an atomic amount + """ + return int(amount * (10 ** Token.decimals)) + + @staticmethod + def from_atomic(amount: int) -> float: + """ + Converts an atomic amount to a float + """ + return amount / (10 ** Token.decimals) + + @staticmethod + def get_main_arg(event: EventData): + match event.event: + case "Approval": + return event.args.owner + case "Transfer": + return event.args.to + case _: + return None + + async def handle_event(self, event: EventData): + main_arg = Token.get_main_arg(event) + assert main_arg is not None + return await self._handle_event(event, main_arg=main_arg) + + async def handle_event_sub(self, event: EthSubscriptionContext): + return await self.handle_event(self._parse_event(event)) + + def get_events(self, address: ChecksumAddress) -> list[AsyncContractEvent]: + events = self.factory(address).events + return [ + events.Transfer, + events.Approval, + ] + + def queue_past_events_for_scanning(self, address: ChecksumAddress): + return queue_past_events_for_scanning( + events=self.get_events(address), + event_queue=self.event_queue, + event_abis=self.event_abis, + handler_past=self.handle_event, + ) diff --git a/src/web3client/contracts_ws/token_vesting_staking.py b/src/web3client/contracts_ws/token_vesting_staking.py new file mode 100644 index 0000000..c714911 --- /dev/null +++ b/src/web3client/contracts_ws/token_vesting_staking.py @@ -0,0 +1,70 @@ +import logging + +from eth_typing import ChecksumAddress +from web3 import AsyncWeb3 +from web3.contract import Contract +from web3.types import EventData +from web3.utils.subscriptions import EthSubscriptionContext + +from src.staking.write import DBWriterStaking +from src.web3client.contracts_ws.contract_ws import ContractWS +from src.web3client.contracts_ws.contract_utils import queue_past_events_for_scanning, create_processed_event +from src.web3client.event_queue_manager import EventQueueManager + + +class TokenVestingStaking(ContractWS): + name = "TokenVestingStaking" + + def __init__(self, w3: AsyncWeb3, db_writer: DBWriterStaking, log: logging, + event_queue: EventQueueManager | None = None): + super().__init__(self.name, w3, db_writer, log, event_queue) + + async def handle_event(self, event: EventData): + await self._handle_event(event, main_arg=event.address) + if event.get("event") == "BeneficiaryTransferred": + to_address = event.get("args").get("newBeneficiary") + self.db_writer.write_update_vesting_contract_beneficiary(event.address, to_address) + + async def handle_event_sub(self, event: EthSubscriptionContext): + await self.handle_event(self._parse_event(event)) + + def queue_past_events_for_scanning(self, address: ChecksumAddress | list[ChecksumAddress]): + events = self.factory(address[0] if isinstance(address, list) else address).events + event_list = [ + events.TokensReleased, + events.TokenVestingRevoked, + events.TokensRevokedReleased, + events.BeneficiaryTransferred, + events.RevokerTransferred, + ] + + for event in event_list: + event.address = address + + return queue_past_events_for_scanning( + events=events, + event_queue=self.event_queue, + event_abis=self.event_abis, + handler_past=self.handle_event, + ) + + batch_items = 9 + + async def batch_get_details(self, addresses: list[ChecksumAddress], token_contract: Contract): + async with self.w3.batch_requests() as batch: + for address in addresses: + contract = self.factory(address) + batch.add(contract.functions.beneficiary()) + batch.add(contract.functions.revoker()) + batch.add(token_contract.functions.balanceOf(address)) + batch.add(contract.functions.transferableBeneficiary()) + batch.add(contract.functions.start()) + batch.add(contract.functions.end()) + batch.add(contract.functions.SESH()) + batch.add(contract.functions.rewardsContract()) + batch.add(contract.functions.snContribFactory()) + + responses = await batch.async_execute() + assert len(responses) == len(addresses) * TokenVestingStaking.batch_items + + return responses diff --git a/src/web3client/event_queue_manager.py b/src/web3client/event_queue_manager.py new file mode 100644 index 0000000..56ff271 --- /dev/null +++ b/src/web3client/event_queue_manager.py @@ -0,0 +1,114 @@ +import asyncio +import logging +from dataclasses import dataclass +from math import ceil +from typing import Callable + +from web3 import AsyncWeb3 + +from src.util.parse import get_relative_time_from_ms +from src.web3client.util import get_time_of_arbitrum_blocks_ms + + +@dataclass +class PastEventsFetchQueueItem: + event: any + handler: callable + start_block: int | None + + +class EventQueueManager: + def __init__(self, w3: AsyncWeb3, log: logging, start_block: int = 0, max_run_depth: int = 10, get_logs_cap: int = 0): + self.processed_events = 0 + self.event_queue = [] + self.w3 = w3 + self.log = log + self.start_block = start_block + self.max_run_depth = max_run_depth + self.get_logs_cap = get_logs_cap + log.info(f"Event queue manager starting with start block {start_block}") + log.debug(f"Event queue manager max run depth {max_run_depth}") + + def add_event(self, event: any, handler: Callable, start_block: int | None = None): + if start_block is None: + start_block = self.start_block + self.event_queue.append(PastEventsFetchQueueItem(event=event, handler=handler, start_block=start_block)) + + async def process_event_queue(self, start_block: int | None = None, current_block: int = 0): + if start_block is None: + start_block = self.start_block + + # Once a websocket subscription is made, any events will be queued to be processed by the websocket consumer. This + # past event scan should be done right after the websocket subscription is made so the block number at this time + # can be used for all past event scans. + block_current = await self.w3.eth.block_number if current_block == 0 else current_block + + queue, self.event_queue = self.event_queue, [] + + if len(queue) > 0: + get_logs_calls = 0 + for e in queue: + from_block = e.start_block if e.start_block else start_block + e.get_logs_calls=(ceil((block_current - from_block) / self.get_logs_cap) if self.get_logs_cap > 0 else 1) + get_logs_calls += e.get_logs_calls + + start_log_message = f"Fetching past events for {len(queue)} events, this will take {get_logs_calls} getLogs calls with get_logs_cap set to {self.get_logs_cap}" + + if get_logs_calls > 500: + start_log_message += f" (this may take a while)" + self.log.warning(start_log_message) + else: + self.log.info(start_log_message) + + responses = [] + for past_event in queue: + from_block = past_event.start_block if past_event.start_block else start_block + self.log.info( + f"Fetching past events for {past_event.event.event_name} from block {from_block} to block {block_current} ({block_current - from_block} blocks ~{get_relative_time_from_ms(get_time_of_arbitrum_blocks_ms(block_current - from_block))})") + + fetched_block = start_block - 1 + + to_block = block_current if self.get_logs_cap == 0 else min(block_current, from_block + self.get_logs_cap) + + self.log.info(f"Fetching logs for {past_event.event.event_name} with get_logs_cap set to {self.get_logs_cap} will take {past_event.get_logs_calls} getLogs calls") + + while fetched_block < to_block: + self.log.info(f"Fetching logs for {past_event.event.event_name} from block {from_block} to block {to_block} ({to_block - from_block} blocks ~{get_relative_time_from_ms(get_time_of_arbitrum_blocks_ms(to_block - from_block))})") + for recent in await past_event.event.get_logs(from_block=from_block, to_block=to_block): + responses.append((recent, past_event.handler)) + fetched_block = to_block + from_block = to_block + 1 + to_block = block_current if self.get_logs_cap == 0 else min(block_current, from_block + self.get_logs_cap) + self.processed_events += 1 + + # sort responses by block then log index + responses = sorted(responses, key=lambda x: (x[0].get("blockNumber"), x[0].get("logIndex"))) + + handlers = [] + for (data, handler) in responses: + handlers.append(handler(data)) + + await asyncio.gather(*handlers) + + else: + self.log.debug("No past events to fetch") + + return block_current + + async def run(self, current_block: int = 0): + # The max depth ensures the loop won't get stuck in an infinite loop. This is just a safety measure as it should + # not be possible due to the queue population dependencies. + run_depth = 0 + last_scanned_block = 0 + while run_depth <= self.max_run_depth and len(self.event_queue) > 0: + last_scanned_block = await self.process_event_queue(current_block=current_block) + run_depth += 1 + + logging.debug( + f"Processed lifetime total of {self.processed_events} events") + + if run_depth > self.max_run_depth: + self.log.warning( + f"Reached max run depth of {self.max_run_depth}. This may indicate a problem with the event scanner. Events may have been missed.") + + return last_scanned_block \ No newline at end of file diff --git a/src/web3client/event_scanner.py b/src/web3client/event_scanner.py new file mode 100644 index 0000000..54cfa96 --- /dev/null +++ b/src/web3client/event_scanner.py @@ -0,0 +1,370 @@ +# Extended from https://web3py.readthedocs.io/en/stable/filters.html#example-code +"""A stateful event scanner for Ethereum-based blockchains using web3.py. + +With the stateful mechanism, you can do one batch scan or incremental scans, +where events are added wherever the scanner left off. +""" +import json +import time +import logging +from dataclasses import dataclass +from typing import Tuple, Optional, Callable, List, Iterable, Dict, Any + +from web3 import Web3, HTTPProvider +from web3.datastructures import AttributeDict +from web3.exceptions import Web3TypeError +from eth_abi.codec import ABICodec + +# Currently this method is not exposed over official web3 API, +# but we need it to construct eth_getLogs parameters +from web3._utils.filters import construct_event_filter_params +from web3._utils.events import get_event_data + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +@dataclass +class ProcessedEvent: + args: dict + block: int + log_index: int + main_arg: str | None + name: str + tx: str + + def __post_init__(self): + self.args = json.loads(self.args) if type(self.args) == str else self.args + + +class EventScanner: + """Scan blockchain for events and try not to abuse JSON-RPC API too much. + + Can be used for real-time scans, as it detects minor chain reorganisation and rescans. + Unlike the easy web3.contract.Contract, this scanner can scan events from multiple contracts at once. + For example, you can get all transfers from all tokens in the same scan. + + You *should* disable the default ``exception_retry_configuration`` on your provider for Web3, + because it cannot correctly throttle and decrease the `eth_getLogs` block number range. + """ + + def __init__( + self, + provider_url: str, + events: List, + filters: Dict[str, Any], + max_chunk_scan_size: int = 10_000, + max_request_retries: int = 5, + request_retry_seconds: float = 5, + safety_blocks: int = 10, + optimal_chunk_size: int = 20, + ): + """ + :param events: List of web3 Event we scan + :param filters: Filters passed to get_logs + :param max_chunk_scan_size: JSON-RPC API limit in the number of blocks we query. (Recommendation: 10,000 for mainnet, 500,000 for testnets) + :param max_request_retries: How many times we try to reattempt a failed JSON-RPC call + :param request_retry_seconds: Delay between failed requests to let JSON-RPC server to recover + """ + + self.logger = logger + provider = HTTPProvider(provider_url) + # Disable the default JSON-RPC retry configuration + # as it correctly cannot handle eth_getLogs block range + provider.exception_retry_configuration = None + self.w3 = Web3(provider) + self.events = events + self.filters = filters + + # Our JSON-RPC throttling parameters + # self.min_scan_chunk_size = 10 # 12 s/block = 120 seconds period + self.min_scan_chunk_size = 10 # 12 s/block = 120 seconds period + self.max_scan_chunk_size = max_chunk_scan_size + self.max_request_retries = max_request_retries + self.request_retry_seconds = request_retry_seconds + + self.safety_blocks = safety_blocks + + # Factor how fast we increase the chunk size if results are found + # # (slow down scan after starting to get hits) + self.chunk_size_decrease = 0.5 + + # Factor how fast we increase chunk size if no results found + # self.chunk_size_increase = 2.0 + self.chunk_size_increase = 10.0 + + # Start low at 20, this value is set at the end of the scan so we can use the optimal chunk size in future scans + self.optimal_chunk_size = optimal_chunk_size + + def scan_chunk(self, start_block, end_block) -> Tuple[int, list]: + """Read and process events between to block numbers. + + Dynamically decrease the size of the chunk if the case JSON-RPC server pukes out. + + :return: tuple(actual end block number, when this block was mined, processed events) + """ + + all_processed = [] + + # TODO: investigate sending all event types in a single call instead of looping over them + for event_type in self.events: + + # Callable that takes care of the underlying web3 call + def _fetch_events(_start_block, _end_block): + return _fetch_events_for_all_contracts( + self.w3, event_type, self.filters, from_block=_start_block, to_block=_end_block + ) + + # Do `n` retries on `eth_getLogs`, + # throttle down block range if needed + end_block, events = _retry_web3_call( + _fetch_events, + start_block=start_block, + end_block=end_block, + retries=self.max_request_retries, + delay=self.request_retry_seconds, + ) + + for evt in events: + idx = evt[ + "logIndex" + ] # Integer of the log index position in the block, null when its pending + + # We cannot avoid minor chain reorganisations, but + # at least we must avoid blocks that are not mined yet + assert idx is not None, "Somehow tried to scan a pending block" + + logger.debug(f"Processing event {evt['event']}, block: {evt['blockNumber']}") + + processed = self.process_event(evt) + all_processed.append(processed) + + return end_block, all_processed + + def estimate_next_chunk_size(self, current_chuck_size: int, event_found_count: int): + """Try to figure out optimal chunk size + + Our scanner might need to scan the whole blockchain for all events + + * We want to minimize API calls over empty blocks + + * We want to make sure that one scan chunk does not try to process too many entries once, as we try to control commit buffer size and potentially asynchronous busy loop + + * Do not overload node serving JSON-RPC API by asking data for too many events at a time + + Currently Ethereum JSON-API does not have an API to tell when a first event occurred in a blockchain + and our heuristics try to accelerate block fetching (chunk size) until we see the first event. + + These heuristics exponentially increase the scan chunk size depending on if we are seeing events or not. + When any transfers are encountered, we are back to scanning only a few blocks at a time. + It does not make sense to do a full chain scan starting from block 1, doing one JSON-RPC call per 20 blocks. + """ + current_chuck_size *= self.chunk_size_increase + + current_chuck_size = max(self.min_scan_chunk_size, current_chuck_size) + current_chuck_size = min(self.max_scan_chunk_size, current_chuck_size) + return int(current_chuck_size) + + def scan( + self, start_block, end_block, progress_callback=Optional[Callable] + ) -> Tuple[list, int]: + """Perform a token balances scan. + + Assumes all balances in the database are valid before start_block (no forks sneaked in). + + :param start_block: The first block included in the scan + + :param end_block: The last block included in the scan + + :param start_chunk_size: How many blocks we try to fetch over JSON-RPC on the first attempt + + :param progress_callback: If this is an UI application, update the progress of the scan + + :return: [All processed events, number of chunks used] + """ + + assert start_block <= end_block + + current_block = start_block + + # Scan in chunks, commit between + chunk_size = self.optimal_chunk_size + last_scan_duration = last_logs_found = 0 + total_chunks_scanned = 0 + + # All processed entries we got on this scan cycle + all_processed = [] + + while current_block <= end_block: + + # Print some diagnostics to logs to try to fiddle with real world JSON-RPC API performance + estimated_end_block = min(current_block + chunk_size, end_block) + logger.debug( + f"Scanning events for blocks: {current_block} - {estimated_end_block}, chunk size {chunk_size}, last chunk scan took {last_scan_duration}, last logs found {last_logs_found}" + ) + + start = time.time() + actual_end_block, new_entries = self.scan_chunk(current_block, estimated_end_block) + + # Where does our current chunk scan ends - are we out of chain yet? + current_end = actual_end_block + + last_scan_duration = time.time() - start + all_processed += new_entries + + if progress_callback: + progress_callback( + start_block, + end_block, + current_block, + chunk_size, + len(new_entries), + ) + + # Try to guess how many blocks to fetch over `eth_getLogs` API next time + chunk_size = self.estimate_next_chunk_size(chunk_size, len(new_entries)) + + # Set where the next chunk starts + current_block = current_end + 1 + total_chunks_scanned += 1 + + self.optimal_chunk_size = chunk_size + return all_processed, total_chunks_scanned + + def process_event(self, event: AttributeDict) -> ProcessedEvent: + """Record a ERC-20 transfer in our database.""" + # Events are keyed by their transaction hash and log index + # One transaction may contain multiple events + # and each one of those gets their own log index + # log_index = event.logIndex # Log index within the block + tx = event.transactionHash.hex() + block = event.blockNumber + name = event.event + args = event["args"] + return ProcessedEvent(tx=tx, block=block, name=name, args=args, timestamp=None, main_arg=None) + + def run( + self, + last_block: int, + end_block: int, + contract_creation_height=0, + ): + start = time.time() + # Scan from [last block scanned] - [latest ethereum block] + # Note that our chain reorg safety blocks cannot go negative + start_block = max(last_block - self.safety_blocks, 0, contract_creation_height) + blocks_to_scan = end_block - start_block + + print(f"Scanning events from blocks {start_block} - {end_block}") + + def _update_progress(start, end, current, chunk_size, events_count): + print( + f"Current block: {current}, blocks in a scan batch: {chunk_size}, events processed in a batch {events_count}" + ) + + # Run the scan + result, total_chunks_scanned = self.scan( + start_block, end_block, progress_callback=_update_progress + ) + + logger.debug( + f"Scanned total {len(result)} events in {time.time() - start}, total {total_chunks_scanned} chunk scans performed" + ) + + return result + + +def _retry_web3_call(func, start_block, end_block, retries, delay) -> Tuple[int, list]: + """A custom retry loop to throttle down block range. + + If our JSON-RPC server cannot serve all incoming `eth_getLogs` in a single request, + we retry and throttle down block range for every retry. + + For example, Go Ethereum does not indicate what is an acceptable response size. + It just fails on the server-side with a "context was cancelled" warning. + + :param func: A callable that triggers Ethereum JSON-RPC, as func(start_block, end_block) + :param start_block: The initial start block of the block range + :param end_block: The initial start block of the block range + :param retries: How many times we retry + :param delay: Time to sleep between retries + """ + for i in range(retries): + try: + return end_block, func(start_block, end_block) + except Exception as e: + # Assume this is HTTPConnectionPool(host='localhost', port=8545): Read timed out. (read timeout=10) + # from Go Ethereum. This translates to the error "context was cancelled" on the server side: + # https://github.com/ethereum/go-ethereum/issues/20426 + if i < retries - 1: + # Give some more verbose info than the default middleware + logger.warning( + f"Retrying events for block range {start_block} - {end_block} ({end_block-start_block}) failed with {e} , retrying in {delay} seconds" + ) + # Decrease the `eth_getBlocks` range + end_block = start_block + ((end_block - start_block) // 2) + # Let the JSON-RPC to recover e.g. from restart + time.sleep(delay) + continue + else: + logger.warning("Out of retries") + raise + + +def _fetch_events_for_all_contracts( + w3, event, argument_filters: Dict[str, Any], from_block: int, to_block: int +) -> Iterable: + """Get events using eth_getLogs API. + + This method is detached from any contract instance. + + This is a stateless method, as opposed to create_filter. + It can be safely called against nodes which do not provide `eth_newFilter` API, like Infura. + """ + + if from_block is None: + raise Web3TypeError("Missing mandatory keyword argument to get_logs: from_block") + + # Currently no way to poke this using a public web3.py API. + # This will return raw underlying ABI JSON object for the event + abi = event._get_event_abi() + + # Depending on the Solidity version used to compile + # the contract that uses the ABI, + # it might have Solidity ABI encoding v1 or v2. + # We just assume the default that you set on Web3 object here. + # More information here https://eth-abi.readthedocs.io/en/latest/index.html + codec: ABICodec = w3.codec + + # Here we need to poke a bit into Web3 internals, as this + # functionality is not exposed by default. + # Construct JSON-RPC raw filter presentation based on human readable Python descriptions + # Namely, convert event names to their keccak signatures + # More information here: + # https://github.com/ethereum/web3.py/blob/e176ce0793dafdd0573acc8d4b76425b6eb604ca/web3/_utils/filters.py#L71 + data_filter_set, event_filter_params = construct_event_filter_params( + abi, + codec, + address=argument_filters.get("address"), + argument_filters=argument_filters, + from_block=from_block, + to_block=to_block, + ) + + logger.debug(f"Querying eth_getLogs with the following parameters: {event_filter_params}") + + # Call JSON-RPC API on your Ethereum node. + # get_logs() returns raw AttributedDict entries + logs = w3.eth.get_logs(event_filter_params) + + # Convert raw binary data to Python proxy objects as described by ABI + all_events = [] + for log in logs: + # Convert raw JSON-RPC log result to human readable event by using ABI data + # More information how process_log works here + # https://github.com/ethereum/web3.py/blob/fbaf1ad11b0c7fac09ba34baff2c256cffe0a148/web3/_utils/events.py#L200 + evt = get_event_data(codec, abi, log) + # Note: This was originally yield, + # but deferring the timeout exception caused the throttle logic not to work + all_events.append(evt) + return all_events diff --git a/src/web3client/event_ws.py b/src/web3client/event_ws.py new file mode 100644 index 0000000..2f4a852 --- /dev/null +++ b/src/web3client/event_ws.py @@ -0,0 +1,446 @@ +import asyncio +import logging +import time +from datetime import datetime +from functools import partial + +from attr import dataclass + +from eth_typing import ChecksumAddress +from eth_utils import is_checksum_address, to_checksum_address +from web3 import AsyncWeb3, WebSocketProvider +from web3._utils.events import get_event_data +from web3.auto.gethdev import async_w3 +from web3.utils.subscriptions import NewHeadsSubscriptionContext, NewHeadsSubscription + +from src.config_validate import validate_log_config, validate_contract_addresses +from src.db.util import is_db_initialized, init_db +from src.log import Log +from src.staking.dataclasses import VestingContract +from src.staking.read import DBReaderStaking +from src.staking.write import DBWriterStaking +from src.web3client.contracts_ws.ierc_1967 import IERC1967 +from src.web3client.contracts_ws.ownable_2_step_upgradeable import Ownable2StepUpgradeable +from src.web3client.contracts_ws.pausable_upgradeable import PausableUpgradeable +from src.web3client.contracts_ws.reward_rate_pool import RewardRatePool +from src.web3client.contracts_ws.service_node_contribution_factory import ServiceNodeContributionFactory +from src.web3client.contracts_ws.service_node_rewards import ServiceNodeRewards +from src.web3client.contracts_ws.token import Token +from src.web3client.contracts_ws.token_vesting_staking import TokenVestingStaking +from src.web3client.event_queue_manager import EventQueueManager + +log = Log("event_ws", enable_perf=True).logger +global_db_writer: DBWriterStaking | None = None +global_db_reader: DBReaderStaking | None = None +event_queue: EventQueueManager | None = None +sn_contrib_factory: ServiceNodeContributionFactory | None = None + +topic_map = {} +event_addresses = set() + +@dataclass(init=False) +class VestingContractDetails: + beneficiary: ChecksumAddress + vesting_address: ChecksumAddress + amount: int + start: int + end: int + transferable_beneficiary: bool + revoker: ChecksumAddress + SESH: ChecksumAddress + rewards_contract: ChecksumAddress + sn_contrib_factory: ChecksumAddress + + def __init__(self, beneficiary, vesting_address, amount, start, end, transferable_beneficiary, revoker, SESH, + rewards_contract, sn_contrib_factory): + self.beneficiary = beneficiary + self.vesting_address = vesting_address + self.amount = Token.to_atomic(float(amount)) + self.start = int(datetime.fromisoformat(start).timestamp()) + self.end = int(datetime.fromisoformat(end).timestamp()) + self.transferable_beneficiary = str.lower(transferable_beneficiary) == "true" + self.revoker = revoker + self.SESH = SESH + self.rewards_contract = rewards_contract + self.sn_contrib_factory = sn_contrib_factory + + # TODO: decide if we want this + # assert is_checksum_address(self.beneficiary) + # assert is_checksum_address(self.vesting_address) + # assert self.amount > 0 + # assert self.start < self.end + # assert self.transferable_beneficiary is not None + # assert is_checksum_address(self.revoker) + # assert is_checksum_address(self.SESH) + # assert is_checksum_address(self.rewards_contract) + assert is_checksum_address(self.sn_contrib_factory) + + +@dataclass +class EventScannerConfig: + log_level: int + log_level_generic: str | None + enable_perf: bool | None + ws_max_run_depth: int + ws_providers: list[str] + ws_max_size: int + genesis_block: int + contrib_factory_start_block: int + addr_token: ChecksumAddress + addr_sn_contrib_factory: ChecksumAddress + addr_sn_rewards: ChecksumAddress + addr_reward_rate_pool: ChecksumAddress + get_logs_cap: int + refresh_block_interval: int + sqlite_db: str + sqlite_schema: str + db_reset_events_on_startup: bool + db_reset_contrib_on_startup: bool + reset_vesting_contracts_on_startup: bool + vesting_contract_details: list[VestingContractDetails] + ws_watch_token_events: bool + run_once_as_script: bool = False + + def __post_init__(self): + self.addr_token = to_checksum_address(self.addr_token) + self.addr_sn_contrib_factory = to_checksum_address(self.addr_sn_contrib_factory) + self.addr_sn_rewards = to_checksum_address(self.addr_sn_rewards) + self.addr_reward_rate_pool = to_checksum_address(self.addr_reward_rate_pool) + + +async def load_vesting_staking_contracts(w3: AsyncWeb3, details: list[VestingContractDetails]): + contract_interface = TokenVestingStaking(w3=w3, db_writer=global_db_writer, log=log, event_queue=event_queue) + #token_contract = Token(w3=w3, db_writer=global_db_writer, log=log).factory(details[0].SESH) + + if global_db_reader.has_vesting_contracts(): + if len(details) > 0: + log.warning("Vesting contracts already loaded in database, skipping provided contracts.") + address_list = [contract.address for contract in global_db_reader.get_vesting_contracts()] + else: + if len(details) == 0: + log.warning( + "No vesting contracts found in database and none were provided to load. Skipping vesting contract loading.") + return + + address_list = [contract.vesting_address for contract in details] + + now = time.time() + #res = await contract_interface.batch_get_details(address_list, token_contract + res = [] + contracts = [] + for known_details in details: + #known_details = details[i // contract_interface.batch_items] + #beneficiary = res[i] + #revoker = res[i + 1] + # amount = res[i + 2] + #transferable_beneficiary = res[i + 3] + #start = res[i + 4] + #end = res[i + 5] + #SESH = res[i + 6] + #rewards_contract = res[i + 7] + #sn_contrib_factory = res[i + 8] + + # assert revoker == known_details.revoker, f"Expected {known_details.revoker}, got {revoker}" + + # if start < now: + # assert amount == known_details.amount, f"Expected {known_details.amount}, got {amount}" + # TODO: consider checking staked amounts and asserting those + + # assert transferable_beneficiary == known_details.transferable_beneficiary, f"Expected {known_details.transferable_beneficiary}, got {transferable_beneficiary}" + # if not transferable_beneficiary: + # assert beneficiary == known_details.beneficiary, f"Expected {known_details.beneficiary}, got {beneficiary}" + # TODO: consider checking if beneficiary has changed and is correct + + # assert start == known_details.start, f"Expected {known_details.start}, got {start}" + # assert end == known_details.end, f"Expected {known_details.end}, got {end}" + # assert SESH == known_details.SESH, f"Expected {known_details.SESH}, got {SESH}" + # assert rewards_contract == known_details.rewards_contract, f"Expected {known_details.rewards_contract}, got {rewards_contract}" + # assert sn_contrib_factory == known_details.sn_contrib_factory, f"Expected {known_details.sn_contrib_factory}, got {sn_contrib_factory}" + + contracts.append(VestingContract( + address=known_details.vesting_address, + beneficiary=known_details.beneficiary, + initial_amount=known_details.amount, + initial_beneficiary=known_details.beneficiary, + revoker=known_details.revoker, + time_end=known_details.end, + time_start=known_details.start, + transferable_beneficiary=known_details.transferable_beneficiary, + )) + + + global_db_writer.write_vesting_contracts(contracts) + + # Verify that the contracts are the same coming out of the db + db_vesting_contracts = global_db_reader.get_vesting_contracts() + for i in range(len(contracts)): + contract = contracts[i] + db_contract = db_vesting_contracts[i] + if not contract.transferable_beneficiary: + assert contract.beneficiary == db_contract.beneficiary, f"Expected {contract.beneficiary}, got {db_contract.beneficiary}" + + assert contract.revoker == db_contract.revoker, f"Expected {contract.revoker}, got {db_contract.revoker}" + if contract.time_start < now: + assert contract.initial_amount == db_contract.initial_amount, f"Expected {contract.initial_amount}, got {db_contract.initial_amount}" + + assert contract.time_start == db_contract.time_start, f"Expected {contract.time_start}, got {db_contract.time_start}" + assert contract.time_end == db_contract.time_end, f"Expected {contract.time_end}, got {db_contract.time_end}" + assert contract.transferable_beneficiary == db_contract.transferable_beneficiary, f"Expected {contract.transferable_beneficiary}, got {db_contract.transferable_beneficiary}" + + log.info( + f"Added vesting contract {contract.address} with initial balance {contract.initial_amount} for {contract.beneficiary}") + + log.info(f"Subscribing to {len(address_list)} vesting contracts") + contract_interface.queue_past_events_for_scanning(address=address_list) + + +async def init_global_contracts(w3: AsyncWeb3, config: EventScannerConfig): + global event_queue, sn_contrib_factory + + last_event_block = global_db_reader.get_last_fetched_arbitrum_event_block_height() + start_block = last_event_block + 1 if last_event_block else config.genesis_block + log.info(f"Last block for an event from the database: {last_event_block}, starting from block {start_block}") + + event_queue = EventQueueManager(w3=w3, log=log, start_block=start_block, max_run_depth=config.ws_max_run_depth, get_logs_cap=config.get_logs_cap) + + if len(config.vesting_contract_details) > 0 : + await load_vesting_staking_contracts(w3, details=config.vesting_contract_details) + + sn_contrib_factory = ServiceNodeContributionFactory(w3=w3, db_writer=global_db_writer, db_reader=global_db_reader, + log=log, event_queue=event_queue, start_block=max(config.contrib_factory_start_block, start_block), topic_map=topic_map, event_addresses=event_addresses) + sn_contrib_factory.queue_past_events_for_scanning(address=config.addr_sn_contrib_factory) + event_addresses.add(config.addr_sn_contrib_factory) + for event in sn_contrib_factory.get_events(config.addr_sn_contrib_factory): + topic_map[event().topic] = (sn_contrib_factory.event_abis[event.name], sn_contrib_factory.handle_event) + + snr = ServiceNodeRewards(w3=w3, db_writer=global_db_writer, db_reader=global_db_reader, log=log, event_queue=event_queue) + snr.queue_past_events_for_scanning(config.addr_sn_rewards) + event_addresses.add(config.addr_sn_rewards) + for event in snr.get_events(config.addr_sn_rewards): + topic_map[event().topic] = (snr.event_abis[event.name], snr.handle_event) + + rrp = RewardRatePool(w3=w3, db_writer=global_db_writer, log=log, event_queue=event_queue) + rrp.queue_past_events_for_scanning( + config.addr_reward_rate_pool + ) + event_addresses.add(config.addr_reward_rate_pool) + for event in rrp.get_events(config.addr_reward_rate_pool): + topic_map[event().topic] = (rrp.event_abis[event.name], rrp.handle_event) + + osu = Ownable2StepUpgradeable(w3=w3, db_writer=global_db_writer, log=log, event_queue=event_queue) + osu.queue_past_events_for_scanning( + [config.addr_sn_contrib_factory, config.addr_sn_rewards, config.addr_reward_rate_pool] + ) + for event in osu.get_events([config.addr_sn_contrib_factory, config.addr_sn_rewards, config.addr_reward_rate_pool]): + topic_map[event().topic] = (osu.event_abis[event.name], osu._handle_event) + + + pu = PausableUpgradeable(w3=w3, db_writer=global_db_writer, log=log, event_queue=event_queue) + pu.queue_past_events_for_scanning( + [config.addr_sn_contrib_factory, config.addr_sn_rewards] + ) + for event in pu.get_events([config.addr_sn_contrib_factory, config.addr_sn_rewards]): + topic_map[event().topic] = (pu.event_abis[event.name], pu._handle_event) + + ierc = IERC1967(w3=w3, db_writer=global_db_writer, log=log, event_queue=event_queue) + ierc.queue_past_events_for_scanning( + [config.addr_sn_contrib_factory, config.addr_sn_rewards, config.addr_reward_rate_pool] + ) + for event in ierc.get_events([config.addr_sn_contrib_factory, config.addr_sn_rewards, config.addr_reward_rate_pool]): + topic_map[event().topic] = (ierc.event_abis[event.name], ierc._handle_event) + + if config.ws_watch_token_events: + log.warning("Watching all token events, this may greatly increase the rescan time (ws_watch_token_events=True)") + tok = Token(w3=w3, db_writer=global_db_writer, log=log, event_queue=event_queue) + tok.queue_past_events_for_scanning(config.addr_token) + + for event in tok.get_events(config.addr_token): + topic_map[event().topic] = (tok.event_abis[event.name], tok.handle_event) + + for topic in topic_map.keys(): + log.info(f"Added topic: {topic}") + + + +async def get_latency(w3: AsyncWeb3): + """ + Gets the latency of ws requests. In nanoseconds. + """ + start = time.time_ns() + await w3.eth.get_block_number() + return time.time_ns() - start + + +async def handle_logs(config: EventScannerConfig, handler_context: NewHeadsSubscriptionContext, logs, depth): + assert depth < 2 + depth += 1 + deploy_topic = sn_contrib_factory.factory(config.addr_sn_contrib_factory).events["NewServiceNodeContributionContract"]().topic + for event in logs: + for _topic in event.get("topics", []): + topic = _topic.to_0x_hex() + if topic in topic_map: + abi, handler = topic_map[topic] + if event["address"] in event_addresses: + data = get_event_data(async_w3.codec, abi, event) + await handler(data) + + if topic == deploy_topic: + block = event["blockNumber"] + contract_address = data.args.get("contributorContract") + new_logs = [] + for new_event in await handler_context.async_w3.eth.get_logs({ + "fromBlock": block, + "toBlock": block, + "address": contract_address, + }): + new_logs.append(new_event) + await handle_logs(config, handler_context, new_logs, depth) + +next_block = 0 +last_block = 0 + +async def new_heads_handler(config: EventScannerConfig, handler_context: NewHeadsSubscriptionContext): + global next_block, last_block + block = handler_context.result["number"] + if block >= next_block: + logs = [] + cutoff = last_block + config.get_logs_cap + was_cut_off = False + if block > cutoff: + block = cutoff + was_cut_off = True + + block = min(block, last_block + config.get_logs_cap) + for event in await handler_context.async_w3.eth.get_logs({ + "fromBlock": last_block + 1, + "toBlock": block, + "address": list(event_addresses), + }): + logs.append(event) + + await handle_logs(config, handler_context, logs, 0) + last_block = block + next_block = block + (1 if was_cut_off else config.refresh_block_interval) + + + +async def monitor_events(config: EventScannerConfig, w3: AsyncWeb3, run_once_as_script=False): + global last_block, next_block + + # Note: We need the current block just before we subscribe to new heads, this block number is used to fetch all + # past events up to and including this "current block". Once we get this block we immediately subscribe to new + # heads, which will start filling up the websocket queue with all the blocks we might miss while we scan for + # past events. Once past events are scanned for we'll have a large queue of blocks to catch up on, which we will + # start processing in the subscription queue. + current_block = await w3.eth.get_block_number() + await w3.subscription_manager.subscribe( + [ + NewHeadsSubscription( + label="new-heads-mainnet", + handler=partial(new_heads_handler, config) + ) + ] + ) + + log.info(f"Created {len(w3.subscription_manager.subscriptions)} subscriptions") + + global_db_writer.defer_writing_arbitrum_events = True + # sn_contrib_factory.add_existing_contribution_contracts(existing_sn_contract_addresses) + last_block = max(config.contrib_factory_start_block, await event_queue.run(current_block=current_block)) + + global_db_writer.defer_writing_arbitrum_events = False + global_db_writer.write_deferred_arbitrum_events_to_db() + + existing_sn_contract_addresses = global_db_reader.get_arbitrum_event_main_args_by_name( + "NewServiceNodeContributionContract") + for address in existing_sn_contract_addresses: + assert is_checksum_address( + address), f"Invalid existing NewServiceNodeContributionContract contract address: {address}" + event_addresses.add(address) + + sn_contrib_factory.add_existing_contribution_contracts(existing_sn_contract_addresses) + sn_contrib_factory.bootstrap_contribution_contracts() + + global_db_writer.defer_writing_arbitrum_events = True + + last_block = max(config.contrib_factory_start_block, await event_queue.run(current_block=current_block)) + + global_db_writer.defer_writing_arbitrum_events = False + global_db_writer.write_deferred_arbitrum_events_to_db() + + assert len( + event_queue.event_queue) == 0 and len( + global_db_writer.deferred_arbitrum_events) == 0, \ + f"Expected all queues to be empty." \ + f"Deferred DB write events: {len(global_db_writer.deferred_arbitrum_events)}" + + latencies = [] + for _ in range(10): + latencies.append(await get_latency(w3)) + + max_latency_ns = max(latencies) + next_block = last_block + config.refresh_block_interval + + log.info(f"Metrics for ws scanner - latency: {max_latency_ns}, batch size: {config.refresh_block_interval}, last block: {last_block}, next block: {next_block}") + + log.perf.end("startup_till_processing_websocket_subscriptions") + if run_once_as_script: + log.info("run_once_as_script is True, exiting...") + return + else: + await w3.subscription_manager.handle_subscriptions() + + +async def start(config: EventScannerConfig): + log.perf.start("startup_till_processing_websocket_subscriptions") + global global_db_writer, global_db_reader, event_queue + # This loop repeats if the connection is lost, to reestablish the connection: when that happens + # we need to re-subscribe and re-fetch any lost events that might have happened during the + # disconnect (or restart). + log.setLevel(config.log_level) + + validate_log_config(config) + validate_contract_addresses(config) + + if config.log_level_generic is not None: + logging.getLogger(None).setLevel(config.log_level_generic) + + if not is_db_initialized(config.sqlite_db): + log.info(f"Initializing database {config.sqlite_db} with schema {config.sqlite_schema}") + init_db(config.sqlite_db, config.sqlite_schema) + + global_db_writer = DBWriterStaking(db_path=config.sqlite_db, log_level=config.log_level, perf=config.enable_perf) + global_db_reader = DBReaderStaking(db_path=config.sqlite_db, log_level=config.log_level, perf=config.enable_perf) + + if config.db_reset_events_on_startup: + log.warning("Deleting events database on startup (db_reset_events_on_startup=True)") + global_db_writer.delete_all_events() + global_db_writer.write_reset_all_rewards_claim_amounts() + + if config.db_reset_contrib_on_startup: + log.warning("Deleting contrib contracts database on startup (db_reset_contrib_on_startup=True)") + global_db_writer.delete_all_contrib_contracts_and_contributors() + + if config.reset_vesting_contracts_on_startup: + log.warning("Deleting vesting contracts database on startup (reset_vesting_contracts_on_startup=True)") + global_db_writer.delete_all_vesting_contracts() + + log.info("Starting event scanner") + provider = config.ws_providers[0] + async for w3 in AsyncWeb3( + WebSocketProvider(provider, websocket_kwargs={"max_size": config.ws_max_size}, + request_timeout=300, + # one hour of arbitrum blocks in the queue, the high number is required because blocks + # pile up in the queue while rescanning the chain. + subscription_response_queue_size=3600*4) + ): + # TODO: investigate if this properly handles disconnects + await init_global_contracts(w3, config) + await asyncio.create_task(monitor_events(config=config, w3=w3, run_once_as_script=config.run_once_as_script)) + if config.run_once_as_script: + log.info("Exiting websocket disconnection loop as run_once_as_script is True") + break + + +def init_ws_event_scanner(config: EventScannerConfig): + asyncio.run(start(config)) diff --git a/src/web3client/util.py b/src/web3client/util.py new file mode 100644 index 0000000..57f6600 --- /dev/null +++ b/src/web3client/util.py @@ -0,0 +1,2 @@ +def get_time_of_arbitrum_blocks_ms(block_number: int) -> int: + return block_number * 250 \ No newline at end of file diff --git a/testnet.py b/testnet.py deleted file mode 100644 index c5d116b..0000000 --- a/testnet.py +++ /dev/null @@ -1,6 +0,0 @@ -from sent import app, config -import oxenmq - -config.testnet = True - -config.oxend_rpc = 'ipc://oxend/testnet.sock' diff --git a/timer.py b/timer.py deleted file mode 100644 index 2d5f949..0000000 --- a/timer.py +++ /dev/null @@ -1,25 +0,0 @@ -import logging - -try: - import uwsgi # noqa: F401 -except ModuleNotFoundError: - logging.error( - """ -WARNING: Failed to load uwsgidecorators; we probably aren't running under uwsgi. -""" - ) - - class timer: - """Do-nothing stub""" - - def __init__(self, secs, **kwargs): - pass - - def __call__(self, f): - pass - - -else: - import uwsgidecorators - - timer = uwsgidecorators.timer