diff --git a/.github/workflows/create-release.yaml b/.github/workflows/create-release.yaml index 40f0f5e..97998a5 100644 --- a/.github/workflows/create-release.yaml +++ b/.github/workflows/create-release.yaml @@ -2,7 +2,6 @@ name: Create release on: push: - branches: [ main ] tags: - "v*.*.*" pull_request: @@ -18,86 +17,102 @@ jobs: python-version: [ 3.11 ] node-version: [ 18.16.0 ] - solc-version: [ 0.8.19 ] ganache-version: [ 7.8.0 ] - opt-flags: [ "--optimize --optimize-runs 200" ] + solc-version: [ v0.8.20 ] + + env: + SOLC_BIN: ${{ github.workspace }}/build/solc-static-linux + SOLC_FLAGS: >- + --optimize --optimize-runs 200 + --revert-strings strip + --via-ir + --overwrite + --base-path ${{ github.workspace }} + --output-dir ${{ github.workspace }}/build/ + SOLC_VER_CMD: >- + ${{ github.workspace }}/build/solc-static-linux + --version | tail -n 1 | sed -e "s/^Version: //g" + RELE_NOTE: ${{ github.workspace }}/build/release_note.md name: A job to create a release steps: - name: Checkout uses: actions/checkout@v3 - - - name: Installing Node ${{ matrix.node-version }} - uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} + submodules: recursive - name: Installing Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + - name: Installing Python packages + run: | + python3 -m pip install --requirement ${{ github.workspace }}/utils/gas_cost_eval_requirements.txt + + - name: Installing Node ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Installing NPM packages run: | - npm install -g solc@${{ matrix.solc-version }} npm install -g ganache@${{ matrix.ganache-version }} - - name: Installing Python packages + - name: Installing Solc compiler run: | - python3 -m pip install --requirement ${{ github.workspace }}/utils/gas_cost_eval_requirements.txt + mkdir -p ${{ github.workspace }}/build/ + curl -fsSL -o ${SOLC_BIN} \ + https://github.com/ethereum/solidity/releases/download/${{ matrix.solc-version }}/solc-static-linux + chmod +x ${SOLC_BIN} - name: Compiling contracts for PubSub/EventManager.sol run: | - solcjs ${{ matrix.opt-flags }} --bin --include-path node_modules/ --base-path . --output-dir ./build/ ./PubSub/EventManager.sol - solcjs ${{ matrix.opt-flags }} --abi --include-path node_modules/ --base-path . --output-dir ./build/ ./PubSub/EventManager.sol - mv ./build/PubSub_EventManager_sol_EventManager.bin ./build/EventManager.bin - mv ./build/PubSub_EventManager_sol_EventManager.abi ./build/EventManager.abi + ${SOLC_BIN} ${SOLC_FLAGS} --bin ${{ github.workspace }}/PubSub/EventManager.sol + ${SOLC_BIN} ${SOLC_FLAGS} --abi ${{ github.workspace }}/PubSub/EventManager.sol - name: Compiling contracts for PubSub/PubSubService.sol run: | - solcjs ${{ matrix.opt-flags }} --bin --include-path node_modules/ --base-path . --output-dir ./build/ ./PubSub/PubSubService.sol - solcjs ${{ matrix.opt-flags }} --abi --include-path node_modules/ --base-path . --output-dir ./build/ ./PubSub/PubSubService.sol - mv ./build/PubSub_PubSubService_sol_PubSubService.bin ./build/PubSubService.bin - mv ./build/PubSub_PubSubService_sol_PubSubService.abi ./build/PubSubService.abi + ${SOLC_BIN} ${SOLC_FLAGS} --bin ${{ github.workspace }}/PubSub/PubSubService.sol + ${SOLC_BIN} ${SOLC_FLAGS} --abi ${{ github.workspace }}/PubSub/PubSubService.sol - name: Compiling contracts for tests/HelloWorldSubscriber.sol run: | - solcjs ${{ matrix.opt-flags }} --bin --include-path node_modules/ --base-path . --output-dir ./build/ ./tests/HelloWorldSubscriber.sol - solcjs ${{ matrix.opt-flags }} --abi --include-path node_modules/ --base-path . --output-dir ./build/ ./tests/HelloWorldSubscriber.sol - mv ./build/tests_HelloWorldSubscriber_sol_HelloWorldSubscriber.bin ./build/HelloWorldSubscriber.bin - mv ./build/tests_HelloWorldSubscriber_sol_HelloWorldSubscriber.abi ./build/HelloWorldSubscriber.abi + ${SOLC_BIN} ${SOLC_FLAGS} --bin ${{ github.workspace }}/tests/HelloWorldSubscriber.sol + ${SOLC_BIN} ${SOLC_FLAGS} --abi ${{ github.workspace }}/tests/HelloWorldSubscriber.sol - name: Compiling contracts for tests/HelloWorldPublisher.sol run: | - solcjs ${{ matrix.opt-flags }} --bin --include-path node_modules/ --base-path . --output-dir ./build/ ./tests/HelloWorldPublisher.sol - solcjs ${{ matrix.opt-flags }} --abi --include-path node_modules/ --base-path . --output-dir ./build/ ./tests/HelloWorldPublisher.sol - mv ./build/tests_HelloWorldPublisher_sol_HelloWorldPublisher.bin ./build/HelloWorldPublisher.bin - mv ./build/tests_HelloWorldPublisher_sol_HelloWorldPublisher.abi ./build/HelloWorldPublisher.abi + ${SOLC_BIN} ${SOLC_FLAGS} --bin ${{ github.workspace }}/tests/HelloWorldPublisher.sol + ${SOLC_BIN} ${SOLC_FLAGS} --abi ${{ github.workspace }}/tests/HelloWorldPublisher.sol - name: Calculating checksums of the binary + working-directory: ${{ github.workspace }}/build run: | - sha256sum ./build/EventManager.bin >> ./build/checksums.txt - sha256sum ./build/EventManager.abi >> ./build/checksums.txt - sha256sum ./build/PubSubService.bin >> ./build/checksums.txt - sha256sum ./build/PubSubService.abi >> ./build/checksums.txt - sha256sum ./build/HelloWorldPublisher.bin >> ./build/checksums.txt - sha256sum ./build/HelloWorldPublisher.abi >> ./build/checksums.txt - sha256sum ./build/HelloWorldSubscriber.bin >> ./build/checksums.txt - sha256sum ./build/HelloWorldSubscriber.abi >> ./build/checksums.txt + sha256sum solc-static-linux >> checksums.txt + sha256sum EventManager.bin >> checksums.txt + sha256sum EventManager.abi >> checksums.txt + sha256sum PubSubService.bin >> checksums.txt + sha256sum PubSubService.abi >> checksums.txt + sha256sum HelloWorldPublisher.bin >> checksums.txt + sha256sum HelloWorldPublisher.abi >> checksums.txt + sha256sum HelloWorldSubscriber.bin >> checksums.txt + sha256sum HelloWorldSubscriber.abi >> checksums.txt - name: Prepare binaries for gas cost evaluation + working-directory: ${{ github.workspace }}/build run: | - mkdir -p ./build/PubSub - cp ./build/EventManager.bin ./build/PubSub/EventManager.bin - cp ./build/EventManager.abi ./build/PubSub/EventManager.abi - cp ./build/PubSubService.bin ./build/PubSub/PubSubService.bin - cp ./build/PubSubService.abi ./build/PubSub/PubSubService.abi - mkdir -p ./build/tests - cp ./build/HelloWorldPublisher.bin ./build/tests/HelloWorldPublisher.bin - cp ./build/HelloWorldPublisher.abi ./build/tests/HelloWorldPublisher.abi - cp ./build/HelloWorldSubscriber.bin ./build/tests/HelloWorldSubscriber.bin - cp ./build/HelloWorldSubscriber.abi ./build/tests/HelloWorldSubscriber.abi + mkdir -p PubSub + cp EventManager.bin PubSub/EventManager.bin + cp EventManager.abi PubSub/EventManager.abi + cp PubSubService.bin PubSub/PubSubService.bin + cp PubSubService.abi PubSub/PubSubService.abi + mkdir -p tests + cp HelloWorldPublisher.bin tests/HelloWorldPublisher.bin + cp HelloWorldPublisher.abi tests/HelloWorldPublisher.abi + cp HelloWorldSubscriber.bin tests/HelloWorldSubscriber.bin + cp HelloWorldSubscriber.abi tests/HelloWorldSubscriber.abi - name: Run publish gas cost evaluation run: | @@ -107,40 +122,59 @@ jobs: run: | python3 ${{ github.workspace }}/tests/GasCostEvalMultiPubs.py + - name: Run deployment gas cost evaluation + run: | + python3 ${{ github.workspace }}/tests/GasCostEvalDeploy.py + - name: Plot gas cost evaluation result figures run: | python3 ${{ github.workspace }}/tests/GasCostEvalPlot.py - name: Generate release note + working-directory: ${{ github.workspace }}/build run: | - echo "# Release note" >> ./build/release_note.md - echo "## Contracts" >> ./build/release_note.md - echo "- PubSub/EventManager.sol" >> ./build/release_note.md - echo "- PubSub/PubSubService.sol" >> ./build/release_note.md - echo "- tests/HelloWorldPublisher.sol" >> ./build/release_note.md - echo "- tests/HelloWorldSubscriber.sol" >> ./build/release_note.md - echo "" >> ./build/release_note.md - echo "## Build configurations" >> ./build/release_note.md - echo "- OS: \`${{ matrix.os }}\`" >> ./build/release_note.md - echo "- Node version: \`$(node --version)\`" >> ./build/release_note.md - echo "- Solc version: \`$(solcjs --version)\`" >> ./build/release_note.md - echo "- Optimizations: \`${{ matrix.opt-flags }}\`" >> ./build/release_note.md - echo "" >> ./build/release_note.md - echo "## Checksums" >> ./build/release_note.md - echo "\`\`\`" >> ./build/release_note.md - cat ./build/checksums.txt >> ./build/release_note.md - echo "\`\`\`" >> ./build/release_note.md - echo "" >> ./build/release_note.md - echo "## Gas Cost Evaluations" >> ./build/release_note.md - echo "### Gas Cost of Publishing Events" >> ./build/release_note.md - echo "" >> ./build/release_note.md - echo '!'"[publish_gas_cost](/../../blob/assets-gas-eval/assets/${{ github.ref_name }}-publish_gas_cost.svg)" >> ./build/release_note.md - echo "" >> ./build/release_note.md - echo "### Gas cost of Subscribing to Publishers" >> ./build/release_note.md - echo "" >> ./build/release_note.md - echo '!'"[subscribe_gas_cost](/../../blob/assets-gas-eval/assets/${{ github.ref_name }}-subscribe_gas_cost.svg)" >> ./build/release_note.md - echo "" >> ./build/release_note.md - echo "" >> ./build/release_note.md + echo "# Release note" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo "## Contracts" >> ${RELE_NOTE} + echo "- PubSub/EventManager.sol" >> ${RELE_NOTE} + echo "- PubSub/PubSubService.sol" >> ${RELE_NOTE} + echo "- tests/HelloWorldPublisher.sol" >> ${RELE_NOTE} + echo "- tests/HelloWorldSubscriber.sol" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo "## Build configurations" >> ${RELE_NOTE} + echo "- OS: \`${{ matrix.os }}\`" >> ${RELE_NOTE} + echo "- Solc version: \`$(bash -c "${SOLC_VER_CMD}")\`" >> ${RELE_NOTE} + echo "- Compiler Flags: \`${SOLC_FLAGS}\`" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo "## Checksums" >> ${RELE_NOTE} + echo "\`\`\`" >> ${RELE_NOTE} + cat checksums.txt >> ${RELE_NOTE} + echo "\`\`\`" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo "## Gas Cost Evaluations" >> ${RELE_NOTE} + echo "### Gas Cost of Publishing Events" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo '!'"[publish_gas_cost](/../../blob/assets-gas-eval/assets/${{ github.ref_name }}-publish_gas_cost.svg)" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo "### Gas Cost of Publisher Registeration" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo '!'"[register_gas_cost](/../../blob/assets-gas-eval/assets/${{ github.ref_name }}-register_gas_cost.svg)" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo "### Gas Cost of Subscribing to Publishers" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo '!'"[subscribe_gas_cost](/../../blob/assets-gas-eval/assets/${{ github.ref_name }}-subscribe_gas_cost.svg)" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo "### Gas Cost of Contract Deployment" >> ${RELE_NOTE} + echo "\`\`\`json" >> ${RELE_NOTE} + cat deploy_gas_cost.json >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo "\`\`\`" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo "### Gas Cost Summary" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo '!'"[gas_cost](/../../blob/assets-gas-eval/assets/${{ github.ref_name }}-gas_cost.svg)" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} + echo "" >> ${RELE_NOTE} - name: Echo release note run: | @@ -165,6 +199,9 @@ jobs: ${{ github.workspace }}/build/publish_gas_cost.json ${{ github.workspace }}/build/subscribe_gas_cost.svg ${{ github.workspace }}/build/subscribe_gas_cost.json + ${{ github.workspace }}/build/register_gas_cost.svg + ${{ github.workspace }}/build/register_gas_cost.json + ${{ github.workspace }}/build/deploy_gas_cost.json ${{ github.workspace }}/build/gas_cost.svg - name: Store evaluation figures to assets branch @@ -182,6 +219,11 @@ jobs: --path "assets/${{ github.ref_name }}-subscribe_gas_cost.svg" \ --commit-msg "Uploaded file assets/${{ github.ref_name }}-subscribe_gas_cost.svg" \ --file "${{ github.workspace }}/build/subscribe_gas_cost.svg" + python3 ${{ github.workspace }}/utils/GitHubCreateFile.py \ + --branch assets-gas-eval \ + --path "assets/${{ github.ref_name }}-register_gas_cost.svg" \ + --commit-msg "Uploaded file assets/${{ github.ref_name }}-register_gas_cost.svg" \ + --file "${{ github.workspace }}/build/register_gas_cost.svg" python3 ${{ github.workspace }}/utils/GitHubCreateFile.py \ --branch assets-gas-eval \ --path "assets/${{ github.ref_name }}-gas_cost.svg" \ @@ -206,3 +248,7 @@ jobs: ${{ github.workspace }}/build/publish_gas_cost.json ${{ github.workspace }}/build/subscribe_gas_cost.svg ${{ github.workspace }}/build/subscribe_gas_cost.json + ${{ github.workspace }}/build/register_gas_cost.svg + ${{ github.workspace }}/build/register_gas_cost.json + ${{ github.workspace }}/build/deploy_gas_cost.json + ${{ github.workspace }}/build/gas_cost.svg diff --git a/.github/workflows/pubsub-solidity-unittesting.yaml b/.github/workflows/pubsub-solidity-unittesting.yaml index fed5a08..80c3e12 100644 --- a/.github/workflows/pubsub-solidity-unittesting.yaml +++ b/.github/workflows/pubsub-solidity-unittesting.yaml @@ -16,15 +16,24 @@ on: jobs: run_sol_contracts_job: - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-22.04 ] + solc-version: [ 0.8.20 ] + chain-fork: [ shanghai ] + opt-runs: [ 200 ] + name: A job to run solidity unit tests on github actions CI steps: - name: Checkout uses: actions/checkout@v3 + - name: Run Solidity Unit Testing Action uses: EthereumRemix/sol-test@v1.1 with: test-path: 'tests/PubSub' - compiler-version: '0.8.19' + compiler-version: ${{ matrix.solc-version }} optimize: true - optimizer-runs: 200 + optimizer-runs: ${{ matrix.opt-runs }} + hard-fork: ${{ matrix.chain-fork }} diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..362210f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "utils/PyEthHelper"] + path = utils/PyEthHelper + url = https://github.com/lsd-ucsc/PyEthHelper.git + branch = main diff --git a/Makefile b/Makefile index 1a13dd5..c3945f4 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,24 @@ -NODEENV_CONFIG := ./utils/nodeenv.ini -NODEENV_REQ := ./utils/nodeenv-requirements.txt MODULES := \ PubSub \ tests +SOLC_VERSION := v0.8.20 +MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIR := $(dir $(MKFILE_PATH)) +SOLC_BIN := $(CURRENT_DIR)/build/solc-static-linux -all: build/nodeenv.state $(MODULES) +all: $(SOLC_BIN) $(MODULES) -build/nodeenv.state: $(NODEENV_CONFIG) $(NODEENV_REQ) - nodeenv --config=$(NODEENV_CONFIG) --requirements=$(NODEENV_REQ) build/nodeenv - touch $@ +$(SOLC_BIN): + mkdir -p $(dir $(SOLC_BIN)) && \ + curl -fsSL -o $(SOLC_BIN) \ + https://github.com/ethereum/solidity/releases/download/$(SOLC_VERSION)/solc-static-linux \ + && \ + chmod +x $(SOLC_BIN) + + +solc_bin: $(SOLC_BIN) $(MODULES): @@ -22,7 +30,7 @@ $(addprefix clean_,$(MODULES)): clean: $(addprefix clean_,$(MODULES)) - rm -rf build/nodeenv build/nodeenv.state + rm -rf $(SOLC_BIN) -.PHONY: all clean $(MODULES) $(addprefix clean_,$(MODULES)) +.PHONY: all clean solc_bin $(MODULES) $(addprefix clean_,$(MODULES)) diff --git a/PubSub/EventManager.sol b/PubSub/EventManager.sol index 2a8923e..df2e416 100644 --- a/PubSub/EventManager.sol +++ b/PubSub/EventManager.sol @@ -75,29 +75,11 @@ contract EventManager { }); } - /** - * Notify all subscribers - * @param data The data to send to the subscribers - * @dev The immediate caller must be a publisher - */ - function notifySubscribers(bytes memory data) external { - // 1. Make sure the entrance lock is free - require( - !m_entranceLock, - "Entrance lock is engaged" - ); - m_entranceLock = true; - - // 2. Make sure the caller is a publisher - require( - m_publisherMap[msg.sender], - "Only registered publisher can notify" - ); - - // 3. Get gas price (Wei per gas unit) that we want to reimburse + function notifyOnChainSubscribers(bytes memory data) private { + // 1. Get gas price (Wei per gas unit) that we want to reimburse uint256 gasPriceWei = tx.gasprice; - // 4. maintain running track of how much to compensate tx.origin + // 2. maintain running track of how much to compensate tx.origin uint256 compensateWei = 0; uint256 numSubscribers = m_subscriberAddrs.length; @@ -107,7 +89,7 @@ contract EventManager { uint256 costWei = 0; uint256 limitGas = 0; - // 5. Make sure the specified gas limit is enough for all subscribers's + // 3. Make sure the specified gas limit is enough for all subscribers's // max gas limit require( gasleft() >= ((m_perSubLimitGas * numSubscribers) + @@ -116,7 +98,7 @@ contract EventManager { ); uint fairLimitGas = (gasleft() - FINISHING_COST_GAS) / numSubscribers; - // 6. Notify all subscribers and reimburse the sender for the gas used + // 4. Notify all subscribers and reimburse the sender for the gas used for (uint256 i = 0; i < numSubscribers; i++) { address subscriberAddr = m_subscriberAddrs[i]; // get a reference to the mapped subscriber @@ -155,11 +137,36 @@ contract EventManager { // reimburse the user who invoked this entire transaction payable(tx.origin).transfer(compensateWei); + } + + /** + * Notify all subscribers + * @param data The data to send to the subscribers + * @dev The immediate caller must be a publisher + */ + function notifySubscribers(bytes memory data) external { + // 1. Make sure the entrance lock is free + require( + !m_entranceLock, + "Entrance lock is engaged" + ); + m_entranceLock = true; + + // 2. Make sure the caller is a publisher + require( + m_publisherMap[msg.sender], + "Only registered publisher can notify" + ); + + // 3. notify all on-chain subscribers if there is any + if (m_subscriberAddrs.length > 0) { + notifyOnChainSubscribers(data); + } - // 5. Release the entrance lock + // 4. Release the entrance lock m_entranceLock = false; - // 4. emit the event for off-chain subscribers + // 5. emit the event for off-chain subscribers emit NotifySubscribers(data); } diff --git a/PubSub/Makefile b/PubSub/Makefile index 98b120a..b253d76 100644 --- a/PubSub/Makefile +++ b/PubSub/Makefile @@ -3,61 +3,61 @@ CONTRACTS := \ EventManager \ PubSubService +MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIR := $(dir $(MKFILE_PATH)) +ROOT_DIR := $(CURRENT_DIR)/.. +BUILD_DIR := $(ROOT_DIR)/build + +SOLC_BIN := $(BUILD_DIR)/solc-static-linux +OPTIMIZE_RUN := 200 +SOLC_FLAGS := --optimize --optimize-runs $(OPTIMIZE_RUN) \ + --revert-strings strip \ + --via-ir \ + --overwrite \ + --base-path $(ROOT_DIR) \ + --output-dir $(BUILD_DIR)/$(MODULE_NAME)/all/ + CHECKSUM_BIN := openssl sha256 -all: $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .abi,$(CONTRACTS))) \ - $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .bin,$(CONTRACTS))) \ - $(addprefix ../build/$(MODULE_NAME)/,checksums.txt) +all: $(CONTRACTS) checksums -../build/nodeenv.state: - $(MAKE) -C .. build/nodeenv.state +$(SOLC_BIN): + $(MAKE) -C .. solc_bin -../build/$(MODULE_NAME)/%.bin: %.sol ../build/nodeenv.state +$(BUILD_DIR)/$(MODULE_NAME)/%.bin: %.sol $(SOLC_BIN) ( \ - . ../build/nodeenv/bin/activate && \ - solcjs --optimize --optimize-runs 200 \ - --bin \ - --include-path node_modules/ --base-path .. \ - --output-dir ../build/$(MODULE_NAME)/ \ - $< && \ - mv ../build/$(MODULE_NAME)/$(MODULE_NAME)_$(basename $<)_sol_$(basename $<).bin \ - ../build/$(MODULE_NAME)/$(basename $<).bin && \ - rm -f ../build/$(MODULE_NAME)/*_sol_*.bin \ + $(SOLC_BIN) --bin $(SOLC_FLAGS) $< && \ + cp $(BUILD_DIR)/$(MODULE_NAME)/all/$(basename $<).bin \ + $(BUILD_DIR)/$(MODULE_NAME)/$(basename $<).bin \ ) -../build/$(MODULE_NAME)/%.abi: %.sol ../build/nodeenv.state +$(BUILD_DIR)/$(MODULE_NAME)/%.abi: %.sol $(SOLC_BIN) ( \ - . ../build/nodeenv/bin/activate && \ - solcjs --optimize --optimize-runs 200 \ - --abi \ - --include-path node_modules/ --base-path .. \ - --output-dir ../build/$(MODULE_NAME)/ \ - $< && \ - mv ../build/$(MODULE_NAME)/$(MODULE_NAME)_$(basename $<)_sol_$(basename $<).abi \ - ../build/$(MODULE_NAME)/$(basename $<).abi && \ - rm -f ../build/$(MODULE_NAME)/*_sol_*.abi \ + $(SOLC_BIN) --abi $(SOLC_FLAGS) $< && \ + cp $(BUILD_DIR)/$(MODULE_NAME)/all/$(basename $<).abi \ + $(BUILD_DIR)/$(MODULE_NAME)/$(basename $<).abi \ ) -../build/$(MODULE_NAME)/checksums.txt: $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .abi,$(CONTRACTS))) \ - $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .bin,$(CONTRACTS))) +$(CONTRACTS): %: $(BUILD_DIR)/$(MODULE_NAME)/%.abi $(BUILD_DIR)/$(MODULE_NAME)/%.bin + + +$(BUILD_DIR)/$(MODULE_NAME)/checksums.txt: $(CONTRACTS) ( \ - cd ../build/$(MODULE_NAME); \ + cd $(BUILD_DIR)/$(MODULE_NAME); \ $(CHECKSUM_BIN) $(addsuffix .abi,$(CONTRACTS)) $(addsuffix .bin,$(CONTRACTS)) > checksums.txt; \ ) -checksums: ../build/$(MODULE_NAME)/checksums.txt +checksums: $(BUILD_DIR)/$(MODULE_NAME)/checksums.txt clean: - rm -f $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .abi,$(CONTRACTS))) \ - $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .bin,$(CONTRACTS))) \ - $(addprefix ../build/$(MODULE_NAME)/,checksums.txt) + rm -rf $(BUILD_DIR)/$(MODULE_NAME)/ -.PHONY: all clean checksums +.PHONY: all clean checksums $(CONTRACTS) diff --git a/tests/GasCostEvalDeploy.py b/tests/GasCostEvalDeploy.py new file mode 100644 index 0000000..2df8193 --- /dev/null +++ b/tests/GasCostEvalDeploy.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +### +# Copyright (c) 2023 Roy Shadmon, Haofan Zheng +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +### + + +import json +import os +import signal +import subprocess +import sys +import time + +from web3 import Web3 + + +BASE_DIR_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BUILD_DIR_PATH = os.path.join(BASE_DIR_PATH, 'build') +UTILS_DIR_PATH = os.path.join(BASE_DIR_PATH, 'utils') +PYHELPER_DIR = os.path.join(UTILS_DIR_PATH, 'PyEthHelper') +PROJECT_CONFIG_PATH = os.path.join(UTILS_DIR_PATH, 'project_conf.json') +CHECKSUM_KEYS_PATH = os.path.join(BUILD_DIR_PATH, 'ganache_keys_checksum.json') +GANACHE_KEYS_PATH = os.path.join(BUILD_DIR_PATH, 'ganache_keys.json') +GANACHE_PORT = 7545 +NUM_OF_ACCOUNTS = 100 +GANACHE_NET_ID = 1337 + + +sys.path.append(PYHELPER_DIR) +from PyEthHelper import EthContractHelper +from PyEthHelper import GanacheAccounts + + +def StartGanache() -> subprocess.Popen: + cmd = [ + 'ganache-cli', + '-p', str(GANACHE_PORT), + '-d', + '-a', str(NUM_OF_ACCOUNTS), + '--network-id', str(GANACHE_NET_ID), + '--chain.hardfork', 'shanghai', + '--wallet.accountKeysPath', str(GANACHE_KEYS_PATH), + ] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + return proc + + +def RunTests() -> dict: + # connect to ganache + ganacheUrl = 'http://localhost:{}'.format(GANACHE_PORT) + w3 = Web3(Web3.HTTPProvider(ganacheUrl)) + while not w3.is_connected(): + print('Attempting to connect to ganache...') + time.sleep(1) + print('Connected to ganache') + + deployCosts = {} + + # checksum keys + GanacheAccounts.ChecksumGanacheKeysFile( + CHECKSUM_KEYS_PATH, + GANACHE_KEYS_PATH + ) + + # setup account + privKey = EthContractHelper.SetupSendingAccount( + w3=w3, + account=0, + keyJson=CHECKSUM_KEYS_PATH + ) + + # deploy PubSub contract + print('Deploying PubSub contract...') + pubSubContract = EthContractHelper.LoadContract( + w3=w3, + projConf=PROJECT_CONFIG_PATH, + contractName='PubSubService', + release=None, # use locally built contract + address=None, # deploy new contract + ) + pubSubReceipt = EthContractHelper.DeployContract( + w3=w3, + contract=pubSubContract, + arguments=[ ], + privKey=privKey, + gas=None, # let web3 estimate + value=0, + confirmPrompt=False # don't prompt for confirmation + ) + deployCosts['deployPubSub'] = pubSubReceipt.gasUsed + pubSubAddr = pubSubReceipt.contractAddress + print('PubSub contract deployed at {}'.format(pubSubAddr)) + + return deployCosts + + +def StopGanache(ganacheProc: subprocess.Popen) -> None: + print('Shutting down ganache (it may take ~15 seconds)...') + waitEnd = time.time() + 20 + ganacheProc.terminate() + while ganacheProc.poll() is None: + try: + if time.time() > waitEnd: + print('Force to shut down ganache') + ganacheProc.kill() + else: + print('Still waiting for ganache to shut down...') + ganacheProc.send_signal(signal.SIGINT) + ganacheProc.wait(timeout=2) + except subprocess.TimeoutExpired: + continue + print('Ganache has been shut down') + + +def main(): + ganacheProc = StartGanache() + + try: + deployCosts = RunTests() + + # save results + outputFile = os.path.join(BUILD_DIR_PATH, 'deploy_gas_cost.json') + with open(outputFile, 'w') as f: + json.dump(deployCosts, f, indent='\t') + + finally: + # finish and exit + StopGanache(ganacheProc) + +if __name__ == "__main__": + main() diff --git a/tests/GasCostEvalMultiPubs.py b/tests/GasCostEvalMultiPubs.py index 72fdef0..914fe71 100644 --- a/tests/GasCostEvalMultiPubs.py +++ b/tests/GasCostEvalMultiPubs.py @@ -23,14 +23,18 @@ BASE_DIR_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BUILD_DIR_PATH = os.path.join(BASE_DIR_PATH, 'build') UTILS_DIR_PATH = os.path.join(BASE_DIR_PATH, 'utils') +PYHELPER_DIR = os.path.join(UTILS_DIR_PATH, 'PyEthHelper') PROJECT_CONFIG_PATH = os.path.join(UTILS_DIR_PATH, 'project_conf.json') -CHECKSUM_KEYS_PATH = os.path.join(UTILS_DIR_PATH, 'ganache_keys_checksum.json') -GANACHE_KEYS_PATH = os.path.join(UTILS_DIR_PATH, 'ganache_keys.json') +CHECKSUM_KEYS_PATH = os.path.join(BUILD_DIR_PATH, 'ganache_keys_checksum.json') +GANACHE_KEYS_PATH = os.path.join(BUILD_DIR_PATH, 'ganache_keys.json') GANACHE_PORT = 7545 NUM_OF_ACCOUNTS = 100 +GANACHE_NET_ID = 1337 -sys.path.append(UTILS_DIR_PATH) -import EthContractHelper + +sys.path.append(PYHELPER_DIR) +from PyEthHelper import EthContractHelper +from PyEthHelper import GanacheAccounts def StartGanache() -> subprocess.Popen: @@ -39,8 +43,9 @@ def StartGanache() -> subprocess.Popen: '-p', str(GANACHE_PORT), '-d', '-a', str(NUM_OF_ACCOUNTS), - '--network-id', '1337', - '--wallet.accountKeysPath', GANACHE_KEYS_PATH, + '--network-id', str(GANACHE_NET_ID), + '--chain.hardfork', 'shanghai', + '--wallet.accountKeysPath', str(GANACHE_KEYS_PATH), ] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -60,7 +65,7 @@ def SelectRandomAccount( ) -def RunTests() -> List[Tuple[int, int]]: +def RunTests() -> Tuple[List[Tuple[int, int]], List[Tuple[int, int]]]: maxNumPublishers = 20 # connect to ganache @@ -71,10 +76,17 @@ def RunTests() -> List[Tuple[int, int]]: time.sleep(1) print('Connected to ganache') + # checksum keys + GanacheAccounts.ChecksumGanacheKeysFile( + CHECKSUM_KEYS_PATH, + GANACHE_KEYS_PATH + ) + # setup account privKey = SelectRandomAccount(w3) + registerCost = [] subscribeCost = [] @@ -114,6 +126,7 @@ def RunTests() -> List[Tuple[int, int]]: ) publishers = [] + regCosts = [] print('Deploying {} publishers...'.format(numPublishers)) for pubIndex in range(0, numPublishers): # choose a random account to deploy from @@ -151,7 +164,7 @@ def RunTests() -> List[Tuple[int, int]]: # register publisher # print('Registering publisher...') - EthContractHelper.CallContractFunc( + regTxReceipt = EthContractHelper.CallContractFunc( w3=w3, contract=publisherContract, funcName='register', @@ -161,10 +174,18 @@ def RunTests() -> List[Tuple[int, int]]: value=0, confirmPrompt=False # don't prompt for confirmation ) + regCosts.append(regTxReceipt.gasUsed) + print('Register gas used: {}'.format(regTxReceipt.gasUsed)) publishers.append(publisherContract) - costs = [] + # record register gas used + registerCost.append(( + numPublishers, + sum(regCosts) / len(regCosts), # average gas cost + )) + + subsCosts = [] for publisherContract in publishers: publisherAddr = publisherContract.address @@ -242,16 +263,16 @@ def RunTests() -> List[Tuple[int, int]]: publisherAddr )) - costs.append(subTxReceipt.gasUsed) + subsCosts.append(subTxReceipt.gasUsed) print('Gas used: {}'.format(subTxReceipt.gasUsed)) - # record gas used + # record subscribe gas used subscribeCost.append(( numPublishers, - sum(costs) / len(costs), # average gas cost + sum(subsCosts) / len(subsCosts), # average gas cost )) - return subscribeCost + return registerCost, subscribeCost def StopGanache(ganacheProc: subprocess.Popen) -> None: @@ -276,21 +297,26 @@ def main(): ganacheProc = StartGanache() try: - gasResults = [] + regGasResults = [] + subsGasResults = [] for _ in range(3): - subscribeCost = RunTests() + registerCost, subscribeCost = RunTests() - print('Subscribe gas cost results:') - for cost in subscribeCost: - print('{:03} publishers: {:010.2f} gas'.format(cost[0], cost[1])) + # print('Subscribe gas cost results:') + # for cost in subscribeCost: + # print('{:03} publishers: {:010.2f} gas'.format(cost[0], cost[1])) - gasResults.append(subscribeCost) + regGasResults.append(registerCost) + subsGasResults.append(subscribeCost) # save results outputFile = os.path.join(BUILD_DIR_PATH, 'subscribe_gas_cost.json') with open(outputFile, 'w') as f: - json.dump(gasResults, f, indent='\t') + json.dump(subsGasResults, f, indent='\t') + outputFile = os.path.join(BUILD_DIR_PATH, 'register_gas_cost.json') + with open(outputFile, 'w') as f: + json.dump(regGasResults, f, indent='\t') finally: # finish and exit diff --git a/tests/GasCostEvalMultiSubs.py b/tests/GasCostEvalMultiSubs.py index 2bf0b97..7a51450 100644 --- a/tests/GasCostEvalMultiSubs.py +++ b/tests/GasCostEvalMultiSubs.py @@ -23,14 +23,18 @@ BASE_DIR_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BUILD_DIR_PATH = os.path.join(BASE_DIR_PATH, 'build') UTILS_DIR_PATH = os.path.join(BASE_DIR_PATH, 'utils') +PYHELPER_DIR = os.path.join(UTILS_DIR_PATH, 'PyEthHelper') PROJECT_CONFIG_PATH = os.path.join(UTILS_DIR_PATH, 'project_conf.json') -CHECKSUM_KEYS_PATH = os.path.join(UTILS_DIR_PATH, 'ganache_keys_checksum.json') -GANACHE_KEYS_PATH = os.path.join(UTILS_DIR_PATH, 'ganache_keys.json') +CHECKSUM_KEYS_PATH = os.path.join(BUILD_DIR_PATH, 'ganache_keys_checksum.json') +GANACHE_KEYS_PATH = os.path.join(BUILD_DIR_PATH, 'ganache_keys.json') GANACHE_PORT = 7545 NUM_OF_ACCOUNTS = 100 +GANACHE_NET_ID = 1337 -sys.path.append(UTILS_DIR_PATH) -import EthContractHelper + +sys.path.append(PYHELPER_DIR) +from PyEthHelper import EthContractHelper +from PyEthHelper import GanacheAccounts def StartGanache() -> subprocess.Popen: @@ -39,8 +43,9 @@ def StartGanache() -> subprocess.Popen: '-p', str(GANACHE_PORT), '-d', '-a', str(NUM_OF_ACCOUNTS), - '--network-id', '1337', - '--wallet.accountKeysPath', GANACHE_KEYS_PATH, + '--network-id', str(GANACHE_NET_ID), + '--chain.hardfork', 'shanghai', + '--wallet.accountKeysPath', str(GANACHE_KEYS_PATH), ] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -71,6 +76,12 @@ def RunTests() -> List[Tuple[int, int]]: time.sleep(1) print('Connected to ganache') + # checksum keys + GanacheAccounts.ChecksumGanacheKeysFile( + CHECKSUM_KEYS_PATH, + GANACHE_KEYS_PATH + ) + # setup account privKey = SelectRandomAccount(w3) diff --git a/tests/GasCostEvalPlot.py b/tests/GasCostEvalPlot.py index 2b29e4d..1f9bf79 100644 --- a/tests/GasCostEvalPlot.py +++ b/tests/GasCostEvalPlot.py @@ -21,75 +21,12 @@ BUILD_DIR_PATH = os.path.join(BASE_DIR_PATH, 'build') -# def DrawGraph( -# dest: os.PathLike, -# data: List[Tuple[int, int]], -# scaleBy: int, -# title: str, -# xlabel: str = 'Number of subscribers', -# ylabel: str = 'Gas cost', -# ) -> None: - -# scale = np.power(10, scaleBy) -# plt.plot( -# np.arange(1, len(data) + 1), -# np.array([ cost for _, cost in data ]) / scale, -# ) -# plt.xticks(np.arange(1, len(data) + 1)) -# plt.title(title) -# plt.xlabel(xlabel) -# plt.ylabel(ylabel + f' (1e{scaleBy})') -# plt.savefig(dest + '.svg', format='svg') -# plt.savefig(dest + '.pdf', format='pdf') - -# # clear plot -# plt.clf() - - -# def DrawGraph( -# dest: os.PathLike, -# data: List[Tuple[int, int]], -# scaleBy: int, -# title: str, -# xlabel: str = 'Number of subscribers', -# ylabel: str = 'Gas cost', -# ) -> None: - -# scale = np.power(10, scaleBy) -# axes = plt.subplot() -# axes.plot( -# np.arange(1, len(data) + 1), -# np.array([ cost for _, cost in data ]) / scale, -# ) - -# # set y-axis limits -# dataAvg = sum([ cost for _, cost in data ]) -# dataAvg = dataAvg / len(data) -# ymax = (dataAvg + (dataAvg * 0.0001)) / scale -# ymin = (dataAvg - (dataAvg * 0.0001)) / scale -# axes.set_ylim([ymin, ymax]) - -# # avoid scientific notation -# current_values = axes.get_yticks() -# axes.set_yticklabels( -# ['{:.04f}'.format(x) for x in current_values] -# ) - -# plt.xticks(np.arange(1, len(data) + 1)) -# plt.title(title) -# plt.xlabel(xlabel) -# plt.ylabel(ylabel + f' (1e{scaleBy})') -# plt.savefig(dest + '.svg', format='svg') -# plt.savefig(dest + '.pdf', format='pdf') - -# # clear plot -# plt.clf() - ErrorBarData = List[int] SingleData = int DataPoint = Tuple[int, Union[SingleData, ErrorBarData]] DataPoints = List[DataPoint] + # available markers: https://plotly.com/python/marker-style/ # circle, square, diamond, cross, x, triangle, pentagon, hexagram, star, diamond, hourglass, bowtie, asterisk, hash, y # available colors: https://plotly.com/python/discrete-color/ @@ -181,6 +118,7 @@ def SaveFigure( outName: str, ) -> None: fig.write_image(outName + '.svg') + # fig.write_image(outName + '.png') fig.write_image(outName + '.pdf') # mitigation for issue https://github.com/plotly/plotly.py/issues/3469 @@ -244,6 +182,7 @@ def ReadResults( def main() -> None: + #===== Publish Gas Cost =====# pubGasCostRes = ReadResults( os.path.join(BUILD_DIR_PATH, 'publish_gas_cost.json') ) @@ -257,6 +196,7 @@ def main() -> None: outName=os.path.join(BUILD_DIR_PATH, 'publish_gas_cost'), ) + #===== Subscribe Gas Cost =====# subGasCostRes = ReadResults( os.path.join(BUILD_DIR_PATH, 'subscribe_gas_cost.json') ) @@ -279,10 +219,34 @@ def main() -> None: outName=os.path.join(BUILD_DIR_PATH, 'subscribe_gas_cost'), ) + #===== Register Gas Cost =====# + regGasCostRes = ReadResults( + os.path.join(BUILD_DIR_PATH, 'register_gas_cost.json') + ) + + fig3 = GenerateFigure( + inData=[ regGasCostRes ], + dataNames=[ 'Register Costs' ], + title='Register Gas Cost', + xLabel='Number of Publishers', + yLabel='Amount of Gas Units', + ) + fig3YMax = max([ y[2] for _, y in regGasCostRes ]) + fig3YMin = min([ y[0] for _, y in regGasCostRes ]) + fig3YMid = (fig3YMax + fig3YMin) / 2 + fig3YMax += fig3YMid * 0.00005 + fig3YMin -= fig3YMid * 0.00005 + fig3.update_yaxes(range=[fig3YMin, fig3YMax]) + SaveFigure( + fig=fig3, + outName=os.path.join(BUILD_DIR_PATH, 'register_gas_cost'), + ) + + #===== Summary Graph =====# PlotGraph( - inData=[ pubGasCostRes, subGasCostRes, ], - dataNames=[ 'Publish Costs', 'Subscribe Costs', ], - title='Publish & Subscribe Gas Cost', + inData=[ pubGasCostRes, subGasCostRes, regGasCostRes, ], + dataNames=[ 'Publish Costs', 'Subscribe Costs', 'Register Costs', ], + title='Pub-Sub Service Gas Costs', xLabel='Number of Subscribers/Publishers', yLabel='Amount of Gas Units', outName=os.path.join(BUILD_DIR_PATH, 'gas_cost'), diff --git a/tests/Makefile b/tests/Makefile index 1fc8a5c..315f3ec 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -3,61 +3,61 @@ CONTRACTS := \ HelloWorldPublisher \ HelloWorldSubscriber +MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIR := $(dir $(MKFILE_PATH)) +ROOT_DIR := $(CURRENT_DIR)/.. +BUILD_DIR := $(ROOT_DIR)/build + +SOLC_BIN := $(BUILD_DIR)/solc-static-linux +OPTIMIZE_RUN := 200 +SOLC_FLAGS := --optimize --optimize-runs $(OPTIMIZE_RUN) \ + --revert-strings strip \ + --via-ir \ + --overwrite \ + --base-path $(ROOT_DIR) \ + --output-dir $(BUILD_DIR)/$(MODULE_NAME)/all/ + CHECKSUM_BIN := openssl sha256 -all: $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .abi,$(CONTRACTS))) \ - $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .bin,$(CONTRACTS))) \ - $(addprefix ../build/$(MODULE_NAME)/,checksums.txt) +all: $(CONTRACTS) checksums -../build/nodeenv.state: - $(MAKE) -C .. build/nodeenv.state +$(SOLC_BIN): + $(MAKE) -C .. solc_bin -../build/$(MODULE_NAME)/%.bin: %.sol ../build/nodeenv.state +$(BUILD_DIR)/$(MODULE_NAME)/%.bin: %.sol $(SOLC_BIN) ( \ - . ../build/nodeenv/bin/activate && \ - solcjs --optimize --optimize-runs 200 \ - --bin \ - --include-path node_modules/ --base-path .. \ - --output-dir ../build/$(MODULE_NAME)/ \ - $< && \ - mv ../build/$(MODULE_NAME)/$(MODULE_NAME)_$(basename $<)_sol_$(basename $<).bin \ - ../build/$(MODULE_NAME)/$(basename $<).bin && \ - rm -f ../build/$(MODULE_NAME)/*_sol_*.bin \ + $(SOLC_BIN) --bin $(SOLC_FLAGS) $< && \ + cp $(BUILD_DIR)/$(MODULE_NAME)/all/$(basename $<).bin \ + $(BUILD_DIR)/$(MODULE_NAME)/$(basename $<).bin \ ) -../build/$(MODULE_NAME)/%.abi: %.sol ../build/nodeenv.state +$(BUILD_DIR)/$(MODULE_NAME)/%.abi: %.sol $(SOLC_BIN) ( \ - . ../build/nodeenv/bin/activate && \ - solcjs --optimize --optimize-runs 200 \ - --abi \ - --include-path node_modules/ --base-path .. \ - --output-dir ../build/$(MODULE_NAME)/ \ - $< && \ - mv ../build/$(MODULE_NAME)/$(MODULE_NAME)_$(basename $<)_sol_$(basename $<).abi \ - ../build/$(MODULE_NAME)/$(basename $<).abi && \ - rm -f ../build/$(MODULE_NAME)/*_sol_*.abi \ + $(SOLC_BIN) --abi $(SOLC_FLAGS) $< && \ + cp $(BUILD_DIR)/$(MODULE_NAME)/all/$(basename $<).abi \ + $(BUILD_DIR)/$(MODULE_NAME)/$(basename $<).abi \ ) -../build/$(MODULE_NAME)/checksums.txt: $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .abi,$(CONTRACTS))) \ - $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .bin,$(CONTRACTS))) +$(CONTRACTS): %: $(BUILD_DIR)/$(MODULE_NAME)/%.abi $(BUILD_DIR)/$(MODULE_NAME)/%.bin + + +$(BUILD_DIR)/$(MODULE_NAME)/checksums.txt: $(CONTRACTS) ( \ - cd ../build/$(MODULE_NAME); \ + cd $(BUILD_DIR)/$(MODULE_NAME); \ $(CHECKSUM_BIN) $(addsuffix .abi,$(CONTRACTS)) $(addsuffix .bin,$(CONTRACTS)) > checksums.txt; \ ) -checksums: ../build/$(MODULE_NAME)/checksums.txt +checksums: $(BUILD_DIR)/$(MODULE_NAME)/checksums.txt clean: - rm -f $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .abi,$(CONTRACTS))) \ - $(addprefix ../build/$(MODULE_NAME)/,$(addsuffix .bin,$(CONTRACTS))) \ - $(addprefix ../build/$(MODULE_NAME)/,checksums.txt) + rm -rf $(BUILD_DIR)/$(MODULE_NAME)/ -.PHONY: all clean checksums +.PHONY: all clean checksums $(CONTRACTS) diff --git a/utils/EthContractHelper.py b/utils/EthContractHelper.py deleted file mode 100644 index 4d50fa3..0000000 --- a/utils/EthContractHelper.py +++ /dev/null @@ -1,586 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding:utf-8 -*- -### -# Copyright (c) 2023 Haofan Zheng -# Use of this source code is governed by an MIT-style -# license that can be found in the LICENSE file or at -# https://opensource.org/licenses/MIT. -### - - -import argparse -import json -import logging -import os -import urllib.request -import web3 - -from eth_account.datastructures import SignedTransaction -from typing import Any, Dict, List, Tuple, Union -from web3 import Web3 # python3 -m pip install web3 -from web3.contract.contract import Contract, ContractConstructor, ContractFunction -from web3.types import TxReceipt - -# check web3 version -if list(map(int, web3.__version__.split('.'))) < [ 6, 2, 0 ]: - raise RuntimeError( - 'web3 version {} is not supported; ' - 'please upgrade to version 6.2.0 or above.'.format(web3.__version__) - ) - - -def LoadBytesFromRelease( - projConf: dict, - release: str, - contract: str -) -> Tuple[str, str]: - - urlAbi = projConf['releaseUrl'].format(version=release, contract=contract + '.abi') - with urllib.request.urlopen(urlAbi) as f: - abiBytes = f.read().decode() - - urlBin = projConf['releaseUrl'].format(version=release, contract=contract + '.bin') - with urllib.request.urlopen(urlBin) as f: - binBytes = f.read().decode() - - return abiBytes, binBytes - - -def LoadBytesFromLocal(projConf: dict, contract: str) -> Tuple[str, str]: - - module = projConf['contractModuleMap'][contract] - - pathAbi = os.path.join(projConf['buildDir'], module, contract + '.abi') - if os.path.isfile(pathAbi) is False: - raise FileNotFoundError( - 'Cannot find locally built contract ABI file at {}; ' - 'please build the contract first.'.format(pathAbi) - ) - - with open(pathAbi, 'r') as f: - abiBytes = f.read() - - pathBin = os.path.join(projConf['buildDir'], module, contract + '.bin') - if os.path.isfile(pathBin) is False: - raise FileNotFoundError( - 'Cannot find locally built contract BIN file at {}; ' - 'please build the contract first.'.format(pathBin) - ) - - with open(pathBin, 'r') as f: - binBytes = f.read() - - return abiBytes, binBytes - - -def LoadContract( - w3: Web3, - projConf: Union[ dict, os.PathLike ], - contractName: str, - release: Union[ None, str ] = None, - address: Union[ None, str ] = None, -) -> Contract: - - if not isinstance(projConf, dict): - with open(projConf, 'r') as f: - projConf = json.load(f) - - if release is None: - # load from local build - abiBytes, binBytes = LoadBytesFromLocal(projConf, contractName) - else: - # load from release - abiBytes, binBytes = LoadBytesFromRelease(projConf, release, contractName) - - if address is None: - # deploy new contract - contract = w3.eth.contract(abi=abiBytes, bytecode=binBytes) - else: - # load existing contract - contract = w3.eth.contract(address=address, abi=abiBytes) - - return contract - - -def _EstimateGas( - executable: Union[ ContractConstructor, ContractFunction ], - value: int, -) -> int: - logger = logging.getLogger(__name__ + '.' + _EstimateGas.__name__) - - gas = executable.estimate_gas({ - 'value': value, - }) - logger.info('Estimated gas: {}'.format(gas)) - # add a little bit flexibility - gas = int(gas * 1.1) - - return gas - - -def _DetermineGas( - executable: Union[ ContractConstructor, ContractFunction ], - gas: Union[ None, int ], - value: int, -) -> int: - logger = logging.getLogger(__name__ + '.' + _DetermineGas.__name__) - - if gas is None: - gas = _EstimateGas(executable, value) - - logger.debug('Gas: {}; Value: {}'.format(gas, value)) - - return gas - - -def _DetermineMaxPriorFee( - w3: Web3, -) -> int: - baseGasFee = w3.eth.gas_price - # priority fee is 2% of base fee - maxPriorFee = int(baseGasFee * 2) // 100 - # ensure it's higher than w3.eth.max_priority_fee - maxPriorFee = max(maxPriorFee, int(w3.eth.max_priority_fee)) - - return maxPriorFee - - -def _FillMessage( - w3: Web3, - gas: int, - value: int, - privKey: Union[ None, str ], -) -> dict: - - msg = { - 'nonce': w3.eth.get_transaction_count(w3.eth.default_account), - 'chainId': w3.eth.chain_id, - 'gas': gas, - 'value': value, - } - if privKey is not None: - msg['maxFeePerGas'] = int(w3.eth.gas_price * 2) - msg['maxPriorityFeePerGas'] = _DetermineMaxPriorFee(w3) - - return msg - - -def _SignTx( - w3: Web3, - tx: dict, - privKey: str, - confirmPrompt: bool -) -> SignedTransaction: - logger = logging.getLogger(__name__ + '.' + _SignTx.__name__) - - maxBaseFee = tx['maxFeePerGas'] - maxPriorityFee = tx['maxPriorityFeePerGas'] - gas = tx['gas'] - value = tx['value'] - - balance = w3.eth.get_balance(w3.eth.default_account) - - maxFee = (maxBaseFee + maxPriorityFee) * gas - maxCost = maxFee + value - if maxCost > balance: - raise RuntimeError( - 'Insufficient balance to pay for the transaction' - '(balance {} wei; max cost: {} wei)'.format( - balance, maxCost - ) - ) - - if confirmPrompt: - baseFee = w3.eth.gas_price - baseFeeGwei = w3.from_wei(baseFee, 'gwei') - fee = baseFee * gas - feeGwei = w3.from_wei(fee, 'gwei') - - maxBaseFeeGwei = w3.from_wei(maxBaseFee, 'gwei') - maxPriorityFeeGwei = w3.from_wei(maxPriorityFee, 'gwei') - maxFeeGwei = w3.from_wei(maxFee, 'gwei') - - valueEther = w3.from_wei(value, 'ether') - - cost = fee + value - costEther = w3.from_wei(cost, 'ether') - maxCostEther = w3.from_wei(maxCost, 'ether') - - balanceEther = w3.from_wei(balance, 'ether') - afterBalanceEther = w3.from_wei(balance - cost, 'ether') - minAfterBalanceEther = w3.from_wei(balance - maxCost, 'ether') - - print('Gas: {}'.format(gas)) - print('gas price: {:.9f} Gwei'.format(baseFeeGwei)) - print('Fee: {:.9f} Gwei'.format(feeGwei)) - print('Max fee / gas: {:.9f} Gwei'.format(maxBaseFeeGwei)) - print('Max prior. fee / gas: {:.9f} Gwei'.format(maxPriorityFeeGwei)) - print('Max fee: {:.9f} Gwei'.format(maxFeeGwei)) - print('Value: {:.18f} Ether'.format(valueEther)) - print() - print('Cost: {:.18f} Ether'.format(costEther)) - print('Max cost: {:.18f} Ether'.format(maxCostEther)) - print() - print('Balance: {:.18f} Ether'.format(balanceEther)) - print('After balance: {:.18f} Ether'.format(afterBalanceEther)) - print('Min. after balance: {:.18f} Ether'.format(minAfterBalanceEther)) - - confirm = input('Confirm transaction? (please type "yes", case insensitive): ') - if confirm.lower() != 'yes': - raise RuntimeError('Transaction cancelled') - - logger.info( - 'Signing transaction with max cost of {} wei'.format(maxCost) - ) - signedTx = w3.eth.account.sign_transaction(tx, privKey) - - return signedTx - - -def _DoTransaction( - w3: Web3, - executable: Union[ ContractConstructor, ContractFunction ], - privKey: Union[ None, str ], - gas: Union[ None, int ], - value: int, - confirmPrompt: bool, -) -> TxReceipt: - logger = logging.getLogger(__name__ + '.' + _DoTransaction.__name__) - - gas = _DetermineGas(executable, gas, value) - msg = _FillMessage(w3, gas, value, privKey) - - if privKey is None: - # no signing needed - txHash = executable.transact(msg) - else: - # need to sign - tx = executable.build_transaction(msg) - signedTx = _SignTx(w3, tx, privKey, confirmPrompt) - txHash = w3.eth.send_raw_transaction(signedTx.rawTransaction) - - receipt = w3.eth.wait_for_transaction_receipt(txHash) - - receiptJson = json.dumps(json.loads(Web3.to_json(receipt)), indent=4) - logger.info('Transaction receipt: {}'.format(receiptJson)) - - logger.info('Balance after transaction: {} Ether'.format( - w3.from_wei( - w3.eth.get_balance(w3.eth.default_account), - 'ether' - ) - )) - - return receipt - - -def _FindConstructorAbi( - abiList: List[ dict ], -) -> dict: - for abi in abiList: - if abi['type'] == 'constructor': - return abi - - raise ValueError('No constructor found in ABI') - - -def DeployContract( - w3: Web3, - contract: Contract, - arguments: list, - privKey: Union[str, None] = None, - gas: Union[int, None] = None, - value: int = 0, - confirmPrompt: bool = True, -) -> TxReceipt: - logger = logging.getLogger(__name__ + '.' + DeployContract.__name__) - - constrAbi = _FindConstructorAbi(contract.abi) - isPayable = constrAbi['stateMutability'] == 'payable' - executable = contract.constructor(*arguments) - - receipt = _DoTransaction( - w3=w3, - executable=executable, - privKey=privKey, - gas=gas, - value=value if isPayable else 0, - confirmPrompt=confirmPrompt, - ) - logger.info('Contract deployed at {}'.format(receipt.contractAddress)) - - return receipt - - -def _FindFuncAbi( - abiList: List[ dict ], - funcName: str, -) -> dict: - for abi in abiList: - if ( - (abi['type'] == 'function') and - (abi['name'] == funcName) - ): - return abi - - raise ValueError('Function "{}" not found in ABI'.format(funcName)) - - -def CallContractFunc( - w3: Web3, - contract: Contract, - funcName: str, - arguments: list, - privKey: Union[str, None] = None, - gas: Union[int, None] = None, - value: int = 0, - confirmPrompt: bool = True, -) -> Union[TxReceipt, Any]: - logger = logging.getLogger(__name__ + '.' + CallContractFunc.__name__) - - funcAbi = _FindFuncAbi(contract.abi, funcName) - isViewFunc = funcAbi['stateMutability'] == 'view' - isPayable = funcAbi['stateMutability'] == 'payable' - executable = contract.functions[funcName](*arguments) - - if isViewFunc: - logger.info('Calling view function "{}"'.format(funcName)) - result = executable.call() - logger.info('Type:{}; Result: {}'.format(type(result), result)) - - return result - else: - receipt = _DoTransaction( - w3=w3, - executable=executable, - privKey=privKey, - gas=gas, - value=value if isPayable else 0, - confirmPrompt=confirmPrompt, - ) - - return receipt - - -def ConvertValToWei(val: int, unit: str) -> int: - logger = logging.getLogger(__name__ + '.' + ConvertValToWei.__name__) - - valueToSend = Web3.to_wei(val, unit) - valueToSendEth = Web3.from_wei(valueToSend, 'ether') - if valueToSend > 0: - logger.warning( - 'Value to be sent: {:.18f} ether (or {} wei)'.format( - valueToSendEth, - valueToSend - ) - ) - - return int(valueToSend) - - -def _LoadAccountCredentials( - keyJson: os.PathLike, - index: int -) -> Tuple[str, str]: - with open(keyJson, 'r') as f: - keyJson: Dict[str, Dict[str, str]] = json.load(f) - - if index >= len(keyJson['addresses']): - raise IndexError('Cannot find address at index {}'.format(index)) - - address = [ - a for i, a in enumerate(keyJson['addresses'].keys()) if i == index - ][0] - - for addr, priv in keyJson['private_keys'].items(): - if addr.lower() == address.lower(): - return address, priv - - raise KeyError('Cannot find private key for address {}'.format(address)) - - -def SetupSendingAccount( - w3: Web3, - account: int, - keyJson: Union[ None, os.PathLike ] = None, -) -> Union[ str, None ]: - logger = logging.getLogger(__name__ + '.' + SetupSendingAccount.__name__) - - if keyJson is not None: - addr, privKey = _LoadAccountCredentials(keyJson, account) - w3.eth.default_account = addr - else: - w3.eth.default_account = w3.eth.accounts[0] - privKey = None - - if privKey is not None: - # ensure that the private key matches the address - privAcc = w3.eth.account.from_key(privKey) - if privAcc.address != w3.eth.default_account: - raise ValueError( - 'The private key does not match the address' - ) - - logger.info( - 'The address of the account to be used: {}'.format( - w3.eth.default_account - ) - ) - - return privKey - - -def ChecksumGanacheKeysFile(dest: os.PathLike, src: os.PathLike): - with open(src, 'r') as f: - keysJson: Dict[str, Dict[str, str]] = json.load(f) - - addrs = keysJson['addresses'] - addrs = { - Web3.to_checksum_address(k): Web3.to_checksum_address(v) - for k, v in addrs.items() - } - keysJson['addresses'] = addrs - - privKeys = keysJson['private_keys'] - privKeys = { - Web3.to_checksum_address(k): v for k, v in privKeys.items() - } - keysJson['private_keys'] = privKeys - - with open(dest, 'w') as f: - json.dump(keysJson, f, indent='\t') - - -def main(): - argParser = argparse.ArgumentParser( - description='Deploy contracts to Ethereum blockchain' - ) - argParser.add_argument( - '--config', '-c', type=str, default='project_conf.json', required=False, - help='Path to the project configuration file' - ) - argParser.add_argument( - '--verbose', action='store_true', - help='Verbose logging' - ) - argParser.add_argument( - '--http', type=str, default='http://localhost:7545', required=False, - help='HTTP provider URL' - ) - argParser.add_argument( - '--key-json', type=str, default=None, required=False, - help='Path to keys.json' - ) - argParser.add_argument( - '--account', type=int, default=0, required=False, - help='Index of the account to use' - ) - argParser.add_argument( - '--release', type=str, default=None, required=False, - help='Use prebuilt version from GitHub Release of given git version tag' - '(if not set, local built version will be used)' - ) - argParser.add_argument( - '--contract', type=str, required=True, - help='Contract name' - ) - argParser.add_argument( - '--gas', type=int, default=None, required=False, - help='Gas limit' - ) - argParser.add_argument( - '--value', type=int, default=0, required=False, - help='Value to be sent along with the transaction' - ) - argParser.add_argument( - '--value-unit', type=str, default='wei', required=False, - choices=['ether', 'gwei', 'wei'], - help='Unit of value (ether, gwei, wei)' - ) - argParser.add_argument( - '--no-confirm', action='store_true', - help='Do not ask for confirmation' - ) - - # two operations: deploy and call - argParserSubOp = argParser.add_subparsers( - help='Operation to be performed', - dest='operation', - required=True - ) - - argParserSubOpDeploy = argParserSubOp.add_parser('deploy') - argParserSubOpDeploy.add_argument( - '--args', type=str, nargs='*', default=[], required=False, - help='Constructor/function arguments' - ) - - argParserSubOpCall = argParserSubOp.add_parser('call') - argParserSubOpCall.add_argument( - '--address', type=str, default=None, required=True, - help='Address of the contract to be called' - ) - argParserSubOpCall.add_argument( - '--function', type=str, required=True, - help='Call function' - ) - argParserSubOpCall.add_argument( - '--args', type=str, nargs='*', default=[], required=False, - help='Constructor/function arguments' - ) - - args = argParser.parse_args() - - address = None if args.operation != 'call' else args.address - - # logging configuration - loggingFormat = '%(asctime)s %(levelname)s %(message)s' - if args.verbose: - logging.basicConfig(level=logging.DEBUG, format=loggingFormat) - else: - logging.basicConfig(level=logging.INFO, format=loggingFormat) - logger = logging.getLogger(__name__ + main.__name__) - - # connect to Ethereum node - w3 = Web3(Web3.HTTPProvider(args.http)) - if not w3.is_connected(): - raise RuntimeError( - 'Failed to connect to Ethereum node at %s' % args.http - ) - - valueToSend = ConvertValToWei(args.value, args.value_unit) - privKey = SetupSendingAccount(w3, args.account, args.key_json) - contract = LoadContract( - w3=w3, - projConf=args.config, - contractName=args.contract, - release=args.release, - address=address - ) - - # deploy or call contract - if args.operation == 'deploy': - DeployContract( - w3=w3, - contract=contract, - arguments=args.args, - privKey=privKey, - gas=args.gas, - value=valueToSend, - confirmPrompt=(not args.no_confirm) - ) - elif args.operation == 'call': - CallContractFunc( - w3=w3, - contract=contract, - funcName=args.function, - arguments=args.args, - privKey=privKey, - gas=args.gas, - value=valueToSend, - confirmPrompt=(not args.no_confirm) - ) - - -if __name__ == '__main__': - main() diff --git a/utils/PyEthHelper b/utils/PyEthHelper new file mode 160000 index 0000000..0e59df2 --- /dev/null +++ b/utils/PyEthHelper @@ -0,0 +1 @@ +Subproject commit 0e59df2450533aaf4669892ff4c152d0e152e0ac diff --git a/utils/ganache_keys.json b/utils/ganache_keys.json deleted file mode 100644 index 6adb0a8..0000000 --- a/utils/ganache_keys.json +++ /dev/null @@ -1 +0,0 @@ -{"addresses":{"0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1":"0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1","0xffcf8fdee72ac11b5c542428b35eef5769c409f0":"0xffcf8fdee72ac11b5c542428b35eef5769c409f0","0x22d491bde2303f2f43325b2108d26f1eaba1e32b":"0x22d491bde2303f2f43325b2108d26f1eaba1e32b","0xe11ba2b4d45eaed5996cd0823791e0c93114882d":"0xe11ba2b4d45eaed5996cd0823791e0c93114882d","0xd03ea8624c8c5987235048901fb614fdca89b117":"0xd03ea8624c8c5987235048901fb614fdca89b117","0x95ced938f7991cd0dfcb48f0a06a40fa1af46ebc":"0x95ced938f7991cd0dfcb48f0a06a40fa1af46ebc","0x3e5e9111ae8eb78fe1cc3bb8915d5d461f3ef9a9":"0x3e5e9111ae8eb78fe1cc3bb8915d5d461f3ef9a9","0x28a8746e75304c0780e011bed21c72cd78cd535e":"0x28a8746e75304c0780e011bed21c72cd78cd535e","0xaca94ef8bd5ffee41947b4585a84bda5a3d3da6e":"0xaca94ef8bd5ffee41947b4585a84bda5a3d3da6e","0x1df62f291b2e969fb0849d99d9ce41e2f137006e":"0x1df62f291b2e969fb0849d99d9ce41e2f137006e","0x610bb1573d1046fcb8a70bbbd395754cd57c2b60":"0x610bb1573d1046fcb8a70bbbd395754cd57c2b60","0x855fa758c77d68a04990e992aa4dcdef899f654a":"0x855fa758c77d68a04990e992aa4dcdef899f654a","0xfa2435eacf10ca62ae6787ba2fb044f8733ee843":"0xfa2435eacf10ca62ae6787ba2fb044f8733ee843","0x64e078a8aa15a41b85890265648e965de686bae6":"0x64e078a8aa15a41b85890265648e965de686bae6","0x2f560290fef1b3ada194b6aa9c40aa71f8e95598":"0x2f560290fef1b3ada194b6aa9c40aa71f8e95598","0xf408f04f9b7691f7174fa2bb73ad6d45fd5d3cbe":"0xf408f04f9b7691f7174fa2bb73ad6d45fd5d3cbe","0x66fc63c2572bf3add0fe5d44b97c2e614e35e9a3":"0x66fc63c2572bf3add0fe5d44b97c2e614e35e9a3","0xf0d5bc18421fa04d0a2a2ef540ba5a9f04014be3":"0xf0d5bc18421fa04d0a2a2ef540ba5a9f04014be3","0x325a621dea613bcfb5b1a69a7aced0ea4afbd73a":"0x325a621dea613bcfb5b1a69a7aced0ea4afbd73a","0x3fd652c93dfa333979ad762cf581df89baba6795":"0x3fd652c93dfa333979ad762cf581df89baba6795","0x73eb6d82cfb20ba669e9c178b718d770c49bb52f":"0x73eb6d82cfb20ba669e9c178b718d770c49bb52f","0x9d8e5fac117b15daced7c326ae009dfe857621f1":"0x9d8e5fac117b15daced7c326ae009dfe857621f1","0x982a8cbe734cb8c29a6a7e02a3b0e4512148f6f9":"0x982a8cbe734cb8c29a6a7e02a3b0e4512148f6f9","0xcdc1e53bdc74bbf5b5f715d6327dca5785e228b4":"0xcdc1e53bdc74bbf5b5f715d6327dca5785e228b4","0xf5d1eaf516ef3b0582609622a221656872b82f78":"0xf5d1eaf516ef3b0582609622a221656872b82f78","0xf8ea26c3800d074a11bf814db9a0735886c90197":"0xf8ea26c3800d074a11bf814db9a0735886c90197","0x2647116f9304abb9f0b7ac29abc0d9ad540506c8":"0x2647116f9304abb9f0b7ac29abc0d9ad540506c8","0x80a32a0e5ca81b5a236168c21532b32e3cbc95e2":"0x80a32a0e5ca81b5a236168c21532b32e3cbc95e2","0x47f55a2ace3b84b0f03717224dbb7d0df4351658":"0x47f55a2ace3b84b0f03717224dbb7d0df4351658","0xc817898296b27589230b891f144dd71a892b0c18":"0xc817898296b27589230b891f144dd71a892b0c18","0x0d38e653ec28bdea5a2296fd5940aab2d0b8875c":"0x0d38e653ec28bdea5a2296fd5940aab2d0b8875c","0x1b569e8f1246907518ff3386d523dcf373e769b6":"0x1b569e8f1246907518ff3386d523dcf373e769b6","0xcbb025e7933fadfc7c830ae520fb2fd6d28c1065":"0xcbb025e7933fadfc7c830ae520fb2fd6d28c1065","0xddeea4839bbed92bdad8ec79ae4f4bc2be1a3974":"0xddeea4839bbed92bdad8ec79ae4f4bc2be1a3974","0xbc2cf859f671b78ba42ebb65deb31cc7fec07019":"0xbc2cf859f671b78ba42ebb65deb31cc7fec07019","0xf75588126126ddf76bdc8aba91a08f31d2567ca5":"0xf75588126126ddf76bdc8aba91a08f31d2567ca5","0x369109c74ea7159e77e180f969f7d48c2bf19b4c":"0x369109c74ea7159e77e180f969f7d48c2bf19b4c","0xa2a628f4eee25f5b02b0688ad9c1290e2e9a3d9e":"0xa2a628f4eee25f5b02b0688ad9c1290e2e9a3d9e","0x693d718ccfade6f4a1379051d6ab998146f3173f":"0x693d718ccfade6f4a1379051d6ab998146f3173f","0x845a0f9441081779110fee40e6d5d8b90ce676ef":"0x845a0f9441081779110fee40e6d5d8b90ce676ef","0xc7739909e08a9a0f303a010d46658bdb4d5a6786":"0xc7739909e08a9a0f303a010d46658bdb4d5a6786","0x99cce66d3a39c2c2b83afceff04c5ec56e9b2a58":"0x99cce66d3a39c2c2b83afceff04c5ec56e9b2a58","0x4b930e7b3e491e37eab48ecc8a667c59e307ef20":"0x4b930e7b3e491e37eab48ecc8a667c59e307ef20","0x02233b22860f810e32fb0751f368fe4ef21a1c05":"0x02233b22860f810e32fb0751f368fe4ef21a1c05","0x89c1d413758f8339ade263e6e6bc072f1d429f32":"0x89c1d413758f8339ade263e6e6bc072f1d429f32","0x61bbb5135b43f03c96570616d6d3f607b7103111":"0x61bbb5135b43f03c96570616d6d3f607b7103111","0x8c4ce7a10a4e38ee96fed47c628be1ffa57ab96e":"0x8c4ce7a10a4e38ee96fed47c628be1ffa57ab96e","0x25c1230c7efc00cfd2fcaa3a44f30948853824bc":"0x25c1230c7efc00cfd2fcaa3a44f30948853824bc","0x709f7ae06fe93be48fbb90ffddd69e2746fa8506":"0x709f7ae06fe93be48fbb90ffddd69e2746fa8506","0xc0514c03d097fcbb77a74b4da5b594ba473b6ce1":"0xc0514c03d097fcbb77a74b4da5b594ba473b6ce1","0x103b31135d99417a22684ed93cbbcd4ccd208046":"0x103b31135d99417a22684ed93cbbcd4ccd208046","0xf8856d473639e40f60db8979f5752a9c15903bb2":"0xf8856d473639e40f60db8979f5752a9c15903bb2","0x753897706061fde347465055fcac4bd040745624":"0x753897706061fde347465055fcac4bd040745624","0x7cd15a5d345558a203655e40b1afb14f936c73f7":"0x7cd15a5d345558a203655e40b1afb14f936c73f7","0x7d8ae65273b9d1e6b239b36af9adea0414d189b7":"0x7d8ae65273b9d1e6b239b36af9adea0414d189b7","0x05a561f51a2d8a092b11e20c72b5df15a9d82278":"0x05a561f51a2d8a092b11e20c72b5df15a9d82278","0x80030beca8292f416e7906535668475c75d9c47e":"0x80030beca8292f416e7906535668475c75d9c47e","0xeda51422804340e3dc0dd9b6d441125b5c7cf3ff":"0xeda51422804340e3dc0dd9b6d441125b5c7cf3ff","0xe21812faa737ff0eeec268f509acb306bc735fec":"0xe21812faa737ff0eeec268f509acb306bc735fec","0x4d85247717cf8621d7894f36de35e8b6b6d384bc":"0x4d85247717cf8621d7894f36de35e8b6b6d384bc","0x19b2d46091dd332f0753dabf0cf8304cf61ed1c5":"0x19b2d46091dd332f0753dabf0cf8304cf61ed1c5","0x42c7c045729a84f8e65239308ca8279d6fb21c89":"0x42c7c045729a84f8e65239308ca8279d6fb21c89","0xeed15bb091bf3f615400f6f8160ac423eaf6a413":"0xeed15bb091bf3f615400f6f8160ac423eaf6a413","0x0f6f0ecfab78f8e54b130e3b3ebd88b3613c97d1":"0x0f6f0ecfab78f8e54b130e3b3ebd88b3613c97d1","0x33a053885a8232ed78d688b43a405587ba446e5e":"0x33a053885a8232ed78d688b43a405587ba446e5e","0x4397655ddd031043eb0859ad7a90c3c889e12a4d":"0x4397655ddd031043eb0859ad7a90c3c889e12a4d","0x6e57514b1997029500c13007a59fb6da1ceac7c4":"0x6e57514b1997029500c13007a59fb6da1ceac7c4","0x85c38d25744f02619047b76195ecf835554f70bc":"0x85c38d25744f02619047b76195ecf835554f70bc","0x69901c8c4263a0368c19d3cd9dc51b09bec4c4b1":"0x69901c8c4263a0368c19d3cd9dc51b09bec4c4b1","0x256dd44a34478acec9a7da479dbcf0c3c599ad55":"0x256dd44a34478acec9a7da479dbcf0c3c599ad55","0x61f41c87113e04b32eb8fbaa4946b1ef98479756":"0x61f41c87113e04b32eb8fbaa4946b1ef98479756","0xa8ba9dea29234be7504fae477d2f6b1fd1078d46":"0xa8ba9dea29234be7504fae477d2f6b1fd1078d46","0x831c50ac59c3794185fabae289d3a5ba8b09403c":"0x831c50ac59c3794185fabae289d3a5ba8b09403c","0xc8f2d6111bc7207c25eb4f944cb29f0e851a8541":"0xc8f2d6111bc7207c25eb4f944cb29f0e851a8541","0xbca6ebd43dcb10851f398b4cb8fbade3133b2c45":"0xbca6ebd43dcb10851f398b4cb8fbade3133b2c45","0x856c1365488375d21875f80d6045c956e47ed5ec":"0x856c1365488375d21875f80d6045c956e47ed5ec","0x356780865cd279e4d2dc6d99b32eda8fd8e8a39c":"0x356780865cd279e4d2dc6d99b32eda8fd8e8a39c","0x1baec60a021c5e26a1071776a1549c45c79951d5":"0x1baec60a021c5e26a1071776a1549c45c79951d5","0x8155eb275ea6ebd0d572a44087c948b02d794013":"0x8155eb275ea6ebd0d572a44087c948b02d794013","0x6a8bbdb024861739b0dcd1700c8b8f603f1cf7c6":"0x6a8bbdb024861739b0dcd1700c8b8f603f1cf7c6","0xb890c74caa6c052db376837e67f0476589991922":"0xb890c74caa6c052db376837e67f0476589991922","0xd5b6d6b730b1c1be10b82a0a1c89f1db24f752c3":"0xd5b6d6b730b1c1be10b82a0a1c89f1db24f752c3","0x2a828adcc1a3647dba43ed05375a4d0b00eea789":"0x2a828adcc1a3647dba43ed05375a4d0b00eea789","0x624a97293d8cea5ca78d538ac6599e4051a19174":"0x624a97293d8cea5ca78d538ac6599e4051a19174","0xe7a3ebba0647fb07d0b21927305aa95284316993":"0xe7a3ebba0647fb07d0b21927305aa95284316993","0xd74485a6600d8de95d84d5e1747480c528df1f9a":"0xd74485a6600d8de95d84d5e1747480c528df1f9a","0x3b1cb706e5fff494da8873ad9c1a30aa802f4522":"0x3b1cb706e5fff494da8873ad9c1a30aa802f4522","0xfbdb66ae3fa6f1b37a02c82751117fc3aad4572b":"0xfbdb66ae3fa6f1b37a02c82751117fc3aad4572b","0x4f92c13cacf198eb25698709e3d225e6a2e22dd8":"0x4f92c13cacf198eb25698709e3d225e6a2e22dd8","0x18282ec61c35bef47698c3e65314c9a0ff617b3c":"0x18282ec61c35bef47698c3e65314c9a0ff617b3c","0xafa5e9d58e245b7f3efecc9e706b06d52cd28da1":"0xafa5e9d58e245b7f3efecc9e706b06d52cd28da1","0xd32115d6e4a4dfdf0807544723d514e3f293d3b6":"0xd32115d6e4a4dfdf0807544723d514e3f293d3b6","0x56fa56dc28081f6353737061e2278631b2659598":"0x56fa56dc28081f6353737061e2278631b2659598","0x3fae75cce89a972fa1b6d87bb080fb2c6060f0b3":"0x3fae75cce89a972fa1b6d87bb080fb2c6060f0b3","0xa9f913312b7ec75f755c4f3edb6e2bbd3526b918":"0xa9f913312b7ec75f755c4f3edb6e2bbd3526b918","0x1f627b7fb483e5b8d59aa191fec94d01753c7d24":"0x1f627b7fb483e5b8d59aa191fec94d01753c7d24","0xb45de3796b206793e8ad3509202da91d35e9a6d9":"0xb45de3796b206793e8ad3509202da91d35e9a6d9","0x17332dd7f9bd584e2e83f4cffdca0a448b3b903a":"0x17332dd7f9bd584e2e83f4cffdca0a448b3b903a","0x9d7822d5bb9f7b9b655669550095d2f14afac469":"0x9d7822d5bb9f7b9b655669550095d2f14afac469","0x4c0408db276ef793333baf5b226d8b180c3d0a89":"0x4c0408db276ef793333baf5b226d8b180c3d0a89"},"private_keys":{"0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1":"0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d","0xffcf8fdee72ac11b5c542428b35eef5769c409f0":"0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1","0x22d491bde2303f2f43325b2108d26f1eaba1e32b":"0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c","0xe11ba2b4d45eaed5996cd0823791e0c93114882d":"0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913","0xd03ea8624c8c5987235048901fb614fdca89b117":"0xadd53f9a7e588d003326d1cbf9e4a43c061aadd9bc938c843a79e7b4fd2ad743","0x95ced938f7991cd0dfcb48f0a06a40fa1af46ebc":"0x395df67f0c2d2d9fe1ad08d1bc8b6627011959b79c53d7dd6a3536a33ab8a4fd","0x3e5e9111ae8eb78fe1cc3bb8915d5d461f3ef9a9":"0xe485d098507f54e7733a205420dfddbe58db035fa577fc294ebd14db90767a52","0x28a8746e75304c0780e011bed21c72cd78cd535e":"0xa453611d9419d0e56f499079478fd72c37b251a94bfde4d19872c44cf65386e3","0xaca94ef8bd5ffee41947b4585a84bda5a3d3da6e":"0x829e924fdf021ba3dbbc4225edfece9aca04b929d6e75613329ca6f1d31c0bb4","0x1df62f291b2e969fb0849d99d9ce41e2f137006e":"0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773","0x610bb1573d1046fcb8a70bbbd395754cd57c2b60":"0x77c5495fbb039eed474fc940f29955ed0531693cc9212911efd35dff0373153f","0x855fa758c77d68a04990e992aa4dcdef899f654a":"0xd99b5b29e6da2528bf458b26237a6cf8655a3e3276c1cdc0de1f98cefee81c01","0xfa2435eacf10ca62ae6787ba2fb044f8733ee843":"0x9b9c613a36396172eab2d34d72331c8ca83a358781883a535d2941f66db07b24","0x64e078a8aa15a41b85890265648e965de686bae6":"0x0874049f95d55fb76916262dc70571701b5c4cc5900c0691af75f1a8a52c8268","0x2f560290fef1b3ada194b6aa9c40aa71f8e95598":"0x21d7212f3b4e5332fd465877b64926e3532653e2798a11255a46f533852dfe46","0xf408f04f9b7691f7174fa2bb73ad6d45fd5d3cbe":"0x47b65307d0d654fd4f786b908c04af8fface7710fc998b37d219de19c39ee58c","0x66fc63c2572bf3add0fe5d44b97c2e614e35e9a3":"0x66109972a14d82dbdb6894e61f74708f26128814b3359b64f8b66565679f7299","0xf0d5bc18421fa04d0a2a2ef540ba5a9f04014be3":"0x2eac15546def97adc6d69ca6e28eec831189baa2533e7910755d15403a0749e8","0x325a621dea613bcfb5b1a69a7aced0ea4afbd73a":"0x2e114163041d2fb8d45f9251db259a68ee6bdbfd6d10fe1ae87c5c4bcd6ba491","0x3fd652c93dfa333979ad762cf581df89baba6795":"0xae9a2e131e9b359b198fa280de53ddbe2247730b881faae7af08e567e58915bd","0x73eb6d82cfb20ba669e9c178b718d770c49bb52f":"0xd09ba371c359f10f22ccda12fd26c598c7921bda3220c9942174562bc6a36fe8","0x9d8e5fac117b15daced7c326ae009dfe857621f1":"0x2d2719c6a828911ed0c50d5a6c637b63353e77cf57ea80b8e90e630c4687e9c5","0x982a8cbe734cb8c29a6a7e02a3b0e4512148f6f9":"0xd353907ab062133759f149a3afcb951f0f746a65a60f351ba05a3ebf26b67f5c","0xcdc1e53bdc74bbf5b5f715d6327dca5785e228b4":"0x971c58af72fd8a158d4e654cfbe98f5de024d28547005909684f58c9c46a25c4","0xf5d1eaf516ef3b0582609622a221656872b82f78":"0x85d168288e7fcf84b1841e447fc7945b1e27bfe9a3776367079a6427405eac66","0xf8ea26c3800d074a11bf814db9a0735886c90197":"0xf3da3ac70552606ed09d16dd2808c924826094f0c5cbfcb4f2e0e1cfc70ff8dd","0x2647116f9304abb9f0b7ac29abc0d9ad540506c8":"0xbf20e9c05d70ce59a6b125eab3b4122eb75044a33749c4c5a77e3b0b86fa091e","0x80a32a0e5ca81b5a236168c21532b32e3cbc95e2":"0x647442126fdb80c6aec75a0d75a6fe1b31a4e204d29a2c446f550c4115cac139","0x47f55a2ace3b84b0f03717224dbb7d0df4351658":"0xef78746d079c9d72d2e9a3c10447d1d4aaae6a51541d0296da4fc9ec7e060aff","0xc817898296b27589230b891f144dd71a892b0c18":"0xc95286117cd74213417aeca52118ccd03ec240582f0a9a3e4ef7b434523179f3","0x0d38e653ec28bdea5a2296fd5940aab2d0b8875c":"0x21118f9a6de181061a2abd549511105adb4877cf9026f271092e6813b7cf58ab","0x1b569e8f1246907518ff3386d523dcf373e769b6":"0x1166189cdf129cdcb011f2ad0e5be24f967f7b7026d162d7c36073b12020b61c","0xcbb025e7933fadfc7c830ae520fb2fd6d28c1065":"0x1aa14c63d481dcc1185a654eb52c9c0749d07ac8f30ef17d45c3c391d9bf68eb","0xddeea4839bbed92bdad8ec79ae4f4bc2be1a3974":"0x4a23fe455a34bb47f8f3282a4f6d36c22987275f0bb9aacb251568df7d038385","0xbc2cf859f671b78ba42ebb65deb31cc7fec07019":"0x2450bb2893d0bddf92f4ac88cb65a8e94b56e89f7ec3e46c9c88b2b46ebe3ca5","0xf75588126126ddf76bdc8aba91a08f31d2567ca5":"0xf934aded8693d6b2b61ccbb3bc1f86a86afbbd8622a5eb3401b2f8de9863b07b","0x369109c74ea7159e77e180f969f7d48c2bf19b4c":"0xc8eea9d162fe9d6852afa0d55ebe6b14b8e6fc9b0e93ae13209e2b4db48a6482","0xa2a628f4eee25f5b02b0688ad9c1290e2e9a3d9e":"0xbe146cdb15d4069e0249da35c928819cbde563dd4fe3d1ccfeda7885a52e0754","0x693d718ccfade6f4a1379051d6ab998146f3173f":"0x74ae0c3d566d7e73613d4ebb814b0f37a2d040060814f75e115d28469d22f4c2","0x845a0f9441081779110fee40e6d5d8b90ce676ef":"0xb2b19df163d1f952df31e32c694d592e530c0b3d54c6276015bc9b0acaf982de","0xc7739909e08a9a0f303a010d46658bdb4d5a6786":"0x86117111fcb34df8d0e58505969021b9308513c6e94d16172f0c8789a7130a43","0x99cce66d3a39c2c2b83afceff04c5ec56e9b2a58":"0xdcb8686c211c231be763f0a95cc02227a707643fd2631bda99fcdbd03cd9ca3d","0x4b930e7b3e491e37eab48ecc8a667c59e307ef20":"0xb74ffec4abd7e93889196054d5e6ed8ea9c1c3314e77a74c00f851c47f5268fd","0x02233b22860f810e32fb0751f368fe4ef21a1c05":"0xba30972105ec13423116d2e5c11a8d282805ac3654bb4c1c2f5fa63f4da42dad","0x89c1d413758f8339ade263e6e6bc072f1d429f32":"0x87ad1798a2d32434f72598575237528a435416da1bdc900025c415903647957e","0x61bbb5135b43f03c96570616d6d3f607b7103111":"0x5d4af11a54d4a5196b0073ba26a1114cb113e1339d9354c8165b8e181c89cad9","0x8c4ce7a10a4e38ee96fed47c628be1ffa57ab96e":"0xa03bf2b145b0154c2e788a1d4642d235f6ff1c8aceeb41d0d7232525da8bdb77","0x25c1230c7efc00cfd2fcaa3a44f30948853824bc":"0xb1f4063952ebc0785bbc201520ed7f0c5fc15298099e60e62f8cfa456bbc2705","0x709f7ae06fe93be48fbb90ffddd69e2746fa8506":"0x41d647879d53baddb93cfadc3f5ef4d5bdc330bec4b4ef9caace19c70a385856","0xc0514c03d097fcbb77a74b4da5b594ba473b6ce1":"0x87c546d6cb8ec705bea47e2ab40f42a768b1e5900686b0cecc68c0e8b74cd789","0x103b31135d99417a22684ed93cbbcd4ccd208046":"0x2133975a252cc4b856c15e4fc34070e289bd0db4fcd9a292b6948c4087eff585","0xf8856d473639e40f60db8979f5752a9c15903bb2":"0x0f379eafea9630e5c66e830bf8729695d1ad1ec2e356e29685b17cb430018a4d","0x753897706061fde347465055fcac4bd040745624":"0x9fb8bcd81b2517bc32823b5e8ab61fd22161b6412da28fe536142477229b7d41","0x7cd15a5d345558a203655e40b1afb14f936c73f7":"0x10fb78c8d22bc1e112329f57cbf01f1a49f83770ef23240c3aabcebe4f96e6ac","0x7d8ae65273b9d1e6b239b36af9adea0414d189b7":"0xa74b0b1e8250fc075d8d770155d8e868b50da906c295e26485008acf841a2ce1","0x05a561f51a2d8a092b11e20c72b5df15a9d82278":"0xcd83d658af9dbc7dde51f5b112e96db398a37f317b00475bfa0847311b128d73","0x80030beca8292f416e7906535668475c75d9c47e":"0xd2aabff72208f9338a05a3dfab90b5c5d7c56f20dff7ff6aaf610e7091a42339","0xeda51422804340e3dc0dd9b6d441125b5c7cf3ff":"0x7bafc085689805d768c4315b3845c30a388901884213f5d1ae5b155c65aba0b4","0xe21812faa737ff0eeec268f509acb306bc735fec":"0xa97d6c2f4289adaa0543a17b29f6c0693aa2d6f9052d922482c7fc3b0740fb25","0x4d85247717cf8621d7894f36de35e8b6b6d384bc":"0xf14585a1cf1f7cb162d5b93f9dc14729b0afd9afb444029394fae2456204e626","0x19b2d46091dd332f0753dabf0cf8304cf61ed1c5":"0x98e1895381b0a03b614af5a148b648cfb9db6781c6abd06035cdbd1407c27677","0x42c7c045729a84f8e65239308ca8279d6fb21c89":"0x20b8cb7817ba1df58dae2bcd67abf22663481d799b8bef7aeb5b9dd2b8a43ed1","0xeed15bb091bf3f615400f6f8160ac423eaf6a413":"0xd3d74f4aa37367842f0f941dec2ae79fea870fa4496865700d2841cf14aa2075","0x0f6f0ecfab78f8e54b130e3b3ebd88b3613c97d1":"0xd6838f8f23b4bd6de81335a193c806c9fd514132ebaf8e7c06e7ae8158958635","0x33a053885a8232ed78d688b43a405587ba446e5e":"0x20192d5a5e11658557831c31e29f272ac411525e10ed43a910046d3793b79272","0x4397655ddd031043eb0859ad7a90c3c889e12a4d":"0x423658d565be792c72ed016acbf9616ea3bf5d2b8de60e9af8caacd519047836","0x6e57514b1997029500c13007a59fb6da1ceac7c4":"0xbd9c1130153fa69647001d40508a08dbd13688024f8cbb5ba9bb233b203d589f","0x85c38d25744f02619047b76195ecf835554f70bc":"0xb1b53bd2b1a3d6177aad317da256e8558dc08fea1dd4d735bbd714d752868889","0x69901c8c4263a0368c19d3cd9dc51b09bec4c4b1":"0xc2312488bbef793579de1f2eaacf9080b51356f51154c78e998831c9e1c1ba94","0x256dd44a34478acec9a7da479dbcf0c3c599ad55":"0x27412066bc3988b127e8b91d80df981df0d941aa71aeafe22b8de46f21358a5b","0x61f41c87113e04b32eb8fbaa4946b1ef98479756":"0xc37b74594bb094e66e41a1b59d7558a8a7f4f5b937643c3eab35dc55fb636945","0xa8ba9dea29234be7504fae477d2f6b1fd1078d46":"0xe178ea48be504a63b01df495a14200c13fa361154b4edd128ce7772d43017712","0x831c50ac59c3794185fabae289d3a5ba8b09403c":"0x9cb4849c0f233dbd56e9336fb9ff9a4f3043643543bd73a48aec74e40a1594e5","0xc8f2d6111bc7207c25eb4f944cb29f0e851a8541":"0x58f2c55bb9c92e1f86d48157d28639eeeb42d84b444c3c5be6d1a4a889d2b820","0xbca6ebd43dcb10851f398b4cb8fbade3133b2c45":"0x10ae118b22ed846c51d0a6f2428b3168915c9fe1c4d6628915d5f2b61217ca19","0x856c1365488375d21875f80d6045c956e47ed5ec":"0x6a70e4002c444bf6e741111c617eb258c1d1fd5a73e25b580d2ad0cb6bde29f4","0x356780865cd279e4d2dc6d99b32eda8fd8e8a39c":"0x071512c65dac57b306a1b0eebb28503c62acbdd50e5cc520c5bc794775485ce4","0x1baec60a021c5e26a1071776a1549c45c79951d5":"0x3b210655710954adc488f0bd032d15fab0b0acc5d1126d3b2d437e7aa3ee1eeb","0x8155eb275ea6ebd0d572a44087c948b02d794013":"0x48a2c92f5b37ff44648477ed0732be6953eded08a1b1cc8dd76dab999b9e11ab","0x6a8bbdb024861739b0dcd1700c8b8f603f1cf7c6":"0xdb82cea5229f4cbd59414b4a30d8e7b039f13b7903d59145a069cf4fefb759cb","0xb890c74caa6c052db376837e67f0476589991922":"0x48b22b7a8f37ede27f34c3456b226381aff9a071d06a16fff97d00dfbe67f240","0xd5b6d6b730b1c1be10b82a0a1c89f1db24f752c3":"0xd65cefcd0debead0adb5132f5be5d006230f3a2eb0c1b2884af05179e6fcbc66","0x2a828adcc1a3647dba43ed05375a4d0b00eea789":"0x9c42bc204c8e7ff36e49ec8e69b288110ee08b5fa36d03be232dc1d0fad7f35e","0x624a97293d8cea5ca78d538ac6599e4051a19174":"0x9f63edf978aa2208e655ff319a063c0d44f980d3fbc94bdf47132b5c9a0566b5","0xe7a3ebba0647fb07d0b21927305aa95284316993":"0xebd44234b1d30890a6f66a3f1fc0c951ba875021cb0a8753fc50d29a2b14f419","0xd74485a6600d8de95d84d5e1747480c528df1f9a":"0x3a82e7bec989f2e92d5e059dbaa56c90018c2bc419e0a2b8cbcc0ddfd3c2fbdd","0x3b1cb706e5fff494da8873ad9c1a30aa802f4522":"0x0b5d31fa59de726c191c7abb14c6d1b36762d0193dc55aef59e9693493c8b397","0xfbdb66ae3fa6f1b37a02c82751117fc3aad4572b":"0x5f20ef1aef89a786596604429a71a90398c4416f820473e88e4c878d72c2e3e3","0x4f92c13cacf198eb25698709e3d225e6a2e22dd8":"0xfb5eaac2758208fe4132b8b91c3a42b8735cea1ffef04f23abc7036dc871d655","0x18282ec61c35bef47698c3e65314c9a0ff617b3c":"0xb7c9cbe15b36cc450de19c154dd8c3c29e6b69fa0a50147e70a19e147723aa09","0xafa5e9d58e245b7f3efecc9e706b06d52cd28da1":"0x0990c773f047d4dc3c8c39a02542176cc1822971ee0f1cfe8e970434aaa5bba1","0xd32115d6e4a4dfdf0807544723d514e3f293d3b6":"0xceb6177b57287294cf36747f8ca97fb40af4addfb7564dbf9fc4f257091b2903","0x56fa56dc28081f6353737061e2278631b2659598":"0x220effb729d7ba7fe6cb74340565bf1851ba4a6bb2d8056024daa8a1ee89a805","0x3fae75cce89a972fa1b6d87bb080fb2c6060f0b3":"0xa4922fd6fc6b8fab356ba12bb457b42dca9502ce4cfc40f61f62fa2516f9975e","0xa9f913312b7ec75f755c4f3edb6e2bbd3526b918":"0xc41183263da1c5aab3df07d917b6010b466008361d3549abf0f1a8198e13be0d","0x1f627b7fb483e5b8d59aa191fec94d01753c7d24":"0x841dd8db624a8f91602ae6ac0a8d54fdf998b5544d60870d293a54ce065d31e3","0xb45de3796b206793e8ad3509202da91d35e9a6d9":"0xf8b78156e0d23fb3570adb1a32538643d7d97387a7695f01ac34f0e890e4f608","0x17332dd7f9bd584e2e83f4cffdca0a448b3b903a":"0xe0344f7ec829bdf9b7f10e0c5d900fdbad3e2c70720d973c81561414badfaad6","0x9d7822d5bb9f7b9b655669550095d2f14afac469":"0x74a387a4e8bb33866895610af34e754a7bdf081147a22ce938116cc276ba502f","0x4c0408db276ef793333baf5b226d8b180c3d0a89":"0x136eba5520852a6ae33f5cef106df79213fe162ff6b58cf1afa2100a60ea0f67"}} \ No newline at end of file diff --git a/utils/ganache_keys_checksum.json b/utils/ganache_keys_checksum.json deleted file mode 100644 index 2662d9e..0000000 --- a/utils/ganache_keys_checksum.json +++ /dev/null @@ -1,206 +0,0 @@ -{ - "addresses": { - "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1", - "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0", - "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b": "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b", - "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d": "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d", - "0xd03ea8624C8C5987235048901fB614fDcA89b117": "0xd03ea8624C8C5987235048901fB614fDcA89b117", - "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC": "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC", - "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9": "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9", - "0x28a8746e75304c0780E011BEd21C72cD78cd535E": "0x28a8746e75304c0780E011BEd21C72cD78cd535E", - "0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E": "0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E", - "0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e": "0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e", - "0x610Bb1573d1046FCb8A70Bbbd395754cD57C2b60": "0x610Bb1573d1046FCb8A70Bbbd395754cD57C2b60", - "0x855FA758c77D68a04990E992aA4dcdeF899F654A": "0x855FA758c77D68a04990E992aA4dcdeF899F654A", - "0xfA2435Eacf10Ca62ae6787ba2fB044f8733Ee843": "0xfA2435Eacf10Ca62ae6787ba2fB044f8733Ee843", - "0x64E078A8Aa15A41B85890265648e965De686bAE6": "0x64E078A8Aa15A41B85890265648e965De686bAE6", - "0x2F560290FEF1B3Ada194b6aA9c40aa71f8e95598": "0x2F560290FEF1B3Ada194b6aA9c40aa71f8e95598", - "0xf408f04F9b7691f7174FA2bb73ad6d45fD5d3CBe": "0xf408f04F9b7691f7174FA2bb73ad6d45fD5d3CBe", - "0x66FC63C2572bF3ADD0Fe5d44b97c2E614E35e9a3": "0x66FC63C2572bF3ADD0Fe5d44b97c2E614E35e9a3", - "0xF0D5BC18421fa04D0a2A2ef540ba5A9f04014BE3": "0xF0D5BC18421fa04D0a2A2ef540ba5A9f04014BE3", - "0x325A621DeA613BCFb5B1A69a7aCED0ea4AfBD73A": "0x325A621DeA613BCFb5B1A69a7aCED0ea4AfBD73A", - "0x3fD652C93dFA333979ad762Cf581Df89BaBa6795": "0x3fD652C93dFA333979ad762Cf581Df89BaBa6795", - "0x73EB6d82CFB20bA669e9c178b718d770C49BB52f": "0x73EB6d82CFB20bA669e9c178b718d770C49BB52f", - "0x9D8E5fAc117b15DaCED7C326Ae009dFE857621f1": "0x9D8E5fAc117b15DaCED7C326Ae009dFE857621f1", - "0x982a8CbE734cb8c29A6a7E02a3B0e4512148F6F9": "0x982a8CbE734cb8c29A6a7E02a3B0e4512148F6F9", - "0xCDC1E53Bdc74bBf5b5F715D6327Dca5785e228B4": "0xCDC1E53Bdc74bBf5b5F715D6327Dca5785e228B4", - "0xf5d1EAF516eF3b0582609622A221656872B82F78": "0xf5d1EAF516eF3b0582609622A221656872B82F78", - "0xf8eA26C3800D074a11bf814dB9a0735886C90197": "0xf8eA26C3800D074a11bf814dB9a0735886C90197", - "0x2647116f9304abb9F0B7aC29aBC0D9aD540506C8": "0x2647116f9304abb9F0B7aC29aBC0D9aD540506C8", - "0x80a32A0E5cA81b5a236168C21532B32e3cBC95e2": "0x80a32A0E5cA81b5a236168C21532B32e3cBC95e2", - "0x47f55A2ACe3b84B0F03717224DBB7D0Df4351658": "0x47f55A2ACe3b84B0F03717224DBB7D0Df4351658", - "0xC817898296b27589230B891f144dd71A892b0C18": "0xC817898296b27589230B891f144dd71A892b0C18", - "0x0D38e653eC28bdea5A2296fD5940aaB2D0B8875c": "0x0D38e653eC28bdea5A2296fD5940aaB2D0B8875c", - "0x1B569e8f1246907518Ff3386D523dcF373e769B6": "0x1B569e8f1246907518Ff3386D523dcF373e769B6", - "0xCBB025e7933FADfc7C830AE520Fb2FD6D28c1065": "0xCBB025e7933FADfc7C830AE520Fb2FD6D28c1065", - "0xdDEEA4839bBeD92BDAD8Ec79AE4f4Bc2Be1A3974": "0xdDEEA4839bBeD92BDAD8Ec79AE4f4Bc2Be1A3974", - "0xBC2cf859f671B78BA42EBB65Deb31Cc7fEc07019": "0xBC2cf859f671B78BA42EBB65Deb31Cc7fEc07019", - "0xF75588126126DdF76bDc8aBA91a08f31d2567Ca5": "0xF75588126126DdF76bDc8aBA91a08f31d2567Ca5", - "0x369109C74ea7159E77e180f969f7D48c2bf19b4C": "0x369109C74ea7159E77e180f969f7D48c2bf19b4C", - "0xA2A628f4eEE25F5b02B0688Ad9c1290e2e9A3D9e": "0xA2A628f4eEE25F5b02B0688Ad9c1290e2e9A3D9e", - "0x693D718cCfadE6F4A1379051D6ab998146F3173F": "0x693D718cCfadE6F4A1379051D6ab998146F3173F", - "0x845A0F9441081779110FEE40E6d5d8b90cE676eF": "0x845A0F9441081779110FEE40E6d5d8b90cE676eF", - "0xC7739909e08A9a0F303A010d46658Bdb4d5a6786": "0xC7739909e08A9a0F303A010d46658Bdb4d5a6786", - "0x99cce66d3A39C2c2b83AfCefF04c5EC56E9B2A58": "0x99cce66d3A39C2c2b83AfCefF04c5EC56E9B2A58", - "0x4b930E7b3E491e37EaB48eCC8a667c59e307ef20": "0x4b930E7b3E491e37EaB48eCC8a667c59e307ef20", - "0x02233B22860f810E32fB0751f368fE4ef21A1C05": "0x02233B22860f810E32fB0751f368fE4ef21A1C05", - "0x89c1D413758F8339Ade263E6e6bC072F1d429f32": "0x89c1D413758F8339Ade263E6e6bC072F1d429f32", - "0x61bBB5135b43F03C96570616d6d3f607b7103111": "0x61bBB5135b43F03C96570616d6d3f607b7103111", - "0x8C4cE7a10A4e38EE96feD47C628Be1FfA57Ab96e": "0x8C4cE7a10A4e38EE96feD47C628Be1FfA57Ab96e", - "0x25c1230C7EFC00cFd2fcAA3a44f30948853824bc": "0x25c1230C7EFC00cFd2fcAA3a44f30948853824bc", - "0x709F7Ae06Fe93be48FbB90FFDDd69e2746FA8506": "0x709F7Ae06Fe93be48FbB90FFDDd69e2746FA8506", - "0xc0514C03D097fCbB77a74B4DA5b594bA473b6CE1": "0xc0514C03D097fCbB77a74B4DA5b594bA473b6CE1", - "0x103b31135D99417A22684ED93cbbCD4ccD208046": "0x103b31135D99417A22684ED93cbbCD4ccD208046", - "0xf8856d473639e40f60db8979F5752A9C15903Bb2": "0xf8856d473639e40f60db8979F5752A9C15903Bb2", - "0x753897706061FDE347465055FcAc4bd040745624": "0x753897706061FDE347465055FcAc4bd040745624", - "0x7cd15A5d345558A203655e40B1afb14F936C73f7": "0x7cd15A5d345558A203655e40B1afb14F936C73f7", - "0x7d8Ae65273B9D1E6B239b36aF9AdEA0414D189B7": "0x7d8Ae65273B9D1E6B239b36aF9AdEA0414D189B7", - "0x05a561F51a2D8A092B11e20C72b5dF15A9D82278": "0x05a561F51a2D8A092B11e20C72b5dF15A9D82278", - "0x80030beCa8292f416E7906535668475c75d9c47E": "0x80030beCa8292f416E7906535668475c75d9c47E", - "0xeDA51422804340e3Dc0DD9b6D441125b5C7Cf3FF": "0xeDA51422804340e3Dc0DD9b6D441125b5C7Cf3FF", - "0xE21812faA737FF0eEec268f509ACb306BC735feC": "0xE21812faA737FF0eEec268f509ACb306BC735feC", - "0x4d85247717Cf8621D7894F36De35E8B6B6d384BC": "0x4d85247717Cf8621D7894F36De35E8B6B6d384BC", - "0x19b2d46091Dd332F0753dAbf0CF8304cf61eD1c5": "0x19b2d46091Dd332F0753dAbf0CF8304cf61eD1c5", - "0x42c7c045729a84f8e65239308cA8279D6fb21c89": "0x42c7c045729a84f8e65239308cA8279D6fb21c89", - "0xEeD15Bb091bf3F615400f6F8160aC423EaF6a413": "0xEeD15Bb091bf3F615400f6F8160aC423EaF6a413", - "0x0F6F0ecfAB78f8E54B130E3b3EBd88B3613c97D1": "0x0F6F0ecfAB78f8E54B130E3b3EBd88B3613c97D1", - "0x33A053885A8232eD78D688B43a405587ba446e5E": "0x33A053885A8232eD78D688B43a405587ba446e5E", - "0x4397655dDd031043Eb0859AD7A90c3c889E12A4d": "0x4397655dDd031043Eb0859AD7A90c3c889E12A4d", - "0x6E57514B1997029500C13007A59fb6da1CeAc7C4": "0x6E57514B1997029500C13007A59fb6da1CeAc7C4", - "0x85c38d25744f02619047B76195EcF835554F70Bc": "0x85c38d25744f02619047B76195EcF835554F70Bc", - "0x69901C8c4263A0368c19D3Cd9dC51B09BeC4C4b1": "0x69901C8c4263A0368c19D3Cd9dC51B09BeC4C4b1", - "0x256DD44a34478AceC9A7da479DBcf0C3C599AD55": "0x256DD44a34478AceC9A7da479DBcf0C3C599AD55", - "0x61f41c87113e04B32eB8FbaA4946b1ef98479756": "0x61f41c87113e04B32eB8FbaA4946b1ef98479756", - "0xA8BA9dEa29234Be7504fAE477d2f6B1fd1078D46": "0xA8BA9dEa29234Be7504fAE477d2f6B1fd1078D46", - "0x831c50Ac59c3794185fABAe289D3a5bA8B09403C": "0x831c50Ac59c3794185fABAe289D3a5bA8B09403C", - "0xc8f2d6111bc7207c25eB4f944cb29F0E851a8541": "0xc8f2d6111bc7207c25eB4f944cb29F0E851a8541", - "0xBcA6ebD43DCB10851F398b4CB8FbAdE3133b2c45": "0xBcA6ebD43DCB10851F398b4CB8FbAdE3133b2c45", - "0x856C1365488375d21875f80d6045C956E47Ed5eC": "0x856C1365488375d21875f80d6045C956E47Ed5eC", - "0x356780865cD279e4D2dC6d99B32eDA8FD8E8A39c": "0x356780865cD279e4D2dC6d99B32eDA8FD8E8A39c", - "0x1baEC60a021C5e26a1071776A1549C45C79951d5": "0x1baEC60a021C5e26a1071776A1549C45C79951d5", - "0x8155EB275eA6Ebd0d572a44087C948b02d794013": "0x8155EB275eA6Ebd0d572a44087C948b02d794013", - "0x6a8bBdB024861739B0DCD1700c8b8F603f1cf7c6": "0x6a8bBdB024861739B0DCD1700c8b8F603f1cf7c6", - "0xB890C74caA6C052Db376837E67F0476589991922": "0xB890C74caA6C052Db376837E67F0476589991922", - "0xd5B6d6b730b1C1be10b82a0A1c89f1Db24f752C3": "0xd5B6d6b730b1C1be10b82a0A1c89f1Db24f752C3", - "0x2A828ADcc1a3647DBA43eD05375a4d0B00eEA789": "0x2A828ADcc1a3647DBA43eD05375a4d0B00eEA789", - "0x624a97293d8cea5ca78D538aC6599e4051a19174": "0x624a97293d8cea5ca78D538aC6599e4051a19174", - "0xE7a3eBbA0647Fb07D0b21927305aA95284316993": "0xE7a3eBbA0647Fb07D0b21927305aA95284316993", - "0xd74485a6600d8dE95d84d5E1747480c528Df1f9a": "0xd74485a6600d8dE95d84d5E1747480c528Df1f9a", - "0x3B1Cb706E5fff494Da8873aD9C1A30aa802f4522": "0x3B1Cb706E5fff494Da8873aD9C1A30aa802f4522", - "0xfBDB66Ae3FA6F1B37A02c82751117FC3Aad4572b": "0xfBDB66Ae3FA6F1B37A02c82751117FC3Aad4572b", - "0x4F92c13CACF198eB25698709e3d225E6A2E22Dd8": "0x4F92c13CACF198eB25698709e3d225E6A2E22Dd8", - "0x18282Ec61C35bef47698C3E65314C9A0ff617b3c": "0x18282Ec61C35bef47698C3E65314C9A0ff617b3c", - "0xAfa5e9d58e245b7F3efECC9e706B06D52Cd28Da1": "0xAfa5e9d58e245b7F3efECC9e706B06D52Cd28Da1", - "0xd32115D6e4a4DFdf0807544723D514E3F293D3B6": "0xd32115D6e4a4DFdf0807544723D514E3F293D3B6", - "0x56Fa56dc28081f6353737061e2278631B2659598": "0x56Fa56dc28081f6353737061e2278631B2659598", - "0x3Fae75Cce89a972FA1b6d87Bb080fb2c6060F0B3": "0x3Fae75Cce89a972FA1b6d87Bb080fb2c6060F0B3", - "0xa9F913312b7ec75f755c4f3EdB6e2BBd3526b918": "0xa9F913312b7ec75f755c4f3EdB6e2BBd3526b918", - "0x1f627b7Fb483E5B8d59aa191FEc94D01753c7d24": "0x1f627b7Fb483E5B8d59aa191FEc94D01753c7d24", - "0xb45dE3796b206793E8aD3509202Da91D35E9A6d9": "0xb45dE3796b206793E8aD3509202Da91D35E9A6d9", - "0x17332DD7f9BD584E2E83f4cFfdCA0a448B3B903a": "0x17332DD7f9BD584E2E83f4cFfdCA0a448B3B903a", - "0x9d7822d5bB9f7b9b655669550095d2F14AFaC469": "0x9d7822d5bB9f7b9b655669550095d2F14AFaC469", - "0x4C0408DB276Ef793333BAF5B226d8b180c3D0A89": "0x4C0408DB276Ef793333BAF5B226d8b180c3D0A89" - }, - "private_keys": { - "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d", - "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0": "0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1", - "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b": "0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c", - "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d": "0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913", - "0xd03ea8624C8C5987235048901fB614fDcA89b117": "0xadd53f9a7e588d003326d1cbf9e4a43c061aadd9bc938c843a79e7b4fd2ad743", - "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC": "0x395df67f0c2d2d9fe1ad08d1bc8b6627011959b79c53d7dd6a3536a33ab8a4fd", - "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9": "0xe485d098507f54e7733a205420dfddbe58db035fa577fc294ebd14db90767a52", - "0x28a8746e75304c0780E011BEd21C72cD78cd535E": "0xa453611d9419d0e56f499079478fd72c37b251a94bfde4d19872c44cf65386e3", - "0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E": "0x829e924fdf021ba3dbbc4225edfece9aca04b929d6e75613329ca6f1d31c0bb4", - "0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e": "0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773", - "0x610Bb1573d1046FCb8A70Bbbd395754cD57C2b60": "0x77c5495fbb039eed474fc940f29955ed0531693cc9212911efd35dff0373153f", - "0x855FA758c77D68a04990E992aA4dcdeF899F654A": "0xd99b5b29e6da2528bf458b26237a6cf8655a3e3276c1cdc0de1f98cefee81c01", - "0xfA2435Eacf10Ca62ae6787ba2fB044f8733Ee843": "0x9b9c613a36396172eab2d34d72331c8ca83a358781883a535d2941f66db07b24", - "0x64E078A8Aa15A41B85890265648e965De686bAE6": "0x0874049f95d55fb76916262dc70571701b5c4cc5900c0691af75f1a8a52c8268", - "0x2F560290FEF1B3Ada194b6aA9c40aa71f8e95598": "0x21d7212f3b4e5332fd465877b64926e3532653e2798a11255a46f533852dfe46", - "0xf408f04F9b7691f7174FA2bb73ad6d45fD5d3CBe": "0x47b65307d0d654fd4f786b908c04af8fface7710fc998b37d219de19c39ee58c", - "0x66FC63C2572bF3ADD0Fe5d44b97c2E614E35e9a3": "0x66109972a14d82dbdb6894e61f74708f26128814b3359b64f8b66565679f7299", - "0xF0D5BC18421fa04D0a2A2ef540ba5A9f04014BE3": "0x2eac15546def97adc6d69ca6e28eec831189baa2533e7910755d15403a0749e8", - "0x325A621DeA613BCFb5B1A69a7aCED0ea4AfBD73A": "0x2e114163041d2fb8d45f9251db259a68ee6bdbfd6d10fe1ae87c5c4bcd6ba491", - "0x3fD652C93dFA333979ad762Cf581Df89BaBa6795": "0xae9a2e131e9b359b198fa280de53ddbe2247730b881faae7af08e567e58915bd", - "0x73EB6d82CFB20bA669e9c178b718d770C49BB52f": "0xd09ba371c359f10f22ccda12fd26c598c7921bda3220c9942174562bc6a36fe8", - "0x9D8E5fAc117b15DaCED7C326Ae009dFE857621f1": "0x2d2719c6a828911ed0c50d5a6c637b63353e77cf57ea80b8e90e630c4687e9c5", - "0x982a8CbE734cb8c29A6a7E02a3B0e4512148F6F9": "0xd353907ab062133759f149a3afcb951f0f746a65a60f351ba05a3ebf26b67f5c", - "0xCDC1E53Bdc74bBf5b5F715D6327Dca5785e228B4": "0x971c58af72fd8a158d4e654cfbe98f5de024d28547005909684f58c9c46a25c4", - "0xf5d1EAF516eF3b0582609622A221656872B82F78": "0x85d168288e7fcf84b1841e447fc7945b1e27bfe9a3776367079a6427405eac66", - "0xf8eA26C3800D074a11bf814dB9a0735886C90197": "0xf3da3ac70552606ed09d16dd2808c924826094f0c5cbfcb4f2e0e1cfc70ff8dd", - "0x2647116f9304abb9F0B7aC29aBC0D9aD540506C8": "0xbf20e9c05d70ce59a6b125eab3b4122eb75044a33749c4c5a77e3b0b86fa091e", - "0x80a32A0E5cA81b5a236168C21532B32e3cBC95e2": "0x647442126fdb80c6aec75a0d75a6fe1b31a4e204d29a2c446f550c4115cac139", - "0x47f55A2ACe3b84B0F03717224DBB7D0Df4351658": "0xef78746d079c9d72d2e9a3c10447d1d4aaae6a51541d0296da4fc9ec7e060aff", - "0xC817898296b27589230B891f144dd71A892b0C18": "0xc95286117cd74213417aeca52118ccd03ec240582f0a9a3e4ef7b434523179f3", - "0x0D38e653eC28bdea5A2296fD5940aaB2D0B8875c": "0x21118f9a6de181061a2abd549511105adb4877cf9026f271092e6813b7cf58ab", - "0x1B569e8f1246907518Ff3386D523dcF373e769B6": "0x1166189cdf129cdcb011f2ad0e5be24f967f7b7026d162d7c36073b12020b61c", - "0xCBB025e7933FADfc7C830AE520Fb2FD6D28c1065": "0x1aa14c63d481dcc1185a654eb52c9c0749d07ac8f30ef17d45c3c391d9bf68eb", - "0xdDEEA4839bBeD92BDAD8Ec79AE4f4Bc2Be1A3974": "0x4a23fe455a34bb47f8f3282a4f6d36c22987275f0bb9aacb251568df7d038385", - "0xBC2cf859f671B78BA42EBB65Deb31Cc7fEc07019": "0x2450bb2893d0bddf92f4ac88cb65a8e94b56e89f7ec3e46c9c88b2b46ebe3ca5", - "0xF75588126126DdF76bDc8aBA91a08f31d2567Ca5": "0xf934aded8693d6b2b61ccbb3bc1f86a86afbbd8622a5eb3401b2f8de9863b07b", - "0x369109C74ea7159E77e180f969f7D48c2bf19b4C": "0xc8eea9d162fe9d6852afa0d55ebe6b14b8e6fc9b0e93ae13209e2b4db48a6482", - "0xA2A628f4eEE25F5b02B0688Ad9c1290e2e9A3D9e": "0xbe146cdb15d4069e0249da35c928819cbde563dd4fe3d1ccfeda7885a52e0754", - "0x693D718cCfadE6F4A1379051D6ab998146F3173F": "0x74ae0c3d566d7e73613d4ebb814b0f37a2d040060814f75e115d28469d22f4c2", - "0x845A0F9441081779110FEE40E6d5d8b90cE676eF": "0xb2b19df163d1f952df31e32c694d592e530c0b3d54c6276015bc9b0acaf982de", - "0xC7739909e08A9a0F303A010d46658Bdb4d5a6786": "0x86117111fcb34df8d0e58505969021b9308513c6e94d16172f0c8789a7130a43", - "0x99cce66d3A39C2c2b83AfCefF04c5EC56E9B2A58": "0xdcb8686c211c231be763f0a95cc02227a707643fd2631bda99fcdbd03cd9ca3d", - "0x4b930E7b3E491e37EaB48eCC8a667c59e307ef20": "0xb74ffec4abd7e93889196054d5e6ed8ea9c1c3314e77a74c00f851c47f5268fd", - "0x02233B22860f810E32fB0751f368fE4ef21A1C05": "0xba30972105ec13423116d2e5c11a8d282805ac3654bb4c1c2f5fa63f4da42dad", - "0x89c1D413758F8339Ade263E6e6bC072F1d429f32": "0x87ad1798a2d32434f72598575237528a435416da1bdc900025c415903647957e", - "0x61bBB5135b43F03C96570616d6d3f607b7103111": "0x5d4af11a54d4a5196b0073ba26a1114cb113e1339d9354c8165b8e181c89cad9", - "0x8C4cE7a10A4e38EE96feD47C628Be1FfA57Ab96e": "0xa03bf2b145b0154c2e788a1d4642d235f6ff1c8aceeb41d0d7232525da8bdb77", - "0x25c1230C7EFC00cFd2fcAA3a44f30948853824bc": "0xb1f4063952ebc0785bbc201520ed7f0c5fc15298099e60e62f8cfa456bbc2705", - "0x709F7Ae06Fe93be48FbB90FFDDd69e2746FA8506": "0x41d647879d53baddb93cfadc3f5ef4d5bdc330bec4b4ef9caace19c70a385856", - "0xc0514C03D097fCbB77a74B4DA5b594bA473b6CE1": "0x87c546d6cb8ec705bea47e2ab40f42a768b1e5900686b0cecc68c0e8b74cd789", - "0x103b31135D99417A22684ED93cbbCD4ccD208046": "0x2133975a252cc4b856c15e4fc34070e289bd0db4fcd9a292b6948c4087eff585", - "0xf8856d473639e40f60db8979F5752A9C15903Bb2": "0x0f379eafea9630e5c66e830bf8729695d1ad1ec2e356e29685b17cb430018a4d", - "0x753897706061FDE347465055FcAc4bd040745624": "0x9fb8bcd81b2517bc32823b5e8ab61fd22161b6412da28fe536142477229b7d41", - "0x7cd15A5d345558A203655e40B1afb14F936C73f7": "0x10fb78c8d22bc1e112329f57cbf01f1a49f83770ef23240c3aabcebe4f96e6ac", - "0x7d8Ae65273B9D1E6B239b36aF9AdEA0414D189B7": "0xa74b0b1e8250fc075d8d770155d8e868b50da906c295e26485008acf841a2ce1", - "0x05a561F51a2D8A092B11e20C72b5dF15A9D82278": "0xcd83d658af9dbc7dde51f5b112e96db398a37f317b00475bfa0847311b128d73", - "0x80030beCa8292f416E7906535668475c75d9c47E": "0xd2aabff72208f9338a05a3dfab90b5c5d7c56f20dff7ff6aaf610e7091a42339", - "0xeDA51422804340e3Dc0DD9b6D441125b5C7Cf3FF": "0x7bafc085689805d768c4315b3845c30a388901884213f5d1ae5b155c65aba0b4", - "0xE21812faA737FF0eEec268f509ACb306BC735feC": "0xa97d6c2f4289adaa0543a17b29f6c0693aa2d6f9052d922482c7fc3b0740fb25", - "0x4d85247717Cf8621D7894F36De35E8B6B6d384BC": "0xf14585a1cf1f7cb162d5b93f9dc14729b0afd9afb444029394fae2456204e626", - "0x19b2d46091Dd332F0753dAbf0CF8304cf61eD1c5": "0x98e1895381b0a03b614af5a148b648cfb9db6781c6abd06035cdbd1407c27677", - "0x42c7c045729a84f8e65239308cA8279D6fb21c89": "0x20b8cb7817ba1df58dae2bcd67abf22663481d799b8bef7aeb5b9dd2b8a43ed1", - "0xEeD15Bb091bf3F615400f6F8160aC423EaF6a413": "0xd3d74f4aa37367842f0f941dec2ae79fea870fa4496865700d2841cf14aa2075", - "0x0F6F0ecfAB78f8E54B130E3b3EBd88B3613c97D1": "0xd6838f8f23b4bd6de81335a193c806c9fd514132ebaf8e7c06e7ae8158958635", - "0x33A053885A8232eD78D688B43a405587ba446e5E": "0x20192d5a5e11658557831c31e29f272ac411525e10ed43a910046d3793b79272", - "0x4397655dDd031043Eb0859AD7A90c3c889E12A4d": "0x423658d565be792c72ed016acbf9616ea3bf5d2b8de60e9af8caacd519047836", - "0x6E57514B1997029500C13007A59fb6da1CeAc7C4": "0xbd9c1130153fa69647001d40508a08dbd13688024f8cbb5ba9bb233b203d589f", - "0x85c38d25744f02619047B76195EcF835554F70Bc": "0xb1b53bd2b1a3d6177aad317da256e8558dc08fea1dd4d735bbd714d752868889", - "0x69901C8c4263A0368c19D3Cd9dC51B09BeC4C4b1": "0xc2312488bbef793579de1f2eaacf9080b51356f51154c78e998831c9e1c1ba94", - "0x256DD44a34478AceC9A7da479DBcf0C3C599AD55": "0x27412066bc3988b127e8b91d80df981df0d941aa71aeafe22b8de46f21358a5b", - "0x61f41c87113e04B32eB8FbaA4946b1ef98479756": "0xc37b74594bb094e66e41a1b59d7558a8a7f4f5b937643c3eab35dc55fb636945", - "0xA8BA9dEa29234Be7504fAE477d2f6B1fd1078D46": "0xe178ea48be504a63b01df495a14200c13fa361154b4edd128ce7772d43017712", - "0x831c50Ac59c3794185fABAe289D3a5bA8B09403C": "0x9cb4849c0f233dbd56e9336fb9ff9a4f3043643543bd73a48aec74e40a1594e5", - "0xc8f2d6111bc7207c25eB4f944cb29F0E851a8541": "0x58f2c55bb9c92e1f86d48157d28639eeeb42d84b444c3c5be6d1a4a889d2b820", - "0xBcA6ebD43DCB10851F398b4CB8FbAdE3133b2c45": "0x10ae118b22ed846c51d0a6f2428b3168915c9fe1c4d6628915d5f2b61217ca19", - "0x856C1365488375d21875f80d6045C956E47Ed5eC": "0x6a70e4002c444bf6e741111c617eb258c1d1fd5a73e25b580d2ad0cb6bde29f4", - "0x356780865cD279e4D2dC6d99B32eDA8FD8E8A39c": "0x071512c65dac57b306a1b0eebb28503c62acbdd50e5cc520c5bc794775485ce4", - "0x1baEC60a021C5e26a1071776A1549C45C79951d5": "0x3b210655710954adc488f0bd032d15fab0b0acc5d1126d3b2d437e7aa3ee1eeb", - "0x8155EB275eA6Ebd0d572a44087C948b02d794013": "0x48a2c92f5b37ff44648477ed0732be6953eded08a1b1cc8dd76dab999b9e11ab", - "0x6a8bBdB024861739B0DCD1700c8b8F603f1cf7c6": "0xdb82cea5229f4cbd59414b4a30d8e7b039f13b7903d59145a069cf4fefb759cb", - "0xB890C74caA6C052Db376837E67F0476589991922": "0x48b22b7a8f37ede27f34c3456b226381aff9a071d06a16fff97d00dfbe67f240", - "0xd5B6d6b730b1C1be10b82a0A1c89f1Db24f752C3": "0xd65cefcd0debead0adb5132f5be5d006230f3a2eb0c1b2884af05179e6fcbc66", - "0x2A828ADcc1a3647DBA43eD05375a4d0B00eEA789": "0x9c42bc204c8e7ff36e49ec8e69b288110ee08b5fa36d03be232dc1d0fad7f35e", - "0x624a97293d8cea5ca78D538aC6599e4051a19174": "0x9f63edf978aa2208e655ff319a063c0d44f980d3fbc94bdf47132b5c9a0566b5", - "0xE7a3eBbA0647Fb07D0b21927305aA95284316993": "0xebd44234b1d30890a6f66a3f1fc0c951ba875021cb0a8753fc50d29a2b14f419", - "0xd74485a6600d8dE95d84d5E1747480c528Df1f9a": "0x3a82e7bec989f2e92d5e059dbaa56c90018c2bc419e0a2b8cbcc0ddfd3c2fbdd", - "0x3B1Cb706E5fff494Da8873aD9C1A30aa802f4522": "0x0b5d31fa59de726c191c7abb14c6d1b36762d0193dc55aef59e9693493c8b397", - "0xfBDB66Ae3FA6F1B37A02c82751117FC3Aad4572b": "0x5f20ef1aef89a786596604429a71a90398c4416f820473e88e4c878d72c2e3e3", - "0x4F92c13CACF198eB25698709e3d225E6A2E22Dd8": "0xfb5eaac2758208fe4132b8b91c3a42b8735cea1ffef04f23abc7036dc871d655", - "0x18282Ec61C35bef47698C3E65314C9A0ff617b3c": "0xb7c9cbe15b36cc450de19c154dd8c3c29e6b69fa0a50147e70a19e147723aa09", - "0xAfa5e9d58e245b7F3efECC9e706B06D52Cd28Da1": "0x0990c773f047d4dc3c8c39a02542176cc1822971ee0f1cfe8e970434aaa5bba1", - "0xd32115D6e4a4DFdf0807544723D514E3F293D3B6": "0xceb6177b57287294cf36747f8ca97fb40af4addfb7564dbf9fc4f257091b2903", - "0x56Fa56dc28081f6353737061e2278631B2659598": "0x220effb729d7ba7fe6cb74340565bf1851ba4a6bb2d8056024daa8a1ee89a805", - "0x3Fae75Cce89a972FA1b6d87Bb080fb2c6060F0B3": "0xa4922fd6fc6b8fab356ba12bb457b42dca9502ce4cfc40f61f62fa2516f9975e", - "0xa9F913312b7ec75f755c4f3EdB6e2BBd3526b918": "0xc41183263da1c5aab3df07d917b6010b466008361d3549abf0f1a8198e13be0d", - "0x1f627b7Fb483E5B8d59aa191FEc94D01753c7d24": "0x841dd8db624a8f91602ae6ac0a8d54fdf998b5544d60870d293a54ce065d31e3", - "0xb45dE3796b206793E8aD3509202Da91D35E9A6d9": "0xf8b78156e0d23fb3570adb1a32538643d7d97387a7695f01ac34f0e890e4f608", - "0x17332DD7f9BD584E2E83f4cFfdCA0a448B3B903a": "0xe0344f7ec829bdf9b7f10e0c5d900fdbad3e2c70720d973c81561414badfaad6", - "0x9d7822d5bB9f7b9b655669550095d2F14AFaC469": "0x74a387a4e8bb33866895610af34e754a7bdf081147a22ce938116cc276ba502f", - "0x4C0408DB276Ef793333BAF5B226d8b180c3D0A89": "0x136eba5520852a6ae33f5cef106df79213fe162ff6b58cf1afa2100a60ea0f67" - } -} \ No newline at end of file